All Downloads are FREE. Search and download functionalities are using the official Maven repository.

kamon.instrumentation.logback.LogbackInstrumentation.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013-2021 The Kamon Project 
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kamon.instrumentation.logback

import com.typesafe.config.Config
import kamon.Kamon
import kamon.context.Context
import kamon.context.Storage.Scope
import kamon.instrumentation.context.HasContext
import kamon.tag.Tag
import kamon.trace.{Identifier, Span}
import kanela.agent.api.instrumentation.InstrumentationBuilder
import kanela.agent.libs.net.bytebuddy.asm.Advice
import kanela.agent.libs.net.bytebuddy.implementation.bind.annotation.{RuntimeType, SuperCall}
import org.slf4j.MDC

import java.util.concurrent.Callable
import scala.annotation.static
import scala.collection.JavaConverters._

class LogbackInstrumentation extends InstrumentationBuilder {

  onSubTypesOf("ch.qos.logback.core.spi.DeferredProcessingAware")
    .mixin(classOf[HasContext.MixinWithInitializer])

  onType("ch.qos.logback.core.spi.AppenderAttachableImpl")
    .advise(method("appendLoopOnAppenders"), classOf[AppendLoopOnAppendersAdvice])

  onType("ch.qos.logback.classic.spi.LoggingEvent")
    .intercept(method("getMDCPropertyMap"), classOf[GetPropertyMapMethodInterceptor])
}

object LogbackInstrumentation {

  @volatile private var _settings = readSettings(Kamon.config())
  Kamon.onReconfigure(newConfig => _settings = readSettings(newConfig))

  def settings(): Settings =
    _settings

  case class Settings(
    propagateContextToMDC: Boolean,
    mdcTraceIdKey: String,
    mdcSpanIdKey: String,
    mdcSpanOperationNameKey: String,
    mdcSourceThreadKey: String,
    mdcCopyTags: Boolean,
    mdcCopyKeys: Seq[String]
  )

  private def readSettings(config: Config): Settings = {
    val logbackConfig = config.getConfig("kamon.instrumentation.logback")

    Settings(
      logbackConfig.getBoolean("mdc.copy.enabled"),
      logbackConfig.getString("mdc.trace-id-key"),
      logbackConfig.getString("mdc.span-id-key"),
      logbackConfig.getString("mdc.span-operation-name-key"),
      logbackConfig.getString("mdc.source-thread-key"),
      logbackConfig.getBoolean("mdc.copy.tags"),
      logbackConfig.getStringList("mdc.copy.entries").asScala.toSeq
    )
  }
}

class AppendLoopOnAppendersAdvice
object AppendLoopOnAppendersAdvice {

  @Advice.OnMethodEnter
  @static def enter(@Advice.Argument(0) event: Any): Scope =
    Kamon.storeContext(event.asInstanceOf[HasContext].context)

  @Advice.OnMethodExit
  @static def exit(@Advice.Enter scope: Scope): Unit =
    scope.close()
}

class GetPropertyMapMethodInterceptor
object GetPropertyMapMethodInterceptor {

  @RuntimeType
  @static def aroundGetMDCPropertyMap(@SuperCall callable: Callable[_]): Any = {
    val settings = LogbackInstrumentation.settings()

    if (settings.propagateContextToMDC) {
      val mdcContextMapBeforePropagation = MDC.getCopyOfContextMap
      val currentContext = Kamon.currentContext()
      val span = currentContext.get(Span.Key)

      if (span.trace.id != Identifier.Empty) {
        MDC.put(settings.mdcTraceIdKey, span.trace.id.string)
        MDC.put(settings.mdcSpanIdKey, span.id.string)
        MDC.put(settings.mdcSpanOperationNameKey, span.operationName())
      }

      if (settings.mdcCopyTags) {
        currentContext.tags.iterator().foreach(t => {
          MDC.put(t.key, Tag.unwrapValue(t).toString)
        })
      }

      settings.mdcCopyKeys.foreach { key =>
        currentContext.get(Context.key[Any](key, "")) match {
          case Some(value) if value.toString.nonEmpty                     => MDC.put(key, value.toString)
          case keyValue if keyValue != null && keyValue.toString.nonEmpty => MDC.put(key, keyValue.toString)
          case _                                                          => // Just ignore the nulls and empty strings
        }
      }

      try callable.call()
      finally {
        if (mdcContextMapBeforePropagation != null) {
          MDC.setContextMap(mdcContextMapBeforePropagation)
        } else { // a null contextMap is possible and means 'empty'
          MDC.clear()
        }
      }
    } else {
      callable.call()
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy