From 7e9cb172cb7ead8e5579ffb1f0b0ba3ffef90605 Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Mon, 4 Feb 2019 23:59:54 +0100 Subject: include environment data in the status page, plust skeleton for instrumentation --- .../src/main/resources/status/css/app.8ec53011.css | 1 - .../src/main/resources/status/css/app.dbe764fe.css | 1 + kamon-core/src/main/resources/status/index.html | 2 +- .../src/main/resources/status/js/about.5273d30c.js | 1 - .../src/main/resources/status/js/app.218647d9.js | 1 - .../src/main/resources/status/js/app.5d48f1c1.js | 1 + kamon-core/src/main/scala/kamon/Kamon.scala | 4 + .../main/scala/kamon/status/JsonMarshalling.scala | 31 ++++++++ .../src/main/scala/kamon/status/Status.scala | 90 +++++++++++++++++++++- .../main/scala/kamon/status/StatusPageServer.scala | 10 ++- kamon-status/src/api/StatusApi.ts | 34 ++++++++ kamon-status/src/components/MetricList.vue | 20 +---- kamon-status/src/components/ModuleStatus.vue | 4 +- kamon-status/src/styles/main.scss | 20 ++++- kamon-status/src/views/Overview.vue | 70 +++++++++++++++-- 15 files changed, 252 insertions(+), 38 deletions(-) delete mode 100644 kamon-core/src/main/resources/status/css/app.8ec53011.css create mode 100644 kamon-core/src/main/resources/status/css/app.dbe764fe.css delete mode 100644 kamon-core/src/main/resources/status/js/about.5273d30c.js delete mode 100644 kamon-core/src/main/resources/status/js/app.218647d9.js create mode 100644 kamon-core/src/main/resources/status/js/app.5d48f1c1.js diff --git a/kamon-core/src/main/resources/status/css/app.8ec53011.css b/kamon-core/src/main/resources/status/css/app.8ec53011.css deleted file mode 100644 index 5eae8dc1..00000000 --- a/kamon-core/src/main/resources/status/css/app.8ec53011.css +++ /dev/null @@ -1 +0,0 @@ -.header{height:70px;background-color:#fff;-webkit-box-shadow:0 5px 9px 1px rgba(0,0,0,.1);box-shadow:0 5px 9px 1px rgba(0,0,0,.1)}.header .navigation{line-height:70px}.header .navigation .navigation-link,.header .navigation a{display:inline-block;padding:0 .5rem;text-transform:uppercase;text-decoration:none;color:#b3b3b3}.header .navigation .navigation-link:hover,.header .navigation a:hover{color:#888}.outer[data-v-1f523759]{background-color:#fff;-webkit-box-shadow:0 2px 9px 1px rgba(0,0,0,.1);box-shadow:0 2px 9px 1px rgba(0,0,0,.1)}.outer .heading[data-v-1f523759]{font-size:.9rem;color:#a5a5a5}.outer .message[data-v-1f523759]{color:#868686;font-weight:600;font-size:1.5rem}.outer .caption[data-v-1f523759]{color:#a5a5a5}.outer .critical[data-v-1f523759]{color:#ff6e6b}hr[data-v-1f523759]{margin:1px;border-color:#f3f3f3}body{background-color:#f7f7f7;font-size:16px;color:#616161;font-family:Open Sans,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3{font-weight:300} \ No newline at end of file diff --git a/kamon-core/src/main/resources/status/css/app.dbe764fe.css b/kamon-core/src/main/resources/status/css/app.dbe764fe.css new file mode 100644 index 00000000..3db8d313 --- /dev/null +++ b/kamon-core/src/main/resources/status/css/app.dbe764fe.css @@ -0,0 +1 @@ +.header{height:70px;background-color:#fff;-webkit-box-shadow:0 5px 9px 1px rgba(0,0,0,.1);box-shadow:0 5px 9px 1px rgba(0,0,0,.1)}.header .navigation{line-height:70px}.header .navigation .navigation-link,.header .navigation a{display:inline-block;padding:0 .5rem;text-transform:uppercase;text-decoration:none;color:#b3b3b3}.header .navigation .navigation-link:hover,.header .navigation a:hover{color:#888}.outer[data-v-836357a0]{background-color:#fff;-webkit-box-shadow:0 2px 9px 1px rgba(0,0,0,.1);box-shadow:0 2px 9px 1px rgba(0,0,0,.1)}hr[data-v-836357a0]{margin:1px;border-color:#f3f3f3}.status-indicator[data-v-63f6fd9e]{font-size:.9rem;border-radius:.3rem;color:#fff;margin:.5rem 1rem;padding:.1rem 1rem;background-color:#ccc}.critical[data-v-63f6fd9e]{background-color:#ff6e6b}.healthy[data-v-63f6fd9e]{background-color:#6ada87}hr[data-v-63f6fd9e]{margin:1px;border-color:#f3f3f3}.search-box input[data-v-5342e993]{color:#676767;height:2.5rem;border:none;border-radius:.3rem;background-color:#efefef}.search-box input[data-v-5342e993]:focus{outline:none}.search-box[data-v-5342e993] ::-webkit-input-placeholder{color:#929292}.search-box[data-v-5342e993] ::-ms-input-placeholder{color:#929292}.search-box[data-v-5342e993] ::placeholder{color:#929292}.search-box .search-stats[data-v-5342e993]{position:absolute;line-height:2.5rem;right:0;padding-right:1rem}body{background-color:#f7f7f7;font-size:14px;color:#929292;font-family:Open Sans,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}h1,h2,h3{font-weight:300}.text-label{color:#b3b3b3}.tag-container{margin:0 -.3rem}.tag{display:inline-block;background-color:#f4f4f4;margin:.3rem;padding:.1rem .5rem;border-radius:.2rem}.tag,.tag-value{overflow-wrap:anywhere}.tag-value{color:#676767} \ No newline at end of file diff --git a/kamon-core/src/main/resources/status/index.html b/kamon-core/src/main/resources/status/index.html index 9cfaa09c..b3795496 100644 --- a/kamon-core/src/main/resources/status/index.html +++ b/kamon-core/src/main/resources/status/index.html @@ -1 +1 @@ -Kamon Status
\ No newline at end of file +Kamon Status
\ No newline at end of file diff --git a/kamon-core/src/main/resources/status/js/about.5273d30c.js b/kamon-core/src/main/resources/status/js/about.5273d30c.js deleted file mode 100644 index c13c6e2e..00000000 --- a/kamon-core/src/main/resources/status/js/about.5273d30c.js +++ /dev/null @@ -1 +0,0 @@ -(window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["about"],{f820:function(t,e,n){"use strict";n.r(e);var a=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},s=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"about"},[n("h1",[t._v("This is an about page")])])}],u=n("2877"),i={},o=Object(u["a"])(i,a,s,!1,null,null,null);o.options.__file="About.vue";e["default"]=o.exports}}]); \ No newline at end of file diff --git a/kamon-core/src/main/resources/status/js/app.218647d9.js b/kamon-core/src/main/resources/status/js/app.218647d9.js deleted file mode 100644 index 5ef0a92a..00000000 --- a/kamon-core/src/main/resources/status/js/app.218647d9.js +++ /dev/null @@ -1 +0,0 @@ -(function(t){function n(n){for(var r,o,i=n[0],u=n[1],c=n[2],l=0,p=[];l0};return this.moduleRegistry.forEach(function(e){var r=e.modules.filter(n);r.length>0&&(t.status=c.Healthy,t.message=r.length+" Active")}),t},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"metricsStatus",{get:function(){var t={heading:"Metrics",message:"Unknown",status:c.Unknown};return this.metricsRegistry.forEach(function(n){t.message=n.metrics.length+" Metrics"}),t},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"instrumentationStatus",{get:function(){return{heading:"Instrumentation",message:"Unknown",status:c.Unknown}},enumerable:!0,configurable:!0}),n.prototype.mounted=function(){this.refreshData()},n.prototype.refreshData=function(){var t=this;x.configStatus().then(function(n){t.config=Object(h["some"])(n)}),x.metricRegistryStatus().then(function(n){t.metricsRegistry=Object(h["some"])(n)}),x.moduleRegistryStatus().then(function(n){t.moduleRegistry=Object(h["some"])(n)})},n=v["a"]([Object(m["a"])({components:{"status-card":j}})],n),n}(m["c"]),I=P,R=I,U=Object(o["a"])(R,p,d,!1,null,null,null);U.options.__file="Overview.vue";var E=U.exports;r["default"].use(f["a"]);var M=new f["a"]({routes:[{path:"/",name:"overview",component:E},{path:"/about",name:"about",component:function(){return e.e("about").then(e.bind(null,"f820"))}}]});e("ab8b"),e("fb98");r["default"].config.productionTip=!1,new r["default"]({router:M,render:function(t){return t(l)}}).$mount("#app")},fb98:function(t,n,e){}}); \ No newline at end of file diff --git a/kamon-core/src/main/resources/status/js/app.5d48f1c1.js b/kamon-core/src/main/resources/status/js/app.5d48f1c1.js new file mode 100644 index 00000000..f6d4ec9c --- /dev/null +++ b/kamon-core/src/main/resources/status/js/app.5d48f1c1.js @@ -0,0 +1 @@ +(function(t){function e(e){for(var r,i,o=e[0],c=e[1],u=e[2],p=0,f=[];p0?n("div",{staticClass:"tag-container"},t._l(Object.keys(t.environmentTags),function(e){return n("span",{key:e,staticClass:"tag"},[t._v("\n "+t._s(e)+"="),n("span",{staticClass:"tag-value"},[t._v(t._s(t.environmentTags[e]))])])}),0):n("div",[n("h6",[t._v("None")])])])])])],1),t._m(2),t._l(t.reporterModules,function(t){return n("div",{key:t.name,staticClass:"col-12 py-1"},[n("module-status",{attrs:{moduleStatus:t}})],1)}),t.plainModules.length>0?n("div",{staticClass:"col-12 pt-4 pb-2"},[n("h2",[t._v("Modules")])]):t._e(),t._l(t.plainModules,function(t){return n("div",{key:t.name,staticClass:"col-12 py-1"},[n("module-status",{attrs:{moduleStatus:t}})],1)}),t.metrics.length>0?n("div",{staticClass:"col-12 pt-4 pb-2"},[n("h2",[t._v("Metrics")])]):t._e(),n("div",{staticClass:"col-12 mb-5"},[n("metric-list",{attrs:{metrics:t.metrics}})],1)],2)])},f=[function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"col-12 pt-4 pb-2"},[n("h3",[t._v("Status")])])},function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"col-12 pt-4 pb-2"},[n("h3",[t._v("Environment")])])},function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"col-12 pt-4 pb-2"},[n("h3",[t._v("Reporters")])])}],d=n("9ab4"),v=n("60a3"),m=n("1b15"),b=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("card",[n("div",{staticClass:"row"},[n("div",{staticClass:"col-12"},[n("div",{staticClass:"py-2 px-3 text-uppercase text-label"},[t._v("\n "+t._s(t.moduleStatus.kind)+"\n ")]),n("hr")]),n("div",{staticClass:"col"},[n("div",{staticClass:"px-3 py-3"},[n("h5",{staticClass:"mb-0"},[t._v(t._s(t.moduleStatus.name))]),n("div",{staticClass:"text-label"},[t._v("\n "+t._s(t.moduleStatus.description)+"\n ")])])]),n("div",{staticClass:"col-auto"},[n("div",{staticClass:"status-indicator text-center",class:t.runStatus.class},[t._v(t._s(t.runStatus.message))]),n("div",{staticClass:"status-indicator text-center"},[t._v(t._s(t.discoveryStatus.message))])])])])},g=[],h=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"outer py-1"},[t._t("default")],2)},y=[],_=(n("4fc8"),{}),C=Object(i["a"])(_,h,y,!1,null,"836357a0",null);C.options.__file="Card.vue";var O=C.exports,x=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return d["b"](e,t),Object.defineProperty(e.prototype,"discoveryStatus",{get:function(){return this.moduleStatus.isProgrammaticallyRegistered?{message:"manual",class:""}:{message:"automatic",class:""}},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"runStatus",{get:function(){return this.moduleStatus.isStarted?{message:"started",class:"healthy"}:{message:"disabled",class:"critical"}},enumerable:!0,configurable:!0}),d["a"]([Object(v["b"])()],e.prototype,"moduleStatus",void 0),e=d["a"]([Object(v["a"])({components:{card:O}})],e),e}(v["c"]),j=x,S=j,w=(n("b9e3"),Object(i["a"])(S,b,g,!1,null,"63f6fd9e",null));w.options.__file="ModuleStatus.vue";var P=w.exports,M=function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"row no-gutters"},[n("div",{staticClass:"col-12"},[n("div",{staticClass:"search-box mb-3"},[n("input",{directives:[{name:"model",rawName:"v-model",value:t.filterPattern,expression:"filterPattern"}],staticClass:"w-100 px-3 py-2",attrs:{type:"text",placeholder:"filter"},domProps:{value:t.filterPattern},on:{input:function(e){e.target.composing||(t.filterPattern=e.target.value)}}}),n("span",{staticClass:"search-stats"},[t._v(t._s(t.searchStats))])])]),n("div",{staticClass:"col-12"},[t.matchedMetrics.length>0?n("card",t._l(t.matchedMetrics,function(e,r){return n("div",{key:e.search,staticClass:"row no-gutters"},[n("div",{staticClass:"col-12 px-3 pt-1 pb-3"},[n("div",{staticClass:"text-uppercase text-label"},[t._v(t._s(e.type))]),n("h5",[t._v(t._s(e.name))]),n("div",{staticClass:"tag-container"},t._l(Object.keys(e.tags),function(r){return n("span",{key:r,staticClass:"tag"},[t._v("\n "+t._s(r)+"="),n("span",{staticClass:"tag-value"},[t._v(t._s(e.tags[r]))])])}),0)]),r0?this.matchedMetrics.length+" matched":this.totalMetrics+" metrics"},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"matchedMetrics",{get:function(){var t=this;return this.filterPattern.length>0?this.metrics.filter(function(e){return null!=e.search.match(t.filterRegex)}):this.metrics},enumerable:!0,configurable:!0}),d["a"]([Object(v["b"])()],e.prototype,"metrics",void 0),e=d["a"]([Object(v["a"])({components:{card:O}})],e),e}(v["c"]),E=R,$=E,T=(n("ab9c"),Object(i["a"])($,M,k,!1,null,"5342e993",null));T.options.__file="MetricList.vue";var U,A=T.exports,J=n("bc3a"),N=n.n(J);(function(t){t["Combined"]="combined",t["Metric"]="metric",t["Span"]="span",t["Plain"]="plain",t["Unknown"]="unknown"})(U||(U={}));var D=function(){function t(){}return t.settings=function(){return N.a.get("/status/settings").then(function(t){var e=JSON.parse(t.data.config);return{version:t.data.version,environment:t.data.environment,config:e}})},t.moduleRegistryStatus=function(){return N.a.get("/status/modules").then(function(t){return t.data})},t.metricRegistryStatus=function(){return N.a.get("/status/metrics").then(function(t){var e=t.data,n=function(t,e){return t+":"+e+" "};return e.metrics.forEach(function(t){"RangeSampler"===t.type&&(t.type="Range Sampler");var e="";Object.keys(t.tags).forEach(function(r){e+=n(r,t.tags[r])}),t.search=n("name",t.name.toLowerCase())+n("type",t.type.toLowerCase())+e}),e})},t.instrumentationStatus=function(){return N.a.get("/status/instrumentation").then(function(t){var e={isActive:t.data.isActive,modules:{},errors:{}},n=t.data.modules;Object.keys(n).forEach(function(t){e.modules[t]=JSON.parse(n[t])});var r=t.data.errors;return Object.keys(r).forEach(function(t){e.errors[t]=JSON.parse(r[t])}),e})},t}(),L=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.settings=m["none"],e.moduleRegistry=m["none"],e.metricsRegistry=m["none"],e.instrumentation=m["none"],e}return d["b"](e,t),Object.defineProperty(e.prototype,"reporterModules",{get:function(){var t=this;return this.moduleRegistry.map(function(e){return e.modules.filter(t.isReporter)}).getOrElse([])},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"activeReporters",{get:function(){return this.reporterModules.filter(this.isStarted)},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"plainModules",{get:function(){var t=this;return this.moduleRegistry.map(function(e){return e.modules.filter(function(e){return!t.isReporter(e)})}).getOrElse([])},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"trackedMetrics",{get:function(){return this.metricsRegistry.map(function(t){return t.metrics.length})},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"instrumentationStatusMessage",{get:function(){return this.instrumentation.map(function(t){return t.isActive?"Active":"Disabled"}).getOrElse("Unknown")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"metricsStatusMessage",{get:function(){return this.trackedMetrics.map(function(t){return t+" Tracked"}).getOrElse("Unknown")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"metrics",{get:function(){return this.metricsRegistry.map(function(t){return t.metrics}).getOrElse([])},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"instance",{get:function(){return this.settings.map(function(t){return t.environment.instance}).getOrElse("Unknown")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"host",{get:function(){return this.settings.map(function(t){return t.environment.host}).getOrElse("Unknown")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"service",{get:function(){return this.settings.map(function(t){return t.environment.service}).getOrElse("Unknown")},enumerable:!0,configurable:!0}),Object.defineProperty(e.prototype,"environmentTags",{get:function(){return this.settings.map(function(t){return t.environment.tags}).getOrElse({})},enumerable:!0,configurable:!0}),e.prototype.mounted=function(){this.refreshData()},e.prototype.refreshData=function(){var t=this;D.settings().then(function(e){t.settings=Object(m["some"])(e)}),D.metricRegistryStatus().then(function(e){t.metricsRegistry=Object(m["some"])(e)}),D.moduleRegistryStatus().then(function(e){t.moduleRegistry=Object(m["some"])(e)}),D.instrumentationStatus().then(function(e){t.instrumentation=Object(m["some"])(e)})},e.prototype.isReporter=function(t){return[U.Combined,U.Span,U.Metric].indexOf(t.kind)>0},e.prototype.isStarted=function(t){return t.isStarted},e=d["a"]([Object(v["a"])({components:{card:O,"module-status":P,"metric-list":A}})],e),e}(v["c"]),H=L,I=H,q=Object(i["a"])(I,p,f,!1,null,null,null);q.options.__file="Overview.vue";var z=q.exports;r["default"].use(l["a"]);var B=new l["a"]({routes:[{path:"/",name:"overview",component:z}]});n("ab8b"),n("fb98");r["default"].config.productionTip=!1,new r["default"]({router:B,render:function(t){return t(u)}}).$mount("#app")},d8a1:function(t,e,n){},fb98:function(t,e,n){}}); \ No newline at end of file diff --git a/kamon-core/src/main/scala/kamon/Kamon.scala b/kamon-core/src/main/scala/kamon/Kamon.scala index 50c3e69d..284c7553 100644 --- a/kamon-core/src/main/scala/kamon/Kamon.scala +++ b/kamon-core/src/main/scala/kamon/Kamon.scala @@ -52,6 +52,10 @@ object QuickTest extends App { | class = kamon.MyCustomMetricDude | } |} + | + |kamon.environment.tags { + | one = test + |} """.stripMargin val newConfig = ConfigFactory.parseString(manualConfig).withFallback(Kamon.config()) diff --git a/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala index 5a3f22dc..370ab467 100644 --- a/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala +++ b/kamon-core/src/main/scala/kamon/status/JsonMarshalling.scala @@ -6,6 +6,8 @@ import java.lang.{StringBuilder => JavaStringBuilder} import com.typesafe.config.ConfigRenderOptions import kamon.module.Module +import scala.collection.JavaConverters.{iterableAsScalaIterableConverter, mapAsScalaMapConverter} + trait JsonMarshalling[T] { @@ -96,4 +98,33 @@ object JsonMarshalling { .done() } } + + implicit object InstrumentationStatusJsonMarshalling extends JsonMarshalling[Status.Instrumentation] { + override def toJson(instance: Status.Instrumentation, builder: JavaStringBuilder): Unit = { + val instrumentationObject = JsonWriter.on(builder) + .`object`() + .value("isActive", instance.isIActive) + .`object`("modules") + + instance.modules.asScala.foreach { + case (moduleName, moduleDescription) => instrumentationObject.value(moduleName, moduleDescription) + } + + instrumentationObject + .end() // end modules + .`object`("errors") + + instance.errors.asScala.foreach { + case (moduleName, errors) => + instrumentationObject.array(moduleName) + errors.asScala.foreach(t => instrumentationObject.value(t.toString)) + instrumentationObject.end() + } + + instrumentationObject + .end() // errors + .end() // object + .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 index dc059277..2a52b95f 100644 --- a/kamon-core/src/main/scala/kamon/status/Status.scala +++ b/kamon-core/src/main/scala/kamon/status/Status.scala @@ -6,23 +6,50 @@ import kamon.metric.MetricRegistry import kamon.{Configuration, Environment, Kamon} import kamon.module.ModuleRegistry import kamon.module.Module.{Kind => ModuleKind} +import java.util.{Collections, Map => JavaMap, List => JavaList} /** - * Allows accessing of component's status APIs without exposing any other internal API from those components. + * Exposes Kamon components' status information. This is meant to be used for informational and debugging purposes. */ class Status(_moduleRegistry: ModuleRegistry, _metricRegistry: MetricRegistry, configuration: Configuration) { + /** + * Settings currently used by Kamon. + */ def settings(): Status.Settings = Status.Settings(BuildInfo.version, Kamon.environment, configuration.config()) /** - * Information about what modules have been detected in the classpath and their current status. + * Status of the module registry. Describes what modules have been detected in the classpath and their current + * statuses. */ def moduleRegistry(): Status.ModuleRegistry = _moduleRegistry.status() + /** + * Status of the metric registry. Describes all metrics currently tracked by Kamon. + */ def metricRegistry(): Status.MetricRegistry = _metricRegistry.status() + + + /** + * PRIVATE API. + * + * Status of instrumentation modules that have been detected and/or loaded into the current JVM. This + * API is not meant to be used by the general public. + * + * Read the [[Status.Instrumentation]] companion object's docs for more information. + */ + private[kamon] def instrumentation(): Status.Instrumentation = { + import Status.Instrumentation._ + + Status.Instrumentation( + isActive(), + modules(), + errors() + ) + } } @@ -57,4 +84,63 @@ object Status { tags: Map[String, String], instrumentType: InstrumentType ) + + + /** + * Status of the instrumentation modules. This data is completely untyped and not expected to be used anywhere + * outside Kamon. + */ + private[kamon] case class Instrumentation( + isIActive: Boolean, + modules: JavaMap[String, String], + errors: JavaMap[String, JavaList[Throwable]] + ) + + + /** + * This object works as a bridge between Kamon and Kanela to gather information about instrumentation modules. When + * instrumentation is enabled, it should replace the implementation of the members of this object and return proper + * information. + * + * This data is only exposed directly to the status page API because it lacks any sort of type safety. We might + * change this in the future and provide proper types for all instrumentation modules' info. + */ + private[kamon] object Instrumentation { + + /** + * Whether instrumentation is active or not. When Kanela is present it will replace this method to return true. + */ + def isActive(): Boolean = + false + + /** + * List all instrumentation modules known and their current status. The result map contains the module name as keys + * and a JSON representation of the module status as values. The expected structure in the JSON representations is + * as follows: + * + * { + * 'description': 'A explicative module description', + * 'isEnabled': true | false, + * 'isActive': true | false + * } + * + * The "isEnabled" flag tells whether the module is able to instrument classes or not. By default, all modules are + * able to instrument classes but some modules might be shipped in a disabled state or forced to be disabled via + * configuration. + * + * The "isActive" flag tells whether the modules has already applied instrumentation to any of its target classes. + * + */ + def modules(): JavaMap[String, String] = + Collections.emptyMap() + + + /** + * List all errors that might have happened during the instrumentation initialization. The resulting map contains + * a list of modules and any exceptions thrown by them during initialization. If not exceptions are thrown the map + * will always be empty. + */ + def errors(): JavaMap[String, JavaList[Throwable]] = + Collections.emptyMap() + } } diff --git a/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala b/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala index 35273f39..2784b87a 100644 --- a/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala +++ b/kamon-core/src/main/scala/kamon/status/StatusPageServer.scala @@ -17,10 +17,11 @@ class StatusPageServer(hostname: String, port: Int, resourceLoader: ClassLoader, // Serve the current status data on Json. session.getUri() match { - case "/status/settings" => json(status.settings()) - case "/status/modules" => json(status.moduleRegistry()) - case "/status/metrics" => json(status.metricRegistry()) - case _ => NotFound + case "/status/settings" => json(status.settings()) + case "/status/modules" => json(status.moduleRegistry()) + case "/status/metrics" => json(status.metricRegistry()) + case "/status/instrumentation" => json(status.instrumentation()) + case _ => NotFound } } else { @@ -43,6 +44,7 @@ class StatusPageServer(hostname: String, port: Int, resourceLoader: ClassLoader, case "css" => "text/css" case "js" => "application/javascript" case "ico" => "image/x-icon" + case "svg" => "image/svg+xml" case "html" => "text/html" case _ => "text/plain" } diff --git a/kamon-status/src/api/StatusApi.ts b/kamon-status/src/api/StatusApi.ts index d00761e5..25d525d2 100644 --- a/kamon-status/src/api/StatusApi.ts +++ b/kamon-status/src/api/StatusApi.ts @@ -44,6 +44,18 @@ export interface MetricRegistry { metrics: Metric[] } +export interface InstrumentationModule { + description: string + isEnabled: boolean + isActive: boolean +} + +export interface Instrumentation { + isActive: boolean + modules: { [key: string]: InstrumentationModule } + errors: { [key: string]: string[]} +} + export class StatusApi { @@ -91,4 +103,26 @@ export class StatusApi { return metricRegistry }) } + + public static instrumentationStatus(): Promise { + return axios.get('/status/instrumentation').then(response => { + const instrumentation: Instrumentation = { + isActive: response.data.isActive as boolean, + modules: {}, + errors: {} + } + + const rawModules = response.data.modules + Object.keys(rawModules).forEach(key => { + instrumentation.modules[key] = JSON.parse(rawModules[key]) + }) + + const rawErrors = response.data.errors + Object.keys(rawErrors).forEach(key => { + instrumentation.errors[key] = JSON.parse(rawErrors[key]) + }) + + return instrumentation + }) + } } diff --git a/kamon-status/src/components/MetricList.vue b/kamon-status/src/components/MetricList.vue index 758252e7..b9b6a92a 100644 --- a/kamon-status/src/components/MetricList.vue +++ b/kamon-status/src/components/MetricList.vue @@ -12,7 +12,7 @@
{{ metric.type }}
-

{{ metric.name }}

+
{{ metric.name }}
{{ tag }}={{ metric.tags[tag] }} @@ -75,7 +75,7 @@ export default class MetricList extends Vue { height: 2.5rem; border: none; border-radius: 0.3rem; - background-color: #e8e8e8; + background-color: #efefef; &:focus { outline: none; @@ -93,20 +93,4 @@ export default class MetricList extends Vue { padding-right: 1rem; } } - -.tag-container { - margin: 0rem -0.3rem; -} - -.tag { - background-color: #f4f4f4; - margin: 0.3rem; - padding: 0.1rem 0.5rem; - border-radius: 0.2rem; -} - -.tag-value { - color: #676767; -} - diff --git a/kamon-status/src/components/ModuleStatus.vue b/kamon-status/src/components/ModuleStatus.vue index a92cf46f..fff3373e 100644 --- a/kamon-status/src/components/ModuleStatus.vue +++ b/kamon-status/src/components/ModuleStatus.vue @@ -8,8 +8,8 @@
-
-

{{ moduleStatus.name }}

+
+
{{ moduleStatus.name }}
{{ moduleStatus.description }}
diff --git a/kamon-status/src/styles/main.scss b/kamon-status/src/styles/main.scss index cde62b37..271da8af 100644 --- a/kamon-status/src/styles/main.scss +++ b/kamon-status/src/styles/main.scss @@ -13,4 +13,22 @@ h1, h2, h3 { .text-label { color: #b3b3b3; -} \ No newline at end of file +} + +.tag-container { + margin: 0rem -0.3rem; +} + +.tag { + display: inline-block; + overflow-wrap: anywhere; + background-color: #f4f4f4; + margin: 0.3rem; + padding: 0.1rem 0.5rem; + border-radius: 0.2rem; +} + +.tag-value { + overflow-wrap: anywhere; + color: #676767; +} diff --git a/kamon-status/src/views/Overview.vue b/kamon-status/src/views/Overview.vue index 2d6e8dd7..75b39eb0 100644 --- a/kamon-status/src/views/Overview.vue +++ b/kamon-status/src/views/Overview.vue @@ -8,21 +8,54 @@
-
Instrumentation
-
Active
+
Instrumentation
+
{{instrumentationStatusMessage}}
-
Reporters
+
Reporters
{{ activeReporters.length }} Started
-
Metrics
+
Metrics
{{metricsStatusMessage}}
+
+

Environment

+
+
+ +
+
+
Service
+
{{ service }}
+
+
+
Host
+
{{ host }}
+
+
+
instance
+
{{instance}}
+
+
+
tags
+
+ + {{ tag }}={{ environmentTags[tag] }} + +
+
+
None
+
+
+
+
+
+

Reporters

@@ -53,7 +86,7 @@ import {Option, none, some} from 'ts-option' import ModuleStatus from '../components/ModuleStatus.vue' import Card from '../components/Card.vue' import MetricList from '../components/MetricList.vue' -import {StatusApi, Settings, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric} from '../api/StatusApi' +import {StatusApi, Settings, ModuleRegistry, ModuleKind, MetricRegistry, Module, Metric, Instrumentation} from '../api/StatusApi' @Component({ components: { @@ -66,6 +99,7 @@ export default class Overview extends Vue { private settings: Option = none private moduleRegistry: Option = none private metricsRegistry: Option = none + private instrumentation: Option = none get reporterModules(): Module[] { return this.moduleRegistry @@ -87,6 +121,10 @@ export default class Overview extends Vue { return this.metricsRegistry.map(metricRegistry => metricRegistry.metrics.length) } + get instrumentationStatusMessage(): string { + return this.instrumentation.map(i => (i.isActive ? 'Active' : 'Disabled') as string).getOrElse('Unknown') + } + get metricsStatusMessage(): string { return this.trackedMetrics.map(mc => mc + ' Tracked').getOrElse('Unknown') } @@ -97,6 +135,23 @@ export default class Overview extends Vue { .getOrElse([]) } + get instance(): string { + return this.settings.map(s => s.environment.instance).getOrElse('Unknown') + } + + get host(): string { + return this.settings.map(s => s.environment.host).getOrElse('Unknown') + } + + get service(): string { + return this.settings.map(s => s.environment.service).getOrElse('Unknown') + } + + get environmentTags(): { [key: string]: string } { + return this.settings.map(s => s.environment.tags).getOrElse({}) + } + + public mounted() { this.refreshData() @@ -106,11 +161,12 @@ export default class Overview extends Vue { StatusApi.settings().then(settings => { this.settings = some(settings) }) StatusApi.metricRegistryStatus().then(metricsRegistry => { this.metricsRegistry = some(metricsRegistry) }) StatusApi.moduleRegistryStatus().then(moduleRegistry => {this.moduleRegistry = some(moduleRegistry) }) + StatusApi.instrumentationStatus().then(instrumentation => {this.instrumentation = some(instrumentation) }) } private isReporter(module: Module): boolean { - return [ModuleKind.Combined, ModuleKind.Span, ModuleKind.Metric].indexOf(module.kind) > 0 - } + return [ModuleKind.Combined, ModuleKind.Span, ModuleKind.Metric].indexOf(module.kind) > 0 + } private isStarted(module: Module): boolean { return module.isStarted -- cgit v1.2.3