aboutsummaryrefslogtreecommitdiff
path: root/kamon-core/src/main/scala/kamon/status
diff options
context:
space:
mode:
Diffstat (limited to 'kamon-core/src/main/scala/kamon/status')
-rw-r--r--kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala65
-rw-r--r--kamon-core/src/main/scala/kamon/status/Status.scala37
-rw-r--r--kamon-core/src/main/scala/kamon/status/StatusPageServer.scala65
3 files changed, 167 insertions, 0 deletions
diff --git a/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala
new file mode 100644
index 00000000..c7c480fd
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala
@@ -0,0 +1,65 @@
+package kamon.status
+
+import com.grack.nanojson.JsonWriter
+import kamon.module.Module
+import kamon.module.Module.Registry
+import java.lang.{StringBuilder => JavaStringBuilder}
+
+import com.typesafe.config.ConfigRenderOptions
+
+
+trait JsonMarshalling[T] {
+
+ /**
+ * Implementations should append a Json object or array that describes the given instance members and any
+ * additional information that is expected to be shown in the status mini site.
+ */
+ def toJson(instance: T, builder: JavaStringBuilder): Unit
+}
+
+object JsonMarshalling {
+
+ implicit object ModuleRegistryStatusJsonMarshalling extends JsonMarshalling[Module.Registry.Status] {
+ override def toJson(instance: Registry.Status, builder: JavaStringBuilder): Unit = {
+ val array = JsonWriter.on(builder)
+ .`object`()
+ .array("modules")
+
+ instance.modules.foreach(m => {
+ array.`object`()
+ .value("name", m.name)
+ .value("description", m.description)
+ .value("enabled", m.enabled)
+ .value("started", m.started)
+ .end()
+ })
+
+ array.end().end().done()
+ }
+ }
+
+ implicit object BaseInfoJsonMarshalling extends JsonMarshalling[Status.BaseInfo] {
+ override def toJson(instance: Status.BaseInfo, builder: JavaStringBuilder): Unit = {
+ val baseConfigJson = JsonWriter.on(builder)
+ .`object`()
+ .value("version", instance.version)
+ .value("config", instance.config.root().render(ConfigRenderOptions.concise()))
+
+ baseConfigJson.`object`("environment")
+ .value("service", instance.environment.service)
+ .value("host", instance.environment.host)
+ .value("instance", instance.environment.instance)
+ .`object`("tags")
+
+ instance.environment.tags.foreach {
+ case (key, value) => baseConfigJson.value(key, value)
+ }
+
+ baseConfigJson
+ .end() // ends tags
+ .end() // ends environment
+ .end() // ends base config
+ .done()
+ }
+ }
+} \ No newline at end of file
diff --git a/kamon-core/src/main/scala/kamon/status/Status.scala b/kamon-core/src/main/scala/kamon/status/Status.scala
new file mode 100644
index 00000000..f212ff55
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/status/Status.scala
@@ -0,0 +1,37 @@
+package kamon.status
+
+import com.typesafe.config.Config
+import kamon.{Configuration, Environment, Kamon}
+import kamon.module.Module
+
+
+/**
+ * Allows accessing of component's status APIs without exposing any other internal API from those components.
+ */
+class Status(_moduleRegistry: Module.Registry, configuration: Configuration) {
+
+ def baseInfo(): Status.BaseInfo =
+ Status.BaseInfo(BuildInfo.version, Kamon.environment, configuration.config())
+
+ /**
+ * Information about what modules have been detected in the classpath and their current status.
+ */
+ def moduleRegistry(): Module.Registry.Status =
+ _moduleRegistry.status()
+}
+
+
+
+
+object Status {
+
+ case class BaseInfo(
+ version: String,
+ environment: Environment,
+ config: Config
+ )
+
+
+
+
+}
diff --git a/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala b/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala
new file mode 100644
index 00000000..4e2bf03c
--- /dev/null
+++ b/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala
@@ -0,0 +1,65 @@
+package kamon.status
+
+import fi.iki.elonen.NanoHTTPD
+import fi.iki.elonen.NanoHTTPD.Response
+import fi.iki.elonen.NanoHTTPD.Response.{Status => StatusCode}
+
+class StatusPageServer(hostname: String, port: Int, resourceLoader: ClassLoader, status: Status)
+ extends NanoHTTPD(hostname, port) {
+
+ private val RootResourceDirectory = "status"
+ private val ResourceExtensionRegex = ".*\\.([a-zA-Z]*)".r
+
+
+ override def serve(session: NanoHTTPD.IHTTPSession): NanoHTTPD.Response = {
+ if(session.getMethod() == NanoHTTPD.Method.GET) {
+ if(session.getUri().startsWith("/status")) {
+
+ // Serve the current status data on Json.
+ session.getUri() match {
+ case "/status/config" => json(status.baseInfo())
+ case "/status/modules" => json(status.moduleRegistry())
+ case _ => NotFound
+ }
+
+ } else {
+ // Serve resources from the status page folder.
+ val resource = if (session.getUri() == "/") "/index.html" else session.getUri()
+ val resourcePath = RootResourceDirectory + resource
+ val resourceStream = resourceLoader.getResourceAsStream(resourcePath)
+
+ if (resourceStream == null) NotFound else {
+ NanoHTTPD.newChunkedResponse(StatusCode.OK, mimeType(resource), resourceStream)
+ }
+ }
+
+ } else NotAllowed
+ }
+
+ private def mimeType(resource: String): String = {
+ val ResourceExtensionRegex(resourceExtension) = resource
+ resourceExtension match {
+ case "css" => "text/css"
+ case "js" => "application/javascript"
+ case "ico" => "image/x-icon"
+ case "html" => "text/html"
+ case _ => "text/plain"
+ }
+ }
+
+ private def json[T : JsonMarshalling](instance: T): Response = {
+ val builder = new java.lang.StringBuilder()
+ implicitly[JsonMarshalling[T]].toJson(instance, builder)
+ NanoHTTPD.newFixedLengthResponse(StatusCode.OK, "application/json", builder.toString())
+ }
+
+ private val NotAllowed = NanoHTTPD.newFixedLengthResponse(
+ StatusCode.METHOD_NOT_ALLOWED,
+ NanoHTTPD.MIME_PLAINTEXT,
+ "Only GET requests are allowed.")
+
+ private val NotFound = NanoHTTPD.newFixedLengthResponse(
+ StatusCode.NOT_FOUND,
+ NanoHTTPD.MIME_PLAINTEXT,
+ "The requested resource was not found.")
+} \ No newline at end of file