Skip to content

Commit

Permalink
implement a kamon.enabled setting (#1243)
Browse files Browse the repository at this point in the history
* implement a kamon.enabled setting

* show the Kanela version and banner during init
  • Loading branch information
ivantopo committed Jan 24, 2023
1 parent 97385be commit 841a077
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 52 deletions.
17 changes: 17 additions & 0 deletions core/kamon-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@

kamon {

# !!! EXPERIMENTAL !!!
#
# Sets whether Kamon should initialize instrumentation, modules, and reporters when calling Kamon.init() (and its
# variants) or not. This setting is not meant to be changed during application runtime, as it might not be able to
# completely add or remove instrumentation from already-loaded classes. If you need to enable/disable Kamon, restart
# your application with a new value for this setting.
#
# Marked as experimental because its behavior might change as we find corner cases and change the exact scope of
# what it does, but the basics of not attaching the instrumentation, reporters, and modules is functional.
enabled = yes

init {

# Hides the Kamon banner shown during Kamon's init process
hide-banner = no
}

environment {

# Identifier for this service.
Expand Down
8 changes: 8 additions & 0 deletions core/kamon-core/src/main/scala/kamon/Configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package kamon

import com.typesafe.config.{Config, ConfigFactory}
import kamon.Configuration.EnabledConfigurationName
import org.slf4j.LoggerFactory

import scala.util.control.NonFatal
Expand All @@ -28,6 +29,7 @@ trait Configuration {
private val _logger = LoggerFactory.getLogger(classOf[Configuration])
private var _currentConfig: Config = loadInitialConfiguration()
private var _onReconfigureHooks = Seq.empty[Configuration.OnReconfigureHook]
@volatile private var _enabled: Boolean = _currentConfig.getBoolean(EnabledConfigurationName)


/**
Expand All @@ -36,11 +38,15 @@ trait Configuration {
def config(): Config =
_currentConfig

def enabled(): Boolean =
_enabled

/**
* Supply a new Config instance to rule Kamon's world.
*/
def reconfigure(newConfig: Config): Unit = synchronized {
_currentConfig = newConfig
_enabled = newConfig.getBoolean(EnabledConfigurationName)
_onReconfigureHooks.foreach(hook => {
try {
hook.onReconfigure(newConfig)
Expand Down Expand Up @@ -97,6 +103,8 @@ trait Configuration {

object Configuration {

private val EnabledConfigurationName = "kamon.enabled"

trait OnReconfigureHook {
def onReconfigure(newConfig: Config): Unit
}
Expand Down
114 changes: 100 additions & 14 deletions core/kamon-core/src/main/scala/kamon/Init.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
package kamon

import com.typesafe.config.Config
import kamon.status.InstrumentationStatus
import kamon.module.Module
import kamon.status.{BuildInfo, InstrumentationStatus}
import org.slf4j.LoggerFactory

import java.util.concurrent.{ScheduledExecutorService, ScheduledThreadPoolExecutor}
Expand All @@ -37,40 +38,66 @@ trait Init { self: ModuleManagement with Configuration with CurrentStatus with M
* Attempts to attach the instrumentation agent and start all registered modules.
*/
def init(): Unit = {
self.attachInstrumentation()
self.initScheduler()
self.loadModules()
self.moduleRegistry().init()
if(enabled()) {
self.attachInstrumentation()
self.initScheduler()
self.loadModules()
self.moduleRegistry().init()
} else {
self.disableInstrumentation()
}

self.logInitStatusInfo()
}

/**
* Reconfigures Kamon to use the provided configuration and then attempts to attach the instrumentation agent and
* start all registered modules.
*/
def init(config: Config): Unit = {
self.attachInstrumentation()
self.initScheduler()
self.reconfigure(config)
self.loadModules()
self.moduleRegistry().init()

if(enabled()) {
self.attachInstrumentation()
self.initScheduler()
self.loadModules()
self.moduleRegistry().init()
} else {
self.disableInstrumentation()
}

self.logInitStatusInfo()

}

/**
* Initializes Kamon without trying to attach the instrumentation agent from the Kamon Bundle.
*/
def initWithoutAttaching(): Unit = {
self.initScheduler()
self.loadModules()
self.moduleRegistry().init()
if(enabled()) {
self.initScheduler()
self.loadModules()
self.moduleRegistry().init()
} else {
self.disableInstrumentation()
}

self.logInitStatusInfo()
}

/**
* Initializes Kamon without trying to attach the instrumentation agent from the Kamon Bundle.
*/
def initWithoutAttaching(config: Config): Unit = {
self.reconfigure(config)
self.initWithoutAttaching()

if(enabled()) {
self.initWithoutAttaching()
} else {
self.disableInstrumentation()
}

self.logInitStatusInfo()
}


Expand All @@ -79,7 +106,6 @@ trait Init { self: ModuleManagement with Configuration with CurrentStatus with M
self.stopScheduler()
self.moduleRegistry().shutdown()
self.stopModules()

}

/**
Expand All @@ -106,6 +132,66 @@ trait Init { self: ModuleManagement with Configuration with CurrentStatus with M
}
}

private def logInitStatusInfo(): Unit = {
def bold(text: String) = s"\u001b[1m${text}\u001b[0m"
def red(text: String) = bold(s"\u001b[31m${text}\u001b[0m")
def green(text: String) = bold(s"\u001b[32m${text}\u001b[0m")

val isEnabled = enabled()
val showBanner = !config().getBoolean("kamon.init.hide-banner")

if(isEnabled) {
val instrumentationStatus = status().instrumentation()
val kanelaVersion = instrumentationStatus.kanelaVersion
.map(v => green("v" + v))
.getOrElse(red("not found"))

if (showBanner) {
_logger.info(
s"""
| _
|| |
|| | ____ _ _ __ ___ ___ _ __
|| |/ / _ | _ ` _ \\ / _ \\| _ \\
|| < (_| | | | | | | (_) | | | |
||_|\\_\\__,_|_| |_| |_|\\___/|_| |_|
|=====================================
|Initializing Kamon Telemetry ${green("v" + BuildInfo.version)} / Kanela ${kanelaVersion}
|""".stripMargin
)
} else
_logger.info(s"Initializing Kamon Telemetry v${BuildInfo.version} / Kanela ${kanelaVersion}")
} else {
_logger.warn(s"Kamon is ${red("DISABLED")}. No instrumentation, reporters, or context propagation will be applied on this " +
"process. Restart the process with kamon.enabled=yes to restore Kamon's functionality")
}

}

/**
* Tries to disable the Kanela agent, in case it was attached via the -javaagent:... option. The agent is always
* attached to the System Classloader so we try to find it there and call "disable" on it.
*/
private def disableInstrumentation(): Unit = {
try {
Class.forName("kanela.agent.Kanela", true, ClassLoader.getSystemClassLoader)
.getDeclaredMethod("disable")
.invoke(null)

_logger.info("Disabled the Kanela instrumentation agent. Classes will not be instrumented in this process")

} catch {
case _: ClassNotFoundException =>
// Do nothing. This means that Kanela wasn't loaded so there was no need to do anything.

case _: NoSuchMethodException =>
_logger.error("Failed to disable the Kanela instrumentation agent. Please ensure you are using Kanela >=1.0.17")

case t: Throwable =>
_logger.error("Failed to disable the Kanela instrumentation agent", t)
}
}

private def initScheduler(): Unit = synchronized {
val newScheduler = newScheduledThreadPool(2, numberedThreadFactory("kamon-scheduler", daemon = true))
self.tracer().bindScheduler(newScheduler)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ object InstrumentationStatus {
// then Kanela must be present.
val present = (registryClass != null) && kanelaLoaded

val kanelaVersion = Class.forName("kanela.agent.util.BuildInfo", false, ClassLoader.getSystemClassLoader)
.getMethod("version")
.invoke(null)
.asInstanceOf[String]

val modules = registryClass.getMethod("shareModules")
.invoke(null)
.asInstanceOf[JavaList[JavaMap[String, String]]]
Expand All @@ -66,7 +71,7 @@ object InstrumentationStatus {
.map(toTypeError)
.toSeq

Status.Instrumentation(present, modules.toSeq, errors)
Status.Instrumentation(present, Option(kanelaVersion), modules.toSeq, errors)
} catch {
case t: Throwable =>

Expand All @@ -80,7 +85,7 @@ object InstrumentationStatus {
}
}

Status.Instrumentation(false, Seq.empty, Seq.empty)
Status.Instrumentation(false, None, Seq.empty, Seq.empty)
}
}

Expand Down
1 change: 1 addition & 0 deletions core/kamon-core/src/main/scala/kamon/status/Status.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ object Status {
*/
case class Instrumentation (
present: Boolean,
kanelaVersion: Option[String],
modules: Seq[Status.Instrumentation.ModuleInfo],
errors: Seq[Status.Instrumentation.TypeError]
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,34 @@ class AwsSdkClientExecutionInterceptor extends ExecutionInterceptor {
import AwsSdkClientExecutionInterceptor.ClientSpanAttribute

override def afterMarshalling(context: Context.AfterMarshalling, executionAttributes: ExecutionAttributes): Unit = {
val operationName = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME)
val serviceName = executionAttributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME)
val clientType = executionAttributes.getAttribute(SdkExecutionAttribute.CLIENT_TYPE)
if(Kamon.enabled()) {
val operationName = executionAttributes.getAttribute(SdkExecutionAttribute.OPERATION_NAME)
val serviceName = executionAttributes.getAttribute(SdkExecutionAttribute.SERVICE_NAME)
val clientType = executionAttributes.getAttribute(SdkExecutionAttribute.CLIENT_TYPE)

val clientSpan = Kamon.clientSpanBuilder(operationName, serviceName)
.tag("aws.sdk.client_type", clientType.name())
.start()
val clientSpan = Kamon.clientSpanBuilder(operationName, serviceName)
.tag("aws.sdk.client_type", clientType.name())
.start()

executionAttributes.putAttribute(ClientSpanAttribute, clientSpan)
executionAttributes.putAttribute(ClientSpanAttribute, clientSpan)
}
}

override def afterExecution(context: Context.AfterExecution, executionAttributes: ExecutionAttributes): Unit = {
val kamonSpan = executionAttributes.getAttribute(ClientSpanAttribute)
if(kamonSpan != null) {
kamonSpan.finish()
if(Kamon.enabled()) {
val kamonSpan = executionAttributes.getAttribute(ClientSpanAttribute)
if(kamonSpan != null) {
kamonSpan.finish()
}
}
}

override def onExecutionFailure(context: Context.FailedExecution, executionAttributes: ExecutionAttributes): Unit = {
val kamonSpan = executionAttributes.getAttribute(ClientSpanAttribute)
if(kamonSpan != null) {
kamonSpan.fail(context.exception()).finish()
if(Kamon.enabled()) {
val kamonSpan = executionAttributes.getAttribute(ClientSpanAttribute)
if (kamonSpan != null) {
kamonSpan.fail(context.exception()).finish()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,38 +32,44 @@ class AwsSdkRequestHandler extends RequestHandler2 {
import AwsSdkRequestHandler.SpanContextKey

override def beforeRequest(request: Request[_]): Unit = {
val serviceName = request.getServiceName
val originalRequestName = {
// Remove the "Request" part of the request class name, if present
var requestClassName = request.getOriginalRequest.getClass.getSimpleName
if(requestClassName.endsWith("Request"))
requestClassName = requestClassName.substring(0, requestClassName.length - 7)
requestClassName
}
if(Kamon.enabled()) {
val serviceName = request.getServiceName
val originalRequestName = {
// Remove the "Request" part of the request class name, if present
var requestClassName = request.getOriginalRequest.getClass.getSimpleName
if (requestClassName.endsWith("Request"))
requestClassName = requestClassName.substring(0, requestClassName.length - 7)
requestClassName
}

val operationName = serviceName + "." + originalRequestName
val operationName = serviceName + "." + originalRequestName

val clientSpan = serviceName match {
case "AmazonSQS" => Kamon.producerSpanBuilder(operationName, serviceName).start()
case _ => Kamon.clientSpanBuilder(operationName, serviceName).start()
}
val clientSpan = serviceName match {
case "AmazonSQS" => Kamon.producerSpanBuilder(operationName, serviceName).start()
case _ => Kamon.clientSpanBuilder(operationName, serviceName).start()
}

request.addHandlerContext(SpanContextKey, clientSpan)
request.addHandlerContext(SpanContextKey, clientSpan)
}
}

override def afterResponse(request: Request[_], response: Response[_]): Unit = {
val requestSpan = request.getHandlerContext(SpanContextKey)
if(requestSpan != null) {
requestSpan.finish()
if(Kamon.enabled()) {
val requestSpan = request.getHandlerContext(SpanContextKey)
if (requestSpan != null) {
requestSpan.finish()
}
}
}

override def afterError(request: Request[_], response: Response[_], e: Exception): Unit = {
val requestSpan = request.getHandlerContext(SpanContextKey)
if(requestSpan != null) {
requestSpan
.fail(e)
.finish()
if(Kamon.enabled()) {
val requestSpan = request.getHandlerContext(SpanContextKey)
if (requestSpan != null) {
requestSpan
.fail(e)
.finish()
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion project/Build.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object BaseProject extends AutoPlugin {
/** Marker configuration for dependencies that will be shaded into their module's jar. */
lazy val Shaded = config("shaded").hide

val kanelaAgent = "io.kamon" % "kanela-agent" % "1.0.16"
val kanelaAgent = "io.kamon" % "kanela-agent" % "1.0.17"
val slf4jApi = "org.slf4j" % "slf4j-api" % "1.7.25"
val slf4jnop = "org.slf4j" % "slf4j-nop" % "1.7.24"
val logbackClassic = "ch.qos.logback" % "logback-classic" % "1.2.3"
Expand Down

0 comments on commit 841a077

Please sign in to comment.