Browse Source

APM: Add TraceContext interface to reduce server dependencies in telemetry (#103635)

`Tracer.startTrace(ThreadContext threadContext, Traceable traceable, String name, Map<String, Object> attributes)` takes in a `ThreadContext` which creates a dependency on `server`.  This change adds a new interface, `ThreadContext` with those methods required from `ThreadContext` and uses that as the parameter to `startTrace`.

The methods in the `TraceContext` interface are
```
    <T> T getTransient(String key);
    void putTransient(String key, Object value);
    String getHeader(String key);
    void putHeader(String key, String value);
```

which are needed for getting the parent context, the remote headers, the x-opaque-id and setting the remote headers for a trace.

This is an ugly but functional way to remove a dependency on server to be able to move telemetry in to a library for static metric registration.
Stuart Tettemer 1 year ago
parent
commit
0ce3e8cb71

+ 17 - 17
modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java

@@ -30,10 +30,10 @@ import org.elasticsearch.common.component.AbstractLifecycleComponent;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.Maps;
 import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
-import org.elasticsearch.common.util.concurrent.ThreadContext;
 import org.elasticsearch.core.Nullable;
 import org.elasticsearch.core.Releasable;
 import org.elasticsearch.tasks.Task;
+import org.elasticsearch.telemetry.tracing.TraceContext;
 import org.elasticsearch.telemetry.tracing.Traceable;
 
 import java.security.AccessController;
@@ -160,8 +160,8 @@ public class APMTracer extends AbstractLifecycleComponent implements org.elastic
     }
 
     @Override
-    public void startTrace(ThreadContext threadContext, Traceable traceable, String spanName, @Nullable Map<String, Object> attributes) {
-        assert threadContext != null;
+    public void startTrace(TraceContext traceContext, Traceable traceable, String spanName, @Nullable Map<String, Object> attributes) {
+        assert traceContext != null;
         String spanId = traceable.getSpanId();
         assert spanId != null;
         assert spanName != null;
@@ -183,21 +183,21 @@ public class APMTracer extends AbstractLifecycleComponent implements org.elastic
 
             // A span can have a parent span, which here is modelled though a parent span context.
             // Setting this is important for seeing a complete trace in the APM UI.
-            final Context parentContext = getParentContext(threadContext);
+            final Context parentContext = getParentContext(traceContext);
             if (parentContext != null) {
                 spanBuilder.setParent(parentContext);
             }
 
-            setSpanAttributes(threadContext, attributes, spanBuilder);
+            setSpanAttributes(traceContext, attributes, spanBuilder);
 
-            Instant startTime = threadContext.getTransient(Task.TRACE_START_TIME);
+            Instant startTime = traceContext.getTransient(Task.TRACE_START_TIME);
             if (startTime != null) {
                 spanBuilder.setStartTimestamp(startTime);
             }
             final Span span = spanBuilder.startSpan();
             final Context contextForNewSpan = Context.current().with(span);
 
-            updateThreadContext(threadContext, services, contextForNewSpan);
+            updateThreadContext(traceContext, services, contextForNewSpan);
 
             return contextForNewSpan;
         }));
@@ -222,29 +222,29 @@ public class APMTracer extends AbstractLifecycleComponent implements org.elastic
         spanBuilder.startSpan();
     }
 
