Skip to content

Commit 3734b8d

Browse files
authored
Merge pull request #1022 from SimunKaracic/initial-kamon-redis
Initial instrumentation for Jedis
2 parents 9b8ae76 + 988d3a4 commit 3734b8d

File tree

4 files changed

+126
-2
lines changed

4 files changed

+126
-2
lines changed

build.sbt

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,8 @@ lazy val instrumentation = (project in file("instrumentation"))
132132
`kamon-akka-http`,
133133
`kamon-play`,
134134
`kamon-okhttp`,
135-
`kamon-tapir`
135+
`kamon-tapir`,
136+
`kamon-redis`,
136137
)
137138

138139

@@ -495,6 +496,21 @@ lazy val `kamon-tapir` = (project in file("instrumentation/kamon-tapir"))
495496
)
496497
).dependsOn(`kamon-core`, `kamon-akka-http`, `kamon-testkit` % "test")
497498

499+
lazy val `kamon-redis` = (project in file("instrumentation/kamon-redis"))
500+
.disablePlugins(AssemblyPlugin)
501+
.enablePlugins(JavaAgent)
502+
.settings(instrumentationSettings)
503+
.settings(
504+
libraryDependencies ++= Seq(
505+
kanelaAgent % "provided",
506+
"redis.clients" % "jedis" % "3.6.0" % "provided",
507+
508+
scalatest % "test",
509+
logbackClassic % "test",
510+
"org.testcontainers" % "testcontainers" % "1.15.3" % "test",
511+
)
512+
).dependsOn(`kamon-core`, `kamon-testkit` % "test")
513+
498514
/**
499515
* Reporters
500516
*/
@@ -732,5 +748,6 @@ val `kamon-bundle` = (project in file("bundle/kamon-bundle"))
732748
`kamon-akka` % "shaded",
733749
`kamon-akka-http` % "shaded",
734750
`kamon-play` % "shaded",
735-
`kamon-okhttp` % "shaded"
751+
`kamon-redis` % "shaded",
752+
`kamon-okhttp` % "shaded",
736753
)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
kanela.modules {
2+
redis {
3+
name = "Redis Instrumentation"
4+
# Does it provide metrics? No.
5+
description = "Provides tracing and metrics for the Jedis library"
6+
7+
instrumentations = [
8+
"kamon.instrumentation.jedis.JedisInstrumentation",
9+
]
10+
11+
within = [
12+
"redis.clients.jedis..*",
13+
]
14+
}
15+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package kamon.instrumentation.jedis
2+
3+
import kamon.Kamon
4+
import kamon.trace.Span
5+
import kanela.agent.api.instrumentation.InstrumentationBuilder
6+
import kanela.agent.libs.net.bytebuddy.asm.Advice
7+
import redis.clients.jedis.commands.ProtocolCommand
8+
9+
class JedisInstrumentation extends InstrumentationBuilder {
10+
onType("redis.clients.jedis.Protocol")
11+
.advise(method("sendCommand").and(withArgument(1, classOf[ProtocolCommand])), classOf[SendCommandAdvice])
12+
}
13+
14+
class SendCommandAdvice
15+
16+
object SendCommandAdvice {
17+
@Advice.OnMethodEnter()
18+
def enter(@Advice.Argument(1) command: ProtocolCommand) = {
19+
val spanName = s"redis.command.${command}"
20+
val span = Kamon.clientSpanBuilder(spanName, "redis.client.jedis")
21+
.start()
22+
23+
span
24+
}
25+
26+
@Advice.OnMethodExit(onThrowable = classOf[Throwable])
27+
def exit(@Advice.Enter span: Span,
28+
@Advice.Thrown t: Throwable) = {
29+
if (t != null) {
30+
span.fail(t)
31+
}
32+
span.finish()
33+
}
34+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package kamon.instrumentation.jedis
2+
3+
import kamon.testkit.{MetricInspection, TestSpanReporter}
4+
import kamon.trace.Span.Kind
5+
import org.scalatest.concurrent.{Eventually, ScalaFutures}
6+
import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, Matchers, OptionValues, WordSpec}
7+
import org.testcontainers.containers.GenericContainer
8+
import org.testcontainers.utility.DockerImageName
9+
import redis.clients.jedis.Jedis
10+
11+
import scala.concurrent.duration.DurationInt
12+
import scala.util.control.NonFatal
13+
14+
15+
class JedisInstrumentationSpec extends WordSpec
16+
with Matchers
17+
with ScalaFutures
18+
with Eventually
19+
with BeforeAndAfterAll
20+
with MetricInspection.Syntax
21+
with OptionValues
22+
with TestSpanReporter {
23+
24+
var container: GenericContainer[Nothing] = _
25+
26+
override def beforeAll: Unit = {
27+
val REDIS_IMAGE = DockerImageName.parse("redis")
28+
container = new GenericContainer(REDIS_IMAGE).withExposedPorts(6379)
29+
30+
container.start()
31+
}
32+
33+
override def afterAll: Unit = {
34+
container.stop()
35+
}
36+
37+
"the Jedis instrumentation" should {
38+
"generate a client span for get and set commands" in {
39+
val jedis = new Jedis(container.getHost, container.getFirstMappedPort)
40+
jedis.set("foo", "bar")
41+
42+
eventually(timeout(10.seconds)) {
43+
val span = testSpanReporter().nextSpan().get
44+
span.operationName shouldBe "redis.command.SET"
45+
span.kind shouldBe Kind.Client
46+
}
47+
48+
testSpanReporter().clear()
49+
50+
jedis.get("foo")
51+
eventually(timeout(10.seconds)) {
52+
val span = testSpanReporter().nextSpan().get
53+
span.operationName shouldBe "redis.command.GET"
54+
span.kind shouldBe Kind.Client
55+
}
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)