Skip to content

Commit

Permalink
Moved jdbc-datasource instrumentation to Instrumenter API (#3160)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mateusz Rzeszutek authored Jun 3, 2021
1 parent 0ad8c1b commit 5e4c568
Show file tree
Hide file tree
Showing 10 changed files with 323 additions and 45 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.code;

import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
* Extractor of <a
* href="https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/semantic_conventions/span-general.md#source-code-attributes">source
* code attributes</a>.
*/
public abstract class CodeAttributesExtractor<REQUEST, RESPONSE>
extends AttributesExtractor<REQUEST, RESPONSE> {

@Override
protected final void onStart(AttributesBuilder attributes, REQUEST request) {
Class<?> cls = codeClass(request);
if (cls != null) {
set(attributes, SemanticAttributes.CODE_NAMESPACE, cls.getName());
}
set(attributes, SemanticAttributes.CODE_FUNCTION, methodName(request));
set(attributes, SemanticAttributes.CODE_FILEPATH, filePath(request));
set(attributes, SemanticAttributes.CODE_LINENO, lineNumber(request));
}

@Override
protected final void onEnd(
AttributesBuilder attributes, REQUEST request, @Nullable RESPONSE response) {}

@Nullable
protected abstract Class<?> codeClass(REQUEST request);

@Nullable
protected abstract String methodName(REQUEST request);

@Nullable
protected abstract String filePath(REQUEST request);

@Nullable
protected abstract Long lineNumber(REQUEST request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.code;

import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.tracer.SpanNames;

/**
* A helper {@link SpanNameExtractor} implementation for instrumentations that target specific Java
* classes/methods.
*/
public final class CodeSpanNameExtractor<REQUEST> implements SpanNameExtractor<REQUEST> {

/**
* Returns a {@link SpanNameExtractor} that constructs the span name according to the following
* pattern: {@code <class.simpleName>.<methodName>}.
*/
public static <REQUEST> SpanNameExtractor<REQUEST> create(
CodeAttributesExtractor<REQUEST, ?> attributesExtractor) {
return new CodeSpanNameExtractor<>(attributesExtractor);
}

private final CodeAttributesExtractor<REQUEST, ?> attributesExtractor;

private CodeSpanNameExtractor(CodeAttributesExtractor<REQUEST, ?> attributesExtractor) {
this.attributesExtractor = attributesExtractor;
}

@Override
public String extract(REQUEST request) {
Class<?> cls = attributesExtractor.codeClass(request);
// TODO: avoid using SpanNames, encapsulate the logic here
String className = cls != null ? SpanNames.spanNameForClass(cls) : "<unknown>";
String methodName = defaultString(attributesExtractor.methodName(request));
return className + "." + methodName;
}

private static String defaultString(String s) {
return s == null ? "<unknown>" : s;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.code;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.entry;

import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;

class CodeAttributesExtractorTest {

CodeAttributesExtractor<Map<String, String>, Void> underTest =
new CodeAttributesExtractor<Map<String, String>, Void>() {
@Override
protected Class<?> codeClass(Map<String, String> request) {
try {
String className = request.get("class");
return className == null ? null : Class.forName(className);
} catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
}

@Override
protected String methodName(Map<String, String> request) {
return request.get("methodName");
}

@Override
protected String filePath(Map<String, String> request) {
return request.get("filePath");
}

@Override
protected Long lineNumber(Map<String, String> request) {
String lineNo = request.get("lineNo");
return lineNo == null ? null : Long.parseLong(lineNo);
}
};

@Test
void shouldExtractAllAttributes() {
// given
Map<String, String> request = new HashMap<>();
request.put("class", TestClass.class.getName());
request.put("methodName", "doSomething");
request.put("filePath", "/tmp/TestClass.java");
request.put("lineNo", "42");

// when
AttributesBuilder startAttributes = Attributes.builder();
underTest.onStart(startAttributes, request);

AttributesBuilder endAttributes = Attributes.builder();
underTest.onEnd(endAttributes, request, null);

// then
assertThat(startAttributes.build())
.containsOnly(
entry(SemanticAttributes.CODE_NAMESPACE, TestClass.class.getName()),
entry(SemanticAttributes.CODE_FUNCTION, "doSomething"),
entry(SemanticAttributes.CODE_FILEPATH, "/tmp/TestClass.java"),
entry(SemanticAttributes.CODE_LINENO, 42L));

assertThat(endAttributes.build().isEmpty()).isTrue();
}

@Test
void shouldExtractNoAttributesIfNoneAreAvailable() {
// when
AttributesBuilder attributes = Attributes.builder();
underTest.onStart(attributes, Collections.emptyMap());

// then
assertThat(attributes.build().isEmpty()).isTrue();
}

static class TestClass {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.api.instrumenter.code;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.BDDMockito.willReturn;

import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class CodeSpanNameExtractorTest {
@Mock CodeAttributesExtractor<Object, Void> attributesExtractor;

@Test
void shouldExtractFullSpanName() {
// given
Object request = new Object();

willReturn(TestClass.class).given(attributesExtractor).codeClass(request);
willReturn("doSomething").given(attributesExtractor).methodName(request);

SpanNameExtractor<Object> underTest = CodeSpanNameExtractor.create(attributesExtractor);

// when
String spanName = underTest.extract(request);

// then
assertEquals("TestClass.doSomething", spanName);
}

@Test
void shouldExtractFullSpanNameForAnonymousClass() {
// given
AnonymousBaseClass anon = new AnonymousBaseClass() {};
Object request = new Object();

willReturn(anon.getClass()).given(attributesExtractor).codeClass(request);
willReturn("doSomething").given(attributesExtractor).methodName(request);

SpanNameExtractor<Object> underTest = CodeSpanNameExtractor.create(attributesExtractor);

// when
String spanName = underTest.extract(request);

// then
assertEquals(getClass().getSimpleName() + "$1.doSomething", spanName);
}

static class TestClass {}

static class AnonymousBaseClass {}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jdbc.datasource;

import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
import javax.sql.DataSource;
import org.checkerframework.checker.nullness.qual.Nullable;

final class DataSourceCodeAttributesExtractor extends CodeAttributesExtractor<DataSource, Void> {

@Override
protected Class<?> codeClass(DataSource dataSource) {
return dataSource.getClass();
}

@Override
protected String methodName(DataSource dataSource) {
return "getConnection";
}

@Override
protected @Nullable String filePath(DataSource dataSource) {
return null;
}

@Override
protected @Nullable Long lineNumber(DataSource dataSource) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jdbc;
package io.opentelemetry.javaagent.instrumentation.jdbc.datasource;

import static io.opentelemetry.api.trace.SpanKind.CLIENT;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static io.opentelemetry.javaagent.instrumentation.jdbc.DataSourceTracer.tracer;
import static io.opentelemetry.javaagent.instrumentation.jdbc.datasource.DataSourceInstrumenters.instrumenter;
import static net.bytebuddy.matcher.ElementMatchers.named;

import io.opentelemetry.context.Context;
Expand Down Expand Up @@ -39,31 +38,28 @@ public static void start(
@Advice.This DataSource ds,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope) {
if (!Java8BytecodeBridge.currentSpan().getSpanContext().isValid()) {
Context parentContext = Java8BytecodeBridge.currentContext();
if (!Java8BytecodeBridge.spanFromContext(parentContext).getSpanContext().isValid()) {
// this instrumentation is already very noisy, and calls to getConnection outside of an
// existing trace do not tend to be very interesting
return;
}

context = tracer().startSpan(ds.getClass().getSimpleName() + ".getConnection", CLIENT);
context = instrumenter().start(parentContext, ds);
scope = context.makeCurrent();
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void stopSpan(
@Advice.This DataSource ds,
@Advice.Local("otelContext") Context context,
@Advice.Local("otelScope") Scope scope,
@Advice.Thrown Throwable throwable) {
if (scope == null) {
return;
}
scope.close();

if (throwable != null) {
tracer().endExceptionally(context, throwable);
} else {
tracer().end(context);
}
instrumenter().end(context, ds, null, throwable);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jdbc;
package io.opentelemetry.javaagent.instrumentation.jdbc.datasource;

import static java.util.Collections.singletonList;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.jdbc.datasource;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
import io.opentelemetry.instrumentation.api.instrumenter.SpanNameExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeAttributesExtractor;
import io.opentelemetry.instrumentation.api.instrumenter.code.CodeSpanNameExtractor;
import javax.sql.DataSource;

public final class DataSourceInstrumenters {
private static final String INSTRUMENTATION_NAME = "io.opentelemetry.javaagent.jdbc";

private static final Instrumenter<DataSource, Void> INSTRUMENTER;

static {
CodeAttributesExtractor<DataSource, Void> attributesExtractor =
new DataSourceCodeAttributesExtractor();
SpanNameExtractor<DataSource> spanNameExtractor =
CodeSpanNameExtractor.create(attributesExtractor);

INSTRUMENTER =
Instrumenter.<DataSource, Void>newBuilder(
GlobalOpenTelemetry.get(), INSTRUMENTATION_NAME, spanNameExtractor)
.addAttributesExtractor(attributesExtractor)
.newInstrumenter();
}

public static Instrumenter<DataSource, Void> instrumenter() {
return INSTRUMENTER;
}
}
Loading

0 comments on commit 5e4c568

Please sign in to comment.