-    private static void updateThreadContext(ThreadContext threadContext, APMServices services, Context context) {
+    private static void updateThreadContext(TraceContext traceContext, APMServices services, Context context) {
         // The new span context can be used as the parent context directly within the same Java process...
-        threadContext.putTransient(Task.APM_TRACE_CONTEXT, context);
+        traceContext.putTransient(Task.APM_TRACE_CONTEXT, context);
 
-        // ...whereas for tasks sent to other ES nodes, we need to put trace HTTP headers into the threadContext so
+        // ...whereas for tasks sent to other ES nodes, we need to put trace HTTP headers into the traceContext so
         // that they can be propagated.
-        services.openTelemetry.getPropagators().getTextMapPropagator().inject(context, threadContext, (tc, key, value) -> {
+        services.openTelemetry.getPropagators().getTextMapPropagator().inject(context, traceContext, (tc, key, value) -> {
             if (isSupportedContextKey(key)) {
                 tc.putHeader(key, value);
             }
         });
     }
 
-    private Context getParentContext(ThreadContext threadContext) {
+    private Context getParentContext(TraceContext traceContext) {
         // https://github.com/open-telemetry/opentelemetry-java/discussions/2884#discussioncomment-381870
         // If you just want to propagate across threads within the same process, you don't need context propagators (extract/inject).
         // You can just pass the Context object directly to another thread (it is immutable and thus thread-safe).
 
         // Attempt to fetch a local parent context first, otherwise look for a remote parent
-        Context parentContext = threadContext.getTransient("parent_" + Task.APM_TRACE_CONTEXT);
+        Context parentContext = traceContext.getTransient("parent_" + Task.APM_TRACE_CONTEXT);
         if (parentContext == null) {
-            final String traceParentHeader = threadContext.getTransient("parent_" + Task.TRACE_PARENT_HTTP_HEADER);
-            final String traceStateHeader = threadContext.getTransient("parent_" + Task.TRACE_STATE);
+            final String traceParentHeader = traceContext.getTransient("parent_" + Task.TRACE_PARENT_HTTP_HEADER);
+            final String traceStateHeader = traceContext.getTransient("parent_" + Task.TRACE_STATE);
 
             if (traceParentHeader != null) {
                 final Map<String, String> traceContextMap = Maps.newMapWithExpectedSize(2);
@@ -328,10 +328,10 @@ public class APMTracer extends AbstractLifecycleComponent implements org.elastic
         spanBuilder.setAttribute(org.elasticsearch.telemetry.tracing.Tracer.AttributeKeys.CLUSTER_NAME, clusterName);
     }
 
-    private void setSpanAttributes(ThreadContext threadContext, @Nullable Map<String, Object> spanAttributes, SpanBuilder spanBuilder) {
+    private void setSpanAttributes(TraceContext traceContext, @Nullable Map<String, Object> spanAttributes, SpanBuilder spanBuilder) {
         setSpanAttributes(spanAttributes, spanBuilder);
 
-        final String xOpaqueId = threadContext.getHeader(Task.X_OPAQUE_ID_HTTP_HEADER);
+        final String xOpaqueId = traceContext.getHeader(Task.X_OPAQUE_ID_HTTP_HEADER);
         if (xOpaqueId != null) {
             spanBuilder.setAttribute("es.x-opaque-id", xOpaqueId);
         }

+ 2 - 1
server/src/main/java/org/elasticsearch/common/util/concurrent/ThreadContext.java

@@ -23,6 +23,7 @@ import org.elasticsearch.core.Releasable;
 import org.elasticsearch.core.Tuple;
 import org.elasticsearch.http.HttpTransportSettings;
 import org.elasticsearch.tasks.Task;
+import org.elasticsearch.telemetry.tracing.TraceContext;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -69,7 +70,7 @@ import static org.elasticsearch.tasks.Task.HEADERS_TO_COPY;
  * </pre>
  *
  */
-public final class ThreadContext implements Writeable {
+public final class ThreadContext implements Writeable, TraceContext {
 
     public static final String PREFIX = "request.headers";
     public static final Setting<Settings> DEFAULT_HEADERS_SETTING = Setting.groupSetting(PREFIX + ".", Property.NodeScope);

+ 34 - 0
server/src/main/java/org/elasticsearch/telemetry/tracing/TraceContext.java

@@ -0,0 +1,34 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.telemetry.tracing;
+
+/**
+ * Required methods from ThreadContext for Tracer
+ */
+public interface TraceContext {
+    /**
+     * Returns a transient header object or <code>null</code> if there is no header for the given key
+     */
+    <T> T getTransient(String key);
+
+    /**
+     * Puts a transient header object into this context
+     */
+    void putTransient(String key, Object value);
+
+    /**
+     * Returns the header for the given key or <code>null</code> if not present
+     */
+    String getHeader(String key);
+
+    /**
+     * Puts a header into the context
+     */
+    void putHeader(String key, String value);
+}

+ 3 - 6
server/src/main/java/org/elasticsearch/telemetry/tracing/Tracer.java

@@ -8,7 +8,6 @@
 
 package org.elasticsearch.telemetry.tracing;
 
-import org.elasticsearch.common.util.concurrent.ThreadContext;
 import org.elasticsearch.core.Releasable;
 
 import java.util.Map;
@@ -35,15 +34,13 @@ public interface Tracer {
 
     /**
      * Called when a span starts.
-     * @param threadContext the current context. Required for tracing parent/child span activity.
+     * @param traceContext the current context. Required for tracing parent/child span activity.
      * @param traceable provides a unique identifier for the activity, and will not be sent to the tracing system. Add the ID
      *                  to the attributes if it is important
      * @param name the name of the span. Used to filter out spans, but also sent to the tracing system
      * @param attributes arbitrary key/value data for the span. Sent to the tracing system
      */
-    default void startTrace(ThreadContext threadContext, Traceable traceable, String name, Map<String, Object> attributes) {
-        startTrace(threadContext, traceable, name, attributes);
-    }
+    void startTrace(TraceContext traceContext, Traceable traceable, String name, Map<String, Object> attributes);
 
     /**
      * Called when a span starts. This version of the method relies on context to assign the span a parent.
@@ -148,7 +145,7 @@ public interface Tracer {
      */
     Tracer NOOP = new Tracer() {
         @Override
-        public void startTrace(ThreadContext threadContext, Traceable traceable, String name, Map<String, Object> attributes) {}
+        public void startTrace(TraceContext traceContext, Traceable traceable, String name, Map<String, Object> attributes) {}
 
         @Override
         public void startTrace(String name, Map<String, Object> attributes) {}