Skip to content

Commit bac5e69

Browse files
committed
Refactor TracingOpenTelemetry for handler-specific attributes, response/error capture, and contextual propagation
1 parent e01f87d commit bac5e69

10 files changed

Lines changed: 800 additions & 159 deletions

File tree

powertools-tracing-opentelemetry/pom.xml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828

2929
<name>Powertools for AWS Lambda (Java) - Tracing OpenTelemetry</name>
3030
<description>
31-
A suite of utilities for AWS Lambda Functions that makes tracing with OpenTelemetry, structured logging and creating custom metrics asynchronously easier.
31+
A suite of utilities for AWS Lambda Functions that makes tracing with OpenTelemetry, structured logging and
32+
creating custom metrics asynchronously easier.
3233
</description>
3334

3435
<dependencies>
@@ -37,6 +38,16 @@
3738
<artifactId>opentelemetry-api</artifactId>
3839
<version>${opentelemetry-api.version}</version>
3940
</dependency>
41+
<dependency>
42+
<groupId>io.opentelemetry</groupId>
43+
<artifactId>opentelemetry-sdk</artifactId>
44+
<version>${opentelemetry-api.version}</version>
45+
</dependency>
46+
<dependency>
47+
<groupId>io.opentelemetry</groupId>
48+
<artifactId>opentelemetry-exporter-otlp</artifactId>
49+
<version>${opentelemetry-api.version}</version>
50+
</dependency>
4051
<dependency>
4152
<groupId>org.aspectj</groupId>
4253
<artifactId>aspectjrt</artifactId>
@@ -64,12 +75,6 @@
6475
</dependency>
6576

6677
<!-- Test dependencies -->
67-
<dependency>
68-
<groupId>io.opentelemetry</groupId>
69-
<artifactId>opentelemetry-sdk</artifactId>
70-
<version>${opentelemetry-api.version}</version>
71-
<scope>test</scope>
72-
</dependency>
7378
<dependency>
7479
<groupId>io.opentelemetry</groupId>
7580
<artifactId>opentelemetry-sdk-testing</artifactId>

powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/TracingOtel.java renamed to powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/Tracing.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
*/
1515
@Retention(RetentionPolicy.RUNTIME)
1616
@Target(ElementType.METHOD)
17-
public @interface TracingOtel {
17+
public @interface Tracing {
1818
/**
1919
* The namespace associated with the span.
2020
*

powertools-tracing-opentelemetry/src/main/java/software/amazon/lambda/powertools/tracing/opentelemetry/TracingOpenTelemetry.java

Lines changed: 205 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
* Copyright 2023 Amazon.com, Inc. or its affiliates.
33
* Licensed under the Apache License, Version 2.0 (the
44
* "License"); you may not use this file except in compliance
5-
* with the License. You may obtain a copy of the License at
5+
* with the License. You may obtain a copy of the License at
6+
*
67
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
79
* Unless required by applicable law or agreed to in writing, software
810
* distributed under the License is distributed on an "AS IS" BASIS,
911
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -14,100 +16,240 @@
1416

1517
package software.amazon.lambda.powertools.tracing.opentelemetry;
1618

17-
import io.opentelemetry.api.GlobalOpenTelemetry;
19+
import io.opentelemetry.api.common.Attributes;
1820
import io.opentelemetry.api.trace.Span;
21+
import io.opentelemetry.api.trace.SpanKind;
1922
import io.opentelemetry.api.trace.Tracer;
23+
import io.opentelemetry.context.Context;
24+
import io.opentelemetry.context.propagation.TextMapGetter;
25+
import io.opentelemetry.context.propagation.TextMapPropagator;
26+
import io.opentelemetry.context.propagation.TextMapSetter;
2027
import java.util.Objects;
28+
import software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor;
29+
import software.amazon.lambda.powertools.tracing.opentelemetry.internal.OpenTelemetryProvider;
2130
import software.amazon.lambda.powertools.tracing.opentelemetry.internal.SpanOperation;
2231
import software.amazon.lambda.powertools.tracing.opentelemetry.internal.SpanScope;
2332

24-
/**
25-
* A wrapper for OpenTelemetry's Tracer that simplifies the creation and management of spans.
26-
* This class provides utility functions for starting and controlling spans and their contexts
27-
* in the current execution thread.
28-
* <p>
29-
* This is a final class and cannot be extended.
30-
*/
31-
public final class TracingOpenTelemetry {
3233

33-
private static final String INSTRUMENTATION_NAME =
34-
"aws-lambda-powertools";
34+
public final class TracingOpenTelemetry {
3535

3636
private final Tracer tracer;
37+
private final TextMapPropagator propagator;
38+
39+
private TracingOpenTelemetry(Builder builder) {
40+
this.tracer = Objects.requireNonNull(
41+
builder.tracer,
42+
"tracer must not be null"
43+
);
44+
this.propagator = Objects.requireNonNull(
45+
builder.propagator,
46+
"propagator must not be null"
47+
);
48+
}
3749

38-
/**
39-
* Creates a tracing instance using the provided tracer.
40-
*
41-
* <p>This constructor is primarily useful for testing.
42-
*
43-
* @param tracer the OpenTelemetry tracer
44-
*/
45-
TracingOpenTelemetry(Tracer tracer) {
46-
this.tracer = Objects.requireNonNull(tracer, "tracer must not be null");
47-
}
48-
49-
/**
50-
* Initializes a new instance of the {@code TracingOpenTelemetry} class, using
51-
* the global OpenTelemetry tracer identified by the instrumentation name.
52-
* <p>
53-
* This constructor simplifies the setup process for applications by
54-
* automatically leveraging the globally configured instrumentation tracer.
55-
*/
5650
public TracingOpenTelemetry() {
57-
this(GlobalOpenTelemetry.getTracer(INSTRUMENTATION_NAME));
51+
this(OpenTelemetryProvider.tracer());
52+
}
53+
54+
55+
public TracingOpenTelemetry(Tracer tracer) {
56+
this(tracer, createDefaultPropagator());
5857
}
5958

60-
/**
61-
* Creates a new span with the specified name and makes it the current span in the thread context.
62-
* The span must be manually closed to properly end it and revert the thread context.
63-
*
64-
* @param name the name of the span to be created
65-
* @return an instance of {@link SpanScope}, which represents the created span and its associated context
66-
*/
59+
60+
public TracingOpenTelemetry(
61+
Tracer tracer,
62+
TextMapPropagator propagator) {
63+
64+
this.tracer = Objects.requireNonNull(
65+
tracer,
66+
"tracer must not be null"
67+
);
68+
this.propagator = Objects.requireNonNull(
69+
propagator,
70+
"propagator must not be null"
71+
);
72+
}
73+
74+
6775
public SpanScope addSpan(String name) {
68-
Span span = tracer
69-
.spanBuilder(name)
76+
return addSpan(name, SpanKind.INTERNAL);
77+
}
78+
79+
80+
public SpanScope addSpan(
81+
String name,
82+
SpanKind kind) {
83+
84+
return addSpan(name, kind, Attributes.empty());
85+
}
86+
87+
88+
public SpanScope addSpan(
89+
String name,
90+
SpanKind kind,
91+
Attributes attributes) {
92+
93+
Objects.requireNonNull(name, "name must not be null");
94+
Objects.requireNonNull(kind, "kind must not be null");
95+
Objects.requireNonNull(attributes, "attributes must not be null");
96+
97+
Span span = tracer.spanBuilder(name)
98+
.setSpanKind(kind)
99+
.setAllAttributes(attributes)
70100
.startSpan();
71101

72102
return new SpanScope(span);
73103
}
74104

75-
/**
76-
* Retrieves the current active span in the execution context.
77-
*
78-
* @return the current {@link Span} if one is active, or a default no-op {@link Span} if none is active
79-
*/
105+
public SpanScope addSpan(
106+
String name,
107+
SpanKind kind,
108+
Attributes attributes,
109+
Context parentContext) {
110+
111+
Objects.requireNonNull(parentContext, "parentContext must not be null");
112+
113+
Span span = tracer.spanBuilder(name)
114+
.setParent(parentContext)
115+
.setSpanKind(kind)
116+
.setAllAttributes(attributes)
117+
.startSpan();
118+
119+
return new SpanScope(span);
120+
}
121+
122+
80123
public Span currentSpan() {
81124
return Span.current();
82125
}
83126

84-
/**
85-
* Executes the specified operation within the context of a new span.
86-
* The span is automatically managed and closed when the operation completes
87-
* or an exception is thrown.
88-
*
89-
* @param name the name of the span to be created
90-
* @param operation the operation to be executed within the span's context
91-
* @throws Exception if the provided operation throws an exception during execution
92-
*/
93-
public <T> T withSpan(String name, SpanOperation<T> operation) throws Exception {
127+
128+
public <T> T withSpan(
129+
String name,
130+
SpanOperation<T> operation) throws Exception {
131+
132+
Objects.requireNonNull(operation, "operation must not be null");
133+
94134
try (SpanScope scope = addSpan(name)) {
95135
try {
96136
return operation.execute(scope.span());
97-
} catch (Exception e) {
98-
scope.recordException(e);
99-
throw e;
137+
} catch (Exception exception) {
138+
scope.recordException(exception);
139+
throw exception;
100140
}
101141
}
102142
}
103143

104-
/**
105-
* Creates a new tracing instance using the global OpenTelemetry tracer.
106-
*
107-
* @return a new tracing instance
108-
*/
144+
public <T> T captureLambdaHandler(
145+
String name,
146+
com.amazonaws.services.lambda.runtime.Context lambdaContext,
147+
io.opentelemetry.context.Context parentContext,
148+
SpanOperation<T> operation) throws Exception {
149+
150+
Objects.requireNonNull(name, "name must not be null");
151+
Objects.requireNonNull(parentContext, "parentContext must not be null");
152+
Objects.requireNonNull(operation, "operation must not be null");
153+
154+
Span span = tracer.spanBuilder(name)
155+
.setParent(parentContext)
156+
.setSpanKind(SpanKind.SERVER)
157+
.setAttribute("faas.coldstart", LambdaHandlerProcessor.isColdStart())
158+
.setAttribute("faas.invocation_id", lambdaContext.getAwsRequestId())
159+
.startSpan();
160+
161+
try (SpanScope scope = new SpanScope(span)) {
162+
try {
163+
T result = operation.execute(span);
164+
165+
LambdaHandlerProcessor.coldStartDone();
166+
167+
return result;
168+
} catch (Exception exception) {
169+
scope.recordException(exception);
170+
throw exception;
171+
}
172+
}
173+
}
174+
175+
public <T> Context extractContext(
176+
T carrier,
177+
TextMapGetter<T> getter) {
178+
179+
return extractContext(Context.current(), carrier, getter);
180+
}
181+
182+
183+
public <T> Context extractContext(
184+
Context context,
185+
T carrier,
186+
TextMapGetter<T> getter) {
187+
188+
Objects.requireNonNull(context, "context must not be null");
189+
Objects.requireNonNull(getter, "getter must not be null");
190+
191+
return propagator.extract(
192+
context,
193+
carrier,
194+
getter
195+
);
196+
}
197+
198+
199+
public <T> void injectContext(
200+
T carrier,
201+
TextMapSetter<T> setter) {
202+
203+
injectContext(Context.current(), carrier, setter);
204+
}
205+
206+
207+
public <T> void injectContext(
208+
Context context,
209+
T carrier,
210+
TextMapSetter<T> setter) {
211+
212+
Objects.requireNonNull(context, "context must not be null");
213+
Objects.requireNonNull(setter, "setter must not be null");
214+
215+
propagator.inject(
216+
context,
217+
carrier,
218+
setter
219+
);
220+
}
221+
222+
private static TextMapPropagator createDefaultPropagator() {
223+
return OpenTelemetryProvider.propagator();
224+
}
225+
226+
109227
public static TracingOpenTelemetry create() {
110228
return new TracingOpenTelemetry();
111229
}
112230

231+
232+
public static Builder builder() {
233+
return new Builder();
234+
}
235+
236+
public static final class Builder {
237+
238+
private Tracer tracer;
239+
private TextMapPropagator propagator = createDefaultPropagator();
240+
241+
public Builder tracer(Tracer tracer) {
242+
this.tracer = tracer;
243+
return this;
244+
}
245+
246+
public Builder propagator(TextMapPropagator propagator) {
247+
this.propagator = propagator;
248+
return this;
249+
}
250+
251+
public TracingOpenTelemetry build() {
252+
return new TracingOpenTelemetry(this);
253+
}
254+
}
113255
}

0 commit comments

Comments
 (0)