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

kamon.zipkin.ZipkinReporter.scala Maven / Gradle / Ivy

There is a newer version: 2.7.4
Show newest version
/*
 * Copyright 2013-2020 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.zipkin

import java.net.InetAddress

import com.typesafe.config.Config
import kamon.Kamon
import kamon.module.{Module, ModuleFactory, SpanReporter}
import kamon.tag.TagSet
import kamon.tag.Lookups.{longOption, option}
import kamon.trace.Span
import kamon.trace.Span.Mark
import kamon.util.Clock
import org.slf4j.LoggerFactory
import zipkin2.{Endpoint, Span => ZipkinSpan}
import zipkin2.reporter.okhttp3.OkHttpSender
import zipkin2.reporter.AsyncReporter

import scala.util.Try

class ZipkinReporter(configPath: String) extends SpanReporter {
  import ZipkinReporter._

  private val _logger = LoggerFactory.getLogger(classOf[ZipkinReporter])
  private var _localEndpoint = buildEndpoint()
  private var _reporter = buildReporter(Kamon.config())

  checkJoinParameter()
  _logger.info("Started the Zipkin reporter")

  def this() =
    this("kamon.zipkin")

  def checkJoinParameter() = {
    val joinRemoteParentsWithSameID = Kamon.config().getBoolean("kamon.trace.join-remote-parents-with-same-span-id")
    if (!joinRemoteParentsWithSameID)
      _logger.warn("For full Zipkin compatibility enable `kamon.trace.join-remote-parents-with-same-span-id` to " +
        "preserve span id across client/server sides of a Span.")
  }

  override def reportSpans(spans: Seq[Span.Finished]): Unit =
    spans.map(convertSpan).foreach(_reporter.report)

  private[zipkin] def convertSpan(kamonSpan: Span.Finished): ZipkinSpan = {
    val duration = Math.floorDiv(Clock.nanosBetween(kamonSpan.from, kamonSpan.to), 1000L)
    // Zipkin uses null to identify no identifier
    val parentId: String = if (kamonSpan.parentId.isEmpty) null else kamonSpan.parentId.string
    val builder = ZipkinSpan.newBuilder()
      .localEndpoint(_localEndpoint)
      .traceId(kamonSpan.trace.id.string)
      .id(kamonSpan.id.string)
      .parentId(parentId)
      .name(kamonSpan.operationName)
      .timestamp(Clock.toEpochMicros(kamonSpan.from))
      .duration(duration)

    val kind = spanKind(kamonSpan)
    builder.kind(kind)

    if (kind == ZipkinSpan.Kind.CLIENT) {
      val remoteEndpoint = Endpoint.newBuilder()
        .ip(getStringTag(kamonSpan, PeerKeys.IPv4))
        .ip(getStringTag(kamonSpan, PeerKeys.IPv6))
        .port(getLongTag(kamonSpan, PeerKeys.Port).toInt)
        .build()

      if (hasAnyData(remoteEndpoint))
        builder.remoteEndpoint(remoteEndpoint)
    }

    kamonSpan.marks.foreach {
      case Mark(instant, key) => builder.addAnnotation(Clock.toEpochMicros(instant), key)
    }

    addTags(kamonSpan.tags, builder)
    addTags(kamonSpan.metricTags, builder)

    builder.build()
  }

  private def spanKind(span: Span.Finished): ZipkinSpan.Kind = span.kind match {
    case Span.Kind.Client   => ZipkinSpan.Kind.CLIENT
    case Span.Kind.Server   => ZipkinSpan.Kind.SERVER
    case Span.Kind.Producer => ZipkinSpan.Kind.PRODUCER
    case Span.Kind.Consumer => ZipkinSpan.Kind.CONSUMER
    case _                  => null
  }

  private def addTags(tags: TagSet, builder: ZipkinSpan.Builder): Unit =
    tags.iterator(_.toString)
      .filterNot(pair =>
        pair.key == Span.TagKeys.Error && pair.value == "false"
      ) // zipkin considers any error tag as failed request
      .foreach(pair => builder.putTag(pair.key, pair.value))

  private def getStringTag(span: Span.Finished, tagName: String): String =
    span.tags.get(option(tagName)).orElse(span.metricTags.get(option(tagName))).orNull

  private def getLongTag(span: Span.Finished, tagName: String): Long =
    span.tags.get(longOption(tagName)).orElse(span.metricTags.get(longOption(tagName))).getOrElse(0L)

  private def hasAnyData(endpoint: Endpoint): Boolean =
    endpoint.ipv4() != null || endpoint.ipv6() != null || endpoint.port() != null || endpoint.serviceName() != null

  override def reconfigure(newConfig: Config): Unit = {
    _localEndpoint = buildEndpoint()
    _reporter = buildReporter(newConfig)
    checkJoinParameter()
  }

  private def buildEndpoint(): Endpoint = {
    val env = Kamon.environment
    val localAddress = Try(InetAddress.getByName(env.host))
      .getOrElse(InetAddress.getLocalHost)

    Endpoint.newBuilder()
      .ip(localAddress)
      .serviceName(env.service)
      .build()
  }

  private def buildReporter(config: Config) = {
    val zipkinConfig = config.getConfig(configPath)
    val fullServerUrl = zipkinConfig.getString("url")

    AsyncReporter.create(OkHttpSender.create(fullServerUrl))
  }

  override def stop(): Unit =
    _logger.info("Stopped the Zipkin reporter")
}

object ZipkinReporter {

  class Factory extends ModuleFactory {
    override def create(settings: ModuleFactory.Settings): Module =
      new ZipkinReporter()
  }

  private object PeerKeys {
    val Host = "peer.host"
    val Port = "peer.port"
    val IPv4 = "peer.ipv4"
    val IPv6 = "peer.ipv6"
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy