Convert aws lambda integration to Instrumenter (#4362)
* Convert AwsLambdaMessageTracer to Instrumenter * Convert AwsLambdaTracer to Instrumenter * Rename class * Polish * Polish * Move classes to internal package
This commit is contained in:
parent
2904578875
commit
699a5adb00
|
@ -5,23 +5,26 @@
|
||||||
|
|
||||||
package io.opentelemetry.javaagent.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.javaagent.instrumentation.awslambda.v1_0;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
||||||
import io.opentelemetry.api.GlobalOpenTelemetry;
|
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||||
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaMessageTracer;
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaTracer;
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaFunctionInstrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaFunctionInstrumenterFactory;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaSqsInstrumenterFactory;
|
||||||
|
|
||||||
public final class AwsLambdaInstrumentationHelper {
|
public final class AwsLambdaInstrumentationHelper {
|
||||||
|
|
||||||
private static final AwsLambdaTracer FUNCTION_TRACER =
|
private static final AwsLambdaFunctionInstrumenter FUNCTION_INSTRUMENTER =
|
||||||
new AwsLambdaTracer(GlobalOpenTelemetry.get());
|
AwsLambdaFunctionInstrumenterFactory.createInstrumenter(GlobalOpenTelemetry.get());
|
||||||
|
|
||||||
public static AwsLambdaTracer functionTracer() {
|
public static AwsLambdaFunctionInstrumenter functionInstrumenter() {
|
||||||
return FUNCTION_TRACER;
|
return FUNCTION_INSTRUMENTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final AwsLambdaMessageTracer MESSAGE_TRACER =
|
private static final Instrumenter<SQSEvent, Void> MESSAGE_TRACER =
|
||||||
new AwsLambdaMessageTracer(GlobalOpenTelemetry.get());
|
AwsLambdaSqsInstrumenterFactory.forEvent(GlobalOpenTelemetry.get());
|
||||||
|
|
||||||
public static AwsLambdaMessageTracer messageTracer() {
|
public static Instrumenter<SQSEvent, Void> messageInstrumenter() {
|
||||||
return MESSAGE_TRACER;
|
return MESSAGE_TRACER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@ package io.opentelemetry.javaagent.instrumentation.awslambda.v1_0;
|
||||||
|
|
||||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
|
||||||
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
|
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
|
||||||
import static io.opentelemetry.javaagent.instrumentation.awslambda.v1_0.AwsLambdaInstrumentationHelper.functionTracer;
|
import static io.opentelemetry.javaagent.instrumentation.awslambda.v1_0.AwsLambdaInstrumentationHelper.functionInstrumenter;
|
||||||
import static io.opentelemetry.javaagent.instrumentation.awslambda.v1_0.AwsLambdaInstrumentationHelper.messageTracer;
|
import static io.opentelemetry.javaagent.instrumentation.awslambda.v1_0.AwsLambdaInstrumentationHelper.messageInstrumenter;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
|
||||||
import static net.bytebuddy.matcher.ElementMatchers.named;
|
import static net.bytebuddy.matcher.ElementMatchers.named;
|
||||||
|
@ -16,11 +16,12 @@ import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
|
||||||
|
|
||||||
import com.amazonaws.services.lambda.runtime.Context;
|
import com.amazonaws.services.lambda.runtime.Context;
|
||||||
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
||||||
import io.opentelemetry.api.trace.SpanKind;
|
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaRequest;
|
||||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
|
||||||
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
|
||||||
import io.opentelemetry.javaagent.instrumentation.api.OpenTelemetrySdkAccess;
|
import io.opentelemetry.javaagent.instrumentation.api.OpenTelemetrySdkAccess;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import net.bytebuddy.asm.Advice;
|
import net.bytebuddy.asm.Advice;
|
||||||
import net.bytebuddy.description.type.TypeDescription;
|
import net.bytebuddy.description.type.TypeDescription;
|
||||||
|
@ -56,21 +57,34 @@ public class AwsLambdaRequestHandlerInstrumentation implements TypeInstrumentati
|
||||||
public static void onEnter(
|
public static void onEnter(
|
||||||
@Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg,
|
@Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg,
|
||||||
@Advice.Argument(1) Context context,
|
@Advice.Argument(1) Context context,
|
||||||
|
@Advice.Local("otelInput") AwsLambdaRequest input,
|
||||||
@Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext,
|
@Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext,
|
||||||
@Advice.Local("otelFunctionScope") Scope functionScope,
|
@Advice.Local("otelFunctionScope") Scope functionScope,
|
||||||
@Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext,
|
@Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext,
|
||||||
@Advice.Local("otelMessageScope") Scope messageScope) {
|
@Advice.Local("otelMessageScope") Scope messageScope) {
|
||||||
functionContext = functionTracer().startSpan(context, SpanKind.SERVER, arg);
|
input = AwsLambdaRequest.create(context, arg, Collections.emptyMap());
|
||||||
|
io.opentelemetry.context.Context parentContext = functionInstrumenter().extract(input);
|
||||||
|
|
||||||
|
if (!functionInstrumenter().shouldStart(parentContext, input)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
functionContext = functionInstrumenter().start(parentContext, input);
|
||||||
functionScope = functionContext.makeCurrent();
|
functionScope = functionContext.makeCurrent();
|
||||||
|
|
||||||
if (arg instanceof SQSEvent) {
|
if (arg instanceof SQSEvent) {
|
||||||
messageContext = messageTracer().startSpan(functionContext, (SQSEvent) arg);
|
if (messageInstrumenter().shouldStart(functionContext, (SQSEvent) arg)) {
|
||||||
messageScope = messageContext.makeCurrent();
|
messageContext = messageInstrumenter().start(functionContext, (SQSEvent) arg);
|
||||||
|
messageScope = messageContext.makeCurrent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
|
||||||
public static void stopSpan(
|
public static void stopSpan(
|
||||||
|
@Advice.Argument(value = 0, typing = Typing.DYNAMIC) Object arg,
|
||||||
@Advice.Thrown Throwable throwable,
|
@Advice.Thrown Throwable throwable,
|
||||||
|
@Advice.Local("otelInput") AwsLambdaRequest input,
|
||||||
@Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext,
|
@Advice.Local("otelFunctionContext") io.opentelemetry.context.Context functionContext,
|
||||||
@Advice.Local("otelFunctionScope") Scope functionScope,
|
@Advice.Local("otelFunctionScope") Scope functionScope,
|
||||||
@Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext,
|
@Advice.Local("otelMessageContext") io.opentelemetry.context.Context messageContext,
|
||||||
|
@ -78,19 +92,14 @@ public class AwsLambdaRequestHandlerInstrumentation implements TypeInstrumentati
|
||||||
|
|
||||||
if (messageScope != null) {
|
if (messageScope != null) {
|
||||||
messageScope.close();
|
messageScope.close();
|
||||||
if (throwable != null) {
|
messageInstrumenter().end(messageContext, (SQSEvent) arg, null, throwable);
|
||||||
messageTracer().endExceptionally(messageContext, throwable);
|
|
||||||
} else {
|
|
||||||
messageTracer().end(messageContext);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
functionScope.close();
|
if (functionScope != null) {
|
||||||
if (throwable != null) {
|
functionScope.close();
|
||||||
functionTracer().endExceptionally(functionContext, throwable);
|
functionInstrumenter().end(functionContext, input, null, throwable);
|
||||||
} else {
|
|
||||||
functionTracer().end(functionContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenTelemetrySdkAccess.forceFlush(1, TimeUnit.SECONDS);
|
OpenTelemetrySdkAccess.forceFlush(1, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,9 @@ dependencies {
|
||||||
compileOnly("io.opentelemetry:opentelemetry-sdk")
|
compileOnly("io.opentelemetry:opentelemetry-sdk")
|
||||||
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
|
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
|
||||||
|
|
||||||
|
compileOnly("com.google.auto.value:auto-value-annotations")
|
||||||
|
annotationProcessor("com.google.auto.value:auto-value")
|
||||||
|
|
||||||
library("com.amazonaws:aws-lambda-java-core:1.0.0")
|
library("com.amazonaws:aws-lambda-java-core:1.0.0")
|
||||||
// First version to includes support for SQSEvent, currently the most popular message queue used
|
// First version to includes support for SQSEvent, currently the most popular message queue used
|
||||||
// with lambda.
|
// with lambda.
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright The OpenTelemetry Authors
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
|
||||||
|
|
||||||
import static io.opentelemetry.api.trace.SpanKind.CONSUMER;
|
|
||||||
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
|
|
||||||
import io.opentelemetry.api.OpenTelemetry;
|
|
||||||
import io.opentelemetry.api.trace.Span;
|
|
||||||
import io.opentelemetry.api.trace.SpanBuilder;
|
|
||||||
import io.opentelemetry.api.trace.SpanContext;
|
|
||||||
import io.opentelemetry.context.Context;
|
|
||||||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
|
||||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
|
||||||
|
|
||||||
public class AwsLambdaMessageTracer extends BaseTracer {
|
|
||||||
|
|
||||||
private static final String AWS_TRACE_HEADER_SQS_ATTRIBUTE_KEY = "AWSTraceHeader";
|
|
||||||
|
|
||||||
public AwsLambdaMessageTracer(OpenTelemetry openTelemetry) {
|
|
||||||
super(openTelemetry);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Context startSpan(Context parentContext, SQSEvent event) {
|
|
||||||
// Use event source in name if all messages have the same source, otherwise use placeholder.
|
|
||||||
String source = "multiple_sources";
|
|
||||||
if (!event.getRecords().isEmpty()) {
|
|
||||||
String messageSource = event.getRecords().get(0).getEventSource();
|
|
||||||
for (int i = 1; i < event.getRecords().size(); i++) {
|
|
||||||
SQSMessage message = event.getRecords().get(i);
|
|
||||||
if (!message.getEventSource().equals(messageSource)) {
|
|
||||||
messageSource = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (messageSource != null) {
|
|
||||||
source = messageSource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SpanBuilder span = spanBuilder(parentContext, source + " process", CONSUMER);
|
|
||||||
|
|
||||||
span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS");
|
|
||||||
span.setAttribute(SemanticAttributes.MESSAGING_OPERATION, "process");
|
|
||||||
|
|
||||||
for (SQSMessage message : event.getRecords()) {
|
|
||||||
addLinkToMessageParent(message, span);
|
|
||||||
}
|
|
||||||
|
|
||||||
return parentContext.with(span.startSpan());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Context startSpan(Context parentContext, SQSMessage message) {
|
|
||||||
SpanBuilder span = spanBuilder(parentContext, message.getEventSource() + " process", CONSUMER);
|
|
||||||
|
|
||||||
span.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS");
|
|
||||||
span.setAttribute(SemanticAttributes.MESSAGING_OPERATION, "process");
|
|
||||||
span.setAttribute(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId());
|
|
||||||
span.setAttribute(SemanticAttributes.MESSAGING_DESTINATION, message.getEventSource());
|
|
||||||
|
|
||||||
addLinkToMessageParent(message, span);
|
|
||||||
|
|
||||||
return parentContext.with(span.startSpan());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void addLinkToMessageParent(SQSMessage message, SpanBuilder span) {
|
|
||||||
String parentHeader = message.getAttributes().get(AWS_TRACE_HEADER_SQS_ATTRIBUTE_KEY);
|
|
||||||
if (parentHeader != null) {
|
|
||||||
SpanContext parentCtx =
|
|
||||||
Span.fromContext(ParentContextExtractor.fromXrayHeader(parentHeader)).getSpanContext();
|
|
||||||
if (parentCtx.isValid()) {
|
|
||||||
span.addLink(parentCtx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getInstrumentationName() {
|
|
||||||
return "io.opentelemetry.aws-lambda-1.0";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.Context;
|
||||||
|
import com.google.auto.value.AutoValue;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@AutoValue
|
||||||
|
public abstract class AwsLambdaRequest {
|
||||||
|
|
||||||
|
public static AwsLambdaRequest create(
|
||||||
|
Context awsContext, Object input, Map<String, String> headers) {
|
||||||
|
return new AutoValue_AwsLambdaRequest(awsContext, input, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Context getAwsContext();
|
||||||
|
|
||||||
|
public abstract Object getInput();
|
||||||
|
|
||||||
|
public abstract Map<String, String> getHeaders();
|
||||||
|
}
|
|
@ -1,139 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright The OpenTelemetry Authors
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
|
||||||
|
|
||||||
import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.CLOUD_ACCOUNT_ID;
|
|
||||||
import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.FAAS_ID;
|
|
||||||
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_EXECUTION;
|
|
||||||
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_TRIGGER;
|
|
||||||
|
|
||||||
import com.amazonaws.services.lambda.runtime.Context;
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
|
|
||||||
import io.opentelemetry.api.OpenTelemetry;
|
|
||||||
import io.opentelemetry.api.trace.Span;
|
|
||||||
import io.opentelemetry.api.trace.SpanBuilder;
|
|
||||||
import io.opentelemetry.api.trace.SpanKind;
|
|
||||||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
|
||||||
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FaasTriggerValues;
|
|
||||||
import java.lang.invoke.MethodHandle;
|
|
||||||
import java.lang.invoke.MethodHandles;
|
|
||||||
import java.lang.invoke.MethodType;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
||||||
|
|
||||||
// Context is defined in both OTel and Lambda
|
|
||||||
@SuppressWarnings("ParameterPackage")
|
|
||||||
public class AwsLambdaTracer extends BaseTracer {
|
|
||||||
|
|
||||||
@Nullable private static final MethodHandle GET_FUNCTION_ARN;
|
|
||||||
|
|
||||||
static {
|
|
||||||
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
|
||||||
MethodHandle getFunctionArn;
|
|
||||||
try {
|
|
||||||
getFunctionArn =
|
|
||||||
lookup.findVirtual(
|
|
||||||
Context.class, "getInvokedFunctionArn", MethodType.methodType(String.class));
|
|
||||||
} catch (NoSuchMethodException | IllegalAccessException e) {
|
|
||||||
getFunctionArn = null;
|
|
||||||
}
|
|
||||||
GET_FUNCTION_ARN = getFunctionArn;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cached accountId value
|
|
||||||
private volatile String accountId;
|
|
||||||
|
|
||||||
public AwsLambdaTracer(OpenTelemetry openTelemetry) {
|
|
||||||
super(openTelemetry);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setAttributes(SpanBuilder span, Context context, Object input) {
|
|
||||||
setCommonAttributes(span, context);
|
|
||||||
if (input instanceof APIGatewayProxyRequestEvent) {
|
|
||||||
span.setAttribute(FAAS_TRIGGER, FaasTriggerValues.HTTP);
|
|
||||||
HttpSpanAttributes.onRequest(span, (APIGatewayProxyRequestEvent) input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCommonAttributes(SpanBuilder span, Context context) {
|
|
||||||
span.setAttribute(FAAS_EXECUTION, context.getAwsRequestId());
|
|
||||||
String arn = getFunctionArn(context);
|
|
||||||
if (arn != null) {
|
|
||||||
span.setAttribute(FAAS_ID, arn);
|
|
||||||
}
|
|
||||||
String accountId = getAccountId(arn);
|
|
||||||
if (accountId != null) {
|
|
||||||
span.setAttribute(CLOUD_ACCOUNT_ID, accountId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String getFunctionArn(Context context) {
|
|
||||||
if (GET_FUNCTION_ARN == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return (String) GET_FUNCTION_ARN.invoke(context);
|
|
||||||
} catch (Throwable throwable) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String getAccountId(@Nullable String arn) {
|
|
||||||
if (arn == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (accountId == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (accountId == null) {
|
|
||||||
String[] arnParts = arn.split(":");
|
|
||||||
if (arnParts.length >= 5) {
|
|
||||||
accountId = arnParts[4];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return accountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String spanName(Context context, Object input) {
|
|
||||||
String name = null;
|
|
||||||
if (input instanceof APIGatewayProxyRequestEvent) {
|
|
||||||
name = ((APIGatewayProxyRequestEvent) input).getResource();
|
|
||||||
}
|
|
||||||
return name == null ? context.getFunctionName() : name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public io.opentelemetry.context.Context startSpan(
|
|
||||||
Context awsContext, SpanKind kind, Object input) {
|
|
||||||
return startSpan(awsContext, kind, input, Collections.emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
public io.opentelemetry.context.Context startSpan(
|
|
||||||
Context awsContext, SpanKind kind, Object input, Map<String, String> headers) {
|
|
||||||
io.opentelemetry.context.Context parentContext = ParentContextExtractor.extract(headers, this);
|
|
||||||
|
|
||||||
SpanBuilder spanBuilder = spanBuilder(parentContext, spanName(awsContext, input), kind);
|
|
||||||
setAttributes(spanBuilder, awsContext, input);
|
|
||||||
|
|
||||||
return withServerSpan(parentContext, spanBuilder.startSpan());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onOutput(io.opentelemetry.context.Context context, Object output) {
|
|
||||||
if (output instanceof APIGatewayProxyResponseEvent) {
|
|
||||||
HttpSpanAttributes.onResponse(
|
|
||||||
Span.fromContext(context), (APIGatewayProxyResponseEvent) output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getInstrumentationName() {
|
|
||||||
return "io.opentelemetry.aws-lambda-1.0";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright The OpenTelemetry Authors
|
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
|
||||||
|
|
||||||
import static io.opentelemetry.instrumentation.awslambda.v1_0.MapUtils.emptyIfNull;
|
|
||||||
import static io.opentelemetry.instrumentation.awslambda.v1_0.MapUtils.lowercaseMap;
|
|
||||||
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD;
|
|
||||||
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE;
|
|
||||||
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL;
|
|
||||||
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_USER_AGENT;
|
|
||||||
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
|
|
||||||
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
|
|
||||||
import io.opentelemetry.api.trace.Span;
|
|
||||||
import io.opentelemetry.api.trace.SpanBuilder;
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
final class HttpSpanAttributes {
|
|
||||||
static void onRequest(SpanBuilder span, APIGatewayProxyRequestEvent request) {
|
|
||||||
String httpMethod = request.getHttpMethod();
|
|
||||||
if (httpMethod != null) {
|
|
||||||
span.setAttribute(HTTP_METHOD, httpMethod);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> headers = lowercaseMap(request.getHeaders());
|
|
||||||
String userAgent = headers.get("user-agent");
|
|
||||||
if (userAgent != null) {
|
|
||||||
span.setAttribute(HTTP_USER_AGENT, userAgent);
|
|
||||||
}
|
|
||||||
String url = getHttpUrl(request, headers);
|
|
||||||
if (!url.isEmpty()) {
|
|
||||||
span.setAttribute(HTTP_URL, url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getHttpUrl(
|
|
||||||
APIGatewayProxyRequestEvent request, Map<String, String> headers) {
|
|
||||||
StringBuilder str = new StringBuilder();
|
|
||||||
|
|
||||||
String scheme = headers.get("x-forwarded-proto");
|
|
||||||
if (scheme != null) {
|
|
||||||
str.append(scheme).append("://");
|
|
||||||
}
|
|
||||||
String host = headers.get("host");
|
|
||||||
if (host != null) {
|
|
||||||
str.append(host);
|
|
||||||
}
|
|
||||||
String path = request.getPath();
|
|
||||||
if (path != null) {
|
|
||||||
str.append(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
boolean first = true;
|
|
||||||
for (Map.Entry<String, String> entry :
|
|
||||||
emptyIfNull(request.getQueryStringParameters()).entrySet()) {
|
|
||||||
String key = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name());
|
|
||||||
String value = URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name());
|
|
||||||
str.append(first ? '?' : '&').append(key).append('=').append(value);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
} catch (UnsupportedEncodingException ignored) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
return str.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void onResponse(Span span, APIGatewayProxyResponseEvent response) {
|
|
||||||
Integer statusCode = response.getStatusCode();
|
|
||||||
if (statusCode != null) {
|
|
||||||
span.setAttribute(HTTP_STATUS_CODE, statusCode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpSpanAttributes() {}
|
|
||||||
}
|
|
|
@ -8,8 +8,9 @@ package io.opentelemetry.instrumentation.awslambda.v1_0;
|
||||||
import com.amazonaws.services.lambda.runtime.Context;
|
import com.amazonaws.services.lambda.runtime.Context;
|
||||||
import com.amazonaws.services.lambda.runtime.RequestHandler;
|
import com.amazonaws.services.lambda.runtime.RequestHandler;
|
||||||
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
|
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
|
||||||
import io.opentelemetry.api.trace.SpanKind;
|
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaFunctionInstrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaFunctionInstrumenterFactory;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -25,7 +26,7 @@ public abstract class TracingRequestHandler<I, O> implements RequestHandler<I, O
|
||||||
|
|
||||||
protected static final Duration DEFAULT_FLUSH_TIMEOUT = Duration.ofSeconds(1);
|
protected static final Duration DEFAULT_FLUSH_TIMEOUT = Duration.ofSeconds(1);
|
||||||
|
|
||||||
private final AwsLambdaTracer tracer;
|
private final AwsLambdaFunctionInstrumenter instrumenter;
|
||||||
private final OpenTelemetrySdk openTelemetrySdk;
|
private final OpenTelemetrySdk openTelemetrySdk;
|
||||||
private final long flushTimeoutNanos;
|
private final long flushTimeoutNanos;
|
||||||
|
|
||||||
|
@ -43,47 +44,55 @@ public abstract class TracingRequestHandler<I, O> implements RequestHandler<I, O
|
||||||
* invocation.
|
* invocation.
|
||||||
*/
|
*/
|
||||||
protected TracingRequestHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
protected TracingRequestHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
||||||
this(openTelemetrySdk, flushTimeout, new AwsLambdaTracer(openTelemetrySdk));
|
this(
|
||||||
|
openTelemetrySdk,
|
||||||
|
flushTimeout,
|
||||||
|
AwsLambdaFunctionInstrumenterFactory.createInstrumenter(openTelemetrySdk));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link TracingRequestHandler} which flushes the provided {@link
|
* Creates a new {@link TracingRequestHandler} which flushes the provided {@link
|
||||||
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
||||||
* invocation, and traces using the provided {@link AwsLambdaTracer}.
|
* invocation, and traces using the provided {@link AwsLambdaFunctionInstrumenter}.
|
||||||
*/
|
*/
|
||||||
protected TracingRequestHandler(
|
protected TracingRequestHandler(
|
||||||
OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout, AwsLambdaTracer tracer) {
|
OpenTelemetrySdk openTelemetrySdk,
|
||||||
|
Duration flushTimeout,
|
||||||
|
AwsLambdaFunctionInstrumenter instrumenter) {
|
||||||
this.openTelemetrySdk = openTelemetrySdk;
|
this.openTelemetrySdk = openTelemetrySdk;
|
||||||
this.flushTimeoutNanos = flushTimeout.toNanos();
|
this.flushTimeoutNanos = flushTimeout.toNanos();
|
||||||
this.tracer = tracer;
|
this.instrumenter = instrumenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, String> getHeaders(I input) {
|
private Map<String, String> getHeaders(I input) {
|
||||||
|
Map<String, String> result = null;
|
||||||
if (input instanceof APIGatewayProxyRequestEvent) {
|
if (input instanceof APIGatewayProxyRequestEvent) {
|
||||||
APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) input;
|
APIGatewayProxyRequestEvent event = (APIGatewayProxyRequestEvent) input;
|
||||||
return event.getHeaders();
|
result = event.getHeaders();
|
||||||
}
|
}
|
||||||
return Collections.emptyMap();
|
return result == null ? Collections.emptyMap() : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final O handleRequest(I input, Context context) {
|
public final O handleRequest(I input, Context context) {
|
||||||
io.opentelemetry.context.Context otelContext =
|
AwsLambdaRequest request = AwsLambdaRequest.create(context, input, getHeaders(input));
|
||||||
tracer.startSpan(context, SpanKind.SERVER, input, getHeaders(input));
|
io.opentelemetry.context.Context parentContext = instrumenter.extract(request);
|
||||||
|
|
||||||
|
if (!instrumenter.shouldStart(parentContext, request)) {
|
||||||
|
return doHandleRequest(input, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
io.opentelemetry.context.Context otelContext = instrumenter.start(parentContext, request);
|
||||||
Throwable error = null;
|
Throwable error = null;
|
||||||
|
O output = null;
|
||||||
try (Scope ignored = otelContext.makeCurrent()) {
|
try (Scope ignored = otelContext.makeCurrent()) {
|
||||||
O output = doHandleRequest(input, context);
|
output = doHandleRequest(input, context);
|
||||||
tracer.onOutput(otelContext, output);
|
|
||||||
return output;
|
return output;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
error = t;
|
error = t;
|
||||||
throw t;
|
throw t;
|
||||||
} finally {
|
} finally {
|
||||||
if (error != null) {
|
instrumenter.end(otelContext, request, output, error);
|
||||||
tracer.endExceptionally(otelContext, error);
|
|
||||||
} else {
|
|
||||||
tracer.end(otelContext);
|
|
||||||
}
|
|
||||||
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
|
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,10 @@ package io.opentelemetry.instrumentation.awslambda.v1_0;
|
||||||
|
|
||||||
import com.amazonaws.services.lambda.runtime.Context;
|
import com.amazonaws.services.lambda.runtime.Context;
|
||||||
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
|
import com.amazonaws.services.lambda.runtime.RequestStreamHandler;
|
||||||
import io.opentelemetry.api.trace.SpanKind;
|
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.ApiGatewayProxyRequest;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaFunctionInstrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaFunctionInstrumenterFactory;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -26,7 +28,7 @@ public abstract class TracingRequestStreamHandler implements RequestStreamHandle
|
||||||
|
|
||||||
private final OpenTelemetrySdk openTelemetrySdk;
|
private final OpenTelemetrySdk openTelemetrySdk;
|
||||||
private final long flushTimeoutNanos;
|
private final long flushTimeoutNanos;
|
||||||
private final AwsLambdaTracer tracer;
|
private final AwsLambdaFunctionInstrumenter instrumenter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link TracingRequestStreamHandler} which traces using the provided {@link
|
* Creates a new {@link TracingRequestStreamHandler} which traces using the provided {@link
|
||||||
|
@ -42,19 +44,24 @@ public abstract class TracingRequestStreamHandler implements RequestStreamHandle
|
||||||
* invocation.
|
* invocation.
|
||||||
*/
|
*/
|
||||||
protected TracingRequestStreamHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
protected TracingRequestStreamHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
||||||
this(openTelemetrySdk, flushTimeout, new AwsLambdaTracer(openTelemetrySdk));
|
this(
|
||||||
|
openTelemetrySdk,
|
||||||
|
flushTimeout,
|
||||||
|
AwsLambdaFunctionInstrumenterFactory.createInstrumenter(openTelemetrySdk));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link TracingRequestStreamHandler} which flushes the provided {@link
|
* Creates a new {@link TracingRequestStreamHandler} which flushes the provided {@link
|
||||||
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
||||||
* invocation, and traces using the provided {@link AwsLambdaTracer}.
|
* invocation, and traces using the provided {@link AwsLambdaFunctionInstrumenter}.
|
||||||
*/
|
*/
|
||||||
protected TracingRequestStreamHandler(
|
protected TracingRequestStreamHandler(
|
||||||
OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout, AwsLambdaTracer tracer) {
|
OpenTelemetrySdk openTelemetrySdk,
|
||||||
|
Duration flushTimeout,
|
||||||
|
AwsLambdaFunctionInstrumenter instrumenter) {
|
||||||
this.openTelemetrySdk = openTelemetrySdk;
|
this.openTelemetrySdk = openTelemetrySdk;
|
||||||
this.flushTimeoutNanos = flushTimeout.toNanos();
|
this.flushTimeoutNanos = flushTimeout.toNanos();
|
||||||
this.tracer = tracer;
|
this.instrumenter = instrumenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,16 +69,23 @@ public abstract class TracingRequestStreamHandler implements RequestStreamHandle
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
|
||||||
ApiGatewayProxyRequest proxyRequest = ApiGatewayProxyRequest.forStream(input);
|
ApiGatewayProxyRequest proxyRequest = ApiGatewayProxyRequest.forStream(input);
|
||||||
io.opentelemetry.context.Context otelContext =
|
AwsLambdaRequest request =
|
||||||
tracer.startSpan(context, SpanKind.SERVER, input, proxyRequest.getHeaders());
|
AwsLambdaRequest.create(context, proxyRequest, proxyRequest.getHeaders());
|
||||||
|
io.opentelemetry.context.Context parentContext = instrumenter.extract(request);
|
||||||
|
|
||||||
|
if (!instrumenter.shouldStart(parentContext, request)) {
|
||||||
|
doHandleRequest(proxyRequest.freshStream(), output, context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
io.opentelemetry.context.Context otelContext = instrumenter.start(parentContext, request);
|
||||||
try (Scope ignored = otelContext.makeCurrent()) {
|
try (Scope ignored = otelContext.makeCurrent()) {
|
||||||
doHandleRequest(
|
doHandleRequest(
|
||||||
proxyRequest.freshStream(),
|
proxyRequest.freshStream(),
|
||||||
new OutputStreamWrapper(output, otelContext, openTelemetrySdk),
|
new OutputStreamWrapper(output, otelContext, request, openTelemetrySdk),
|
||||||
context);
|
context);
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
tracer.endExceptionally(otelContext, t);
|
instrumenter.end(otelContext, request, null, t);
|
||||||
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
|
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
|
||||||
throw t;
|
throw t;
|
||||||
}
|
}
|
||||||
|
@ -84,14 +98,17 @@ public abstract class TracingRequestStreamHandler implements RequestStreamHandle
|
||||||
|
|
||||||
private final OutputStream delegate;
|
private final OutputStream delegate;
|
||||||
private final io.opentelemetry.context.Context otelContext;
|
private final io.opentelemetry.context.Context otelContext;
|
||||||
|
private final AwsLambdaRequest request;
|
||||||
private final OpenTelemetrySdk openTelemetrySdk;
|
private final OpenTelemetrySdk openTelemetrySdk;
|
||||||
|
|
||||||
private OutputStreamWrapper(
|
private OutputStreamWrapper(
|
||||||
OutputStream delegate,
|
OutputStream delegate,
|
||||||
io.opentelemetry.context.Context otelContext,
|
io.opentelemetry.context.Context otelContext,
|
||||||
|
AwsLambdaRequest request,
|
||||||
OpenTelemetrySdk openTelemetrySdk) {
|
OpenTelemetrySdk openTelemetrySdk) {
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
this.otelContext = otelContext;
|
this.otelContext = otelContext;
|
||||||
|
this.request = request;
|
||||||
this.openTelemetrySdk = openTelemetrySdk;
|
this.openTelemetrySdk = openTelemetrySdk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +135,7 @@ public abstract class TracingRequestStreamHandler implements RequestStreamHandle
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
delegate.close();
|
delegate.close();
|
||||||
tracer.end(otelContext);
|
instrumenter.end(otelContext, request, null, null);
|
||||||
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
|
LambdaUtils.forceFlush(openTelemetrySdk, flushTimeoutNanos, TimeUnit.NANOSECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,15 @@ package io.opentelemetry.instrumentation.awslambda.v1_0;
|
||||||
import com.amazonaws.services.lambda.runtime.Context;
|
import com.amazonaws.services.lambda.runtime.Context;
|
||||||
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaFunctionInstrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaSqsInstrumenterFactory;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public abstract class TracingSqsEventHandler extends TracingRequestHandler<SQSEvent, Void> {
|
public abstract class TracingSqsEventHandler extends TracingRequestHandler<SQSEvent, Void> {
|
||||||
|
|
||||||
private final AwsLambdaMessageTracer tracer;
|
private final Instrumenter<SQSEvent, Void> instrumenter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link TracingSqsEventHandler} which traces using the provided {@link
|
* Creates a new {@link TracingSqsEventHandler} which traces using the provided {@link
|
||||||
|
@ -29,36 +32,39 @@ public abstract class TracingSqsEventHandler extends TracingRequestHandler<SQSEv
|
||||||
* invocation.
|
* invocation.
|
||||||
*/
|
*/
|
||||||
protected TracingSqsEventHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
protected TracingSqsEventHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
||||||
this(openTelemetrySdk, flushTimeout, new AwsLambdaMessageTracer(openTelemetrySdk));
|
this(
|
||||||
|
openTelemetrySdk, flushTimeout, AwsLambdaSqsInstrumenterFactory.forEvent(openTelemetrySdk));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link TracingSqsEventHandler} which flushes the provided {@link
|
* Creates a new {@link TracingSqsEventHandler} which flushes the provided {@link
|
||||||
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
||||||
* invocation, and traces using the provided {@link AwsLambdaTracer}.
|
* invocation, and traces using the provided {@link AwsLambdaFunctionInstrumenter}.
|
||||||
*/
|
*/
|
||||||
protected TracingSqsEventHandler(
|
protected TracingSqsEventHandler(
|
||||||
OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout, AwsLambdaMessageTracer tracer) {
|
OpenTelemetrySdk openTelemetrySdk,
|
||||||
|
Duration flushTimeout,
|
||||||
|
Instrumenter<SQSEvent, Void> instrumenter) {
|
||||||
super(openTelemetrySdk, flushTimeout);
|
super(openTelemetrySdk, flushTimeout);
|
||||||
this.tracer = tracer;
|
this.instrumenter = instrumenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Void doHandleRequest(SQSEvent event, Context context) {
|
public Void doHandleRequest(SQSEvent event, Context context) {
|
||||||
io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.current();
|
io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.current();
|
||||||
io.opentelemetry.context.Context otelContext = tracer.startSpan(parentContext, event);
|
if (instrumenter.shouldStart(parentContext, event)) {
|
||||||
Throwable error = null;
|
io.opentelemetry.context.Context otelContext = instrumenter.start(parentContext, event);
|
||||||
try (Scope ignored = otelContext.makeCurrent()) {
|
Throwable error = null;
|
||||||
handleEvent(event, context);
|
try (Scope ignored = otelContext.makeCurrent()) {
|
||||||
} catch (Throwable t) {
|
handleEvent(event, context);
|
||||||
error = t;
|
} catch (Throwable t) {
|
||||||
throw t;
|
error = t;
|
||||||
} finally {
|
throw t;
|
||||||
if (error != null) {
|
} finally {
|
||||||
tracer.endExceptionally(otelContext, error);
|
instrumenter.end(otelContext, event, null, error);
|
||||||
} else {
|
|
||||||
tracer.end(otelContext);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
handleEvent(event, context);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -68,9 +74,4 @@ public abstract class TracingSqsEventHandler extends TracingRequestHandler<SQSEv
|
||||||
* processing of incoming SQS messages.
|
* processing of incoming SQS messages.
|
||||||
*/
|
*/
|
||||||
protected abstract void handleEvent(SQSEvent event, Context context);
|
protected abstract void handleEvent(SQSEvent event, Context context);
|
||||||
|
|
||||||
// We use in SQS message handler too.
|
|
||||||
AwsLambdaMessageTracer getTracer() {
|
|
||||||
return tracer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,21 @@ import com.amazonaws.services.lambda.runtime.Context;
|
||||||
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
||||||
import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
|
||||||
import io.opentelemetry.context.Scope;
|
import io.opentelemetry.context.Scope;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.internal.AwsLambdaSqsInstrumenterFactory;
|
||||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
public abstract class TracingSqsMessageHandler extends TracingSqsEventHandler {
|
public abstract class TracingSqsMessageHandler extends TracingSqsEventHandler {
|
||||||
|
|
||||||
|
private final Instrumenter<SQSMessage, Void> messageInstrumenter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link TracingSqsMessageHandler} which traces using the provided {@link
|
* Creates a new {@link TracingSqsMessageHandler} which traces using the provided {@link
|
||||||
* OpenTelemetrySdk} and has a timeout of 1s when flushing at the end of an invocation.
|
* OpenTelemetrySdk} and has a timeout of 1s when flushing at the end of an invocation.
|
||||||
*/
|
*/
|
||||||
protected TracingSqsMessageHandler(OpenTelemetrySdk openTelemetrySdk) {
|
protected TracingSqsMessageHandler(OpenTelemetrySdk openTelemetrySdk) {
|
||||||
super(openTelemetrySdk);
|
this(openTelemetrySdk, DEFAULT_FLUSH_TIMEOUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,36 +32,60 @@ public abstract class TracingSqsMessageHandler extends TracingSqsEventHandler {
|
||||||
* invocation.
|
* invocation.
|
||||||
*/
|
*/
|
||||||
protected TracingSqsMessageHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
protected TracingSqsMessageHandler(OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout) {
|
||||||
super(openTelemetrySdk, flushTimeout);
|
this(
|
||||||
|
openTelemetrySdk, flushTimeout, AwsLambdaSqsInstrumenterFactory.forEvent(openTelemetrySdk));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link TracingSqsMessageHandler} which flushes the provided {@link
|
* Creates a new {@link TracingSqsMessageHandler} which flushes the provided {@link
|
||||||
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
||||||
* invocation, and traces using the provided {@link AwsLambdaTracer}.
|
* invocation, and instruments {@link SQSEvent} using the provided {@code Instrumenter<SQSEvent,
|
||||||
|
* Void>}.
|
||||||
*/
|
*/
|
||||||
protected TracingSqsMessageHandler(
|
protected TracingSqsMessageHandler(
|
||||||
OpenTelemetrySdk openTelemetrySdk, Duration flushTimeout, AwsLambdaMessageTracer tracer) {
|
OpenTelemetrySdk openTelemetrySdk,
|
||||||
super(openTelemetrySdk, flushTimeout, tracer);
|
Duration flushTimeout,
|
||||||
|
Instrumenter<SQSEvent, Void> eventInstrumenter) {
|
||||||
|
this(
|
||||||
|
openTelemetrySdk,
|
||||||
|
flushTimeout,
|
||||||
|
eventInstrumenter,
|
||||||
|
AwsLambdaSqsInstrumenterFactory.forMessage(openTelemetrySdk));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link TracingSqsMessageHandler} which flushes the provided {@link
|
||||||
|
* OpenTelemetrySdk}, has a timeout of {@code flushTimeout} when flushing at the end of an
|
||||||
|
* invocation, and traces using the provided {@code Instrumenter<SQSEvent, Void>} and {@code
|
||||||
|
* Instrumenter<SQSMessage, Void>}.
|
||||||
|
*/
|
||||||
|
protected TracingSqsMessageHandler(
|
||||||
|
OpenTelemetrySdk openTelemetrySdk,
|
||||||
|
Duration flushTimeout,
|
||||||
|
Instrumenter<SQSEvent, Void> eventInstrumenter,
|
||||||
|
Instrumenter<SQSMessage, Void> messageInstrumenter) {
|
||||||
|
super(openTelemetrySdk, flushTimeout, eventInstrumenter);
|
||||||
|
this.messageInstrumenter = messageInstrumenter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected final void handleEvent(SQSEvent event, Context context) {
|
protected final void handleEvent(SQSEvent event, Context context) {
|
||||||
io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.current();
|
io.opentelemetry.context.Context parentContext = io.opentelemetry.context.Context.current();
|
||||||
for (SQSMessage message : event.getRecords()) {
|
for (SQSMessage message : event.getRecords()) {
|
||||||
io.opentelemetry.context.Context otelContext = getTracer().startSpan(parentContext, message);
|
if (messageInstrumenter.shouldStart(parentContext, message)) {
|
||||||
Throwable error = null;
|
io.opentelemetry.context.Context otelContext =
|
||||||
try (Scope ignored = otelContext.makeCurrent()) {
|
messageInstrumenter.start(parentContext, message);
|
||||||
handleMessage(message, context);
|
Throwable error = null;
|
||||||
} catch (Throwable t) {
|
try (Scope ignored = otelContext.makeCurrent()) {
|
||||||
error = t;
|
handleMessage(message, context);
|
||||||
throw t;
|
} catch (Throwable t) {
|
||||||
} finally {
|
error = t;
|
||||||
if (error != null) {
|
throw t;
|
||||||
getTracer().endExceptionally(otelContext, error);
|
} finally {
|
||||||
} else {
|
messageInstrumenter.end(otelContext, message, null, error);
|
||||||
getTracer().end(otelContext);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
handleMessage(message, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import static io.opentelemetry.instrumentation.awslambda.v1_0.internal.MapUtils.emptyIfNull;
|
||||||
|
import static io.opentelemetry.instrumentation.awslambda.v1_0.internal.MapUtils.lowercaseMap;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_TRIGGER;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_METHOD;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_STATUS_CODE;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_URL;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.HTTP_USER_AGENT;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
|
||||||
|
import io.opentelemetry.api.common.AttributesBuilder;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaRequest;
|
||||||
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
final class ApiGatewayProxyAttributesExtractor
|
||||||
|
implements AttributesExtractor<AwsLambdaRequest, Object> {
|
||||||
|
@Override
|
||||||
|
public void onStart(AttributesBuilder attributes, AwsLambdaRequest request) {
|
||||||
|
if (request.getInput() instanceof APIGatewayProxyRequestEvent) {
|
||||||
|
set(attributes, FAAS_TRIGGER, SemanticAttributes.FaasTriggerValues.HTTP);
|
||||||
|
onRequest(attributes, (APIGatewayProxyRequestEvent) request.getInput());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onRequest(AttributesBuilder attributes, APIGatewayProxyRequestEvent request) {
|
||||||
|
set(attributes, HTTP_METHOD, request.getHttpMethod());
|
||||||
|
|
||||||
|
Map<String, String> headers = lowercaseMap(request.getHeaders());
|
||||||
|
set(attributes, HTTP_USER_AGENT, headers.get("user-agent"));
|
||||||
|
set(attributes, HTTP_URL, getHttpUrl(request, headers));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getHttpUrl(
|
||||||
|
APIGatewayProxyRequestEvent request, Map<String, String> headers) {
|
||||||
|
StringBuilder str = new StringBuilder();
|
||||||
|
|
||||||
|
String scheme = headers.get("x-forwarded-proto");
|
||||||
|
if (scheme != null) {
|
||||||
|
str.append(scheme).append("://");
|
||||||
|
}
|
||||||
|
String host = headers.get("host");
|
||||||
|
if (host != null) {
|
||||||
|
str.append(host);
|
||||||
|
}
|
||||||
|
String path = request.getPath();
|
||||||
|
if (path != null) {
|
||||||
|
str.append(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
boolean first = true;
|
||||||
|
for (Map.Entry<String, String> entry :
|
||||||
|
emptyIfNull(request.getQueryStringParameters()).entrySet()) {
|
||||||
|
String key = URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name());
|
||||||
|
String value = URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name());
|
||||||
|
str.append(first ? '?' : '&').append(key).append('=').append(value);
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
} catch (UnsupportedEncodingException ignored) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
return str.length() == 0 ? null : str.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(
|
||||||
|
AttributesBuilder attributes,
|
||||||
|
AwsLambdaRequest request,
|
||||||
|
@Nullable Object response,
|
||||||
|
@Nullable Throwable error) {
|
||||||
|
if (response instanceof APIGatewayProxyResponseEvent) {
|
||||||
|
Integer statusCode = ((APIGatewayProxyResponseEvent) response).getStatusCode();
|
||||||
|
if (statusCode != null) {
|
||||||
|
attributes.put(HTTP_STATUS_CODE, statusCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiGatewayProxyAttributesExtractor() {}
|
||||||
|
}
|
|
@ -3,9 +3,9 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
import static io.opentelemetry.instrumentation.awslambda.v1_0.HeadersFactory.ofStream;
|
import static io.opentelemetry.instrumentation.awslambda.v1_0.internal.HeadersFactory.ofStream;
|
||||||
|
|
||||||
import io.opentelemetry.api.GlobalOpenTelemetry;
|
import io.opentelemetry.api.GlobalOpenTelemetry;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
@ -17,7 +17,7 @@ import java.util.Map;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
abstract class ApiGatewayProxyRequest {
|
public abstract class ApiGatewayProxyRequest {
|
||||||
|
|
||||||
// TODO(anuraaga): We should create a RequestFactory type of class instead of evaluating this
|
// TODO(anuraaga): We should create a RequestFactory type of class instead of evaluating this
|
||||||
// for every request.
|
// for every request.
|
||||||
|
@ -34,7 +34,7 @@ abstract class ApiGatewayProxyRequest {
|
||||||
fields.iterator().next());
|
fields.iterator().next());
|
||||||
}
|
}
|
||||||
|
|
||||||
static ApiGatewayProxyRequest forStream(InputStream source) throws IOException {
|
public static ApiGatewayProxyRequest forStream(InputStream source) throws IOException {
|
||||||
|
|
||||||
if (noHttpPropagationNeeded()) {
|
if (noHttpPropagationNeeded()) {
|
||||||
return new NoopRequest(source);
|
return new NoopRequest(source);
|
||||||
|
@ -48,12 +48,12 @@ abstract class ApiGatewayProxyRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
Map<String, String> getHeaders() throws IOException {
|
public Map<String, String> getHeaders() throws IOException {
|
||||||
Map<String, String> headers = ofStream(freshStream());
|
Map<String, String> headers = ofStream(freshStream());
|
||||||
return (headers == null ? Collections.emptyMap() : headers);
|
return (headers == null ? Collections.emptyMap() : headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract InputStream freshStream() throws IOException;
|
public abstract InputStream freshStream() throws IOException;
|
||||||
|
|
||||||
private static class NoopRequest extends ApiGatewayProxyRequest {
|
private static class NoopRequest extends ApiGatewayProxyRequest {
|
||||||
|
|
||||||
|
@ -64,12 +64,12 @@ abstract class ApiGatewayProxyRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
InputStream freshStream() {
|
public InputStream freshStream() {
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Map<String, String> getHeaders() {
|
public Map<String, String> getHeaders() {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ abstract class ApiGatewayProxyRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
InputStream freshStream() throws IOException {
|
public InputStream freshStream() throws IOException {
|
||||||
|
|
||||||
inputStream.reset();
|
inputStream.reset();
|
||||||
inputStream.mark(Integer.MAX_VALUE);
|
inputStream.mark(Integer.MAX_VALUE);
|
||||||
|
@ -101,7 +101,7 @@ abstract class ApiGatewayProxyRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
InputStream freshStream() {
|
public InputStream freshStream() {
|
||||||
return new ByteArrayInputStream(data);
|
return new ByteArrayInputStream(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.CLOUD_ACCOUNT_ID;
|
||||||
|
import static io.opentelemetry.semconv.resource.attributes.ResourceAttributes.FAAS_ID;
|
||||||
|
import static io.opentelemetry.semconv.trace.attributes.SemanticAttributes.FAAS_EXECUTION;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.Context;
|
||||||
|
import io.opentelemetry.api.common.AttributesBuilder;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.AttributesExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaRequest;
|
||||||
|
import java.lang.invoke.MethodHandle;
|
||||||
|
import java.lang.invoke.MethodHandles;
|
||||||
|
import java.lang.invoke.MethodType;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
class AwsLambdaFunctionAttributesExtractor
|
||||||
|
implements AttributesExtractor<AwsLambdaRequest, Object> {
|
||||||
|
|
||||||
|
@Nullable private static final MethodHandle GET_FUNCTION_ARN;
|
||||||
|
|
||||||
|
static {
|
||||||
|
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
|
||||||
|
MethodHandle getFunctionArn;
|
||||||
|
try {
|
||||||
|
getFunctionArn =
|
||||||
|
lookup.findVirtual(
|
||||||
|
Context.class, "getInvokedFunctionArn", MethodType.methodType(String.class));
|
||||||
|
} catch (NoSuchMethodException | IllegalAccessException e) {
|
||||||
|
getFunctionArn = null;
|
||||||
|
}
|
||||||
|
GET_FUNCTION_ARN = getFunctionArn;
|
||||||
|
}
|
||||||
|
|
||||||
|
// cached accountId value
|
||||||
|
private volatile String accountId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart(AttributesBuilder attributes, AwsLambdaRequest request) {
|
||||||
|
Context context = request.getAwsContext();
|
||||||
|
set(attributes, FAAS_EXECUTION, context.getAwsRequestId());
|
||||||
|
set(attributes, FAAS_ID, getFunctionArn(context));
|
||||||
|
set(attributes, CLOUD_ACCOUNT_ID, getAccountId(getFunctionArn(context)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(
|
||||||
|
AttributesBuilder attributes,
|
||||||
|
AwsLambdaRequest request,
|
||||||
|
@Nullable Object response,
|
||||||
|
@Nullable Throwable error) {}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static String getFunctionArn(Context context) {
|
||||||
|
if (GET_FUNCTION_ARN == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return (String) GET_FUNCTION_ARN.invoke(context);
|
||||||
|
} catch (Throwable throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String getAccountId(@Nullable String arn) {
|
||||||
|
if (arn == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (accountId == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (accountId == null) {
|
||||||
|
String[] arnParts = arn.split(":");
|
||||||
|
if (arnParts.length >= 5) {
|
||||||
|
accountId = arnParts[4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.context.propagation.TextMapGetter;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.api.internal.ContextPropagationDebug;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaRequest;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
|
|
||||||
|
public class AwsLambdaFunctionInstrumenter {
|
||||||
|
|
||||||
|
private final OpenTelemetry openTelemetry;
|
||||||
|
final Instrumenter<AwsLambdaRequest, Object> instrumenter;
|
||||||
|
|
||||||
|
AwsLambdaFunctionInstrumenter(
|
||||||
|
OpenTelemetry openTelemetry, Instrumenter<AwsLambdaRequest, Object> instrumenter) {
|
||||||
|
this.openTelemetry = openTelemetry;
|
||||||
|
this.instrumenter = instrumenter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean shouldStart(Context parentContext, AwsLambdaRequest input) {
|
||||||
|
return instrumenter.shouldStart(parentContext, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context start(Context parentContext, AwsLambdaRequest input) {
|
||||||
|
return instrumenter.start(parentContext, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void end(
|
||||||
|
Context context,
|
||||||
|
AwsLambdaRequest input,
|
||||||
|
@Nullable Object response,
|
||||||
|
@Nullable Throwable error) {
|
||||||
|
instrumenter.end(context, input, response, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context extract(AwsLambdaRequest input) {
|
||||||
|
return ParentContextExtractor.extract(input.getHeaders(), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Context extract(Map<String, String> headers, TextMapGetter<Map<String, String>> getter) {
|
||||||
|
ContextPropagationDebug.debugContextLeakIfEnabled();
|
||||||
|
|
||||||
|
return openTelemetry
|
||||||
|
.getPropagators()
|
||||||
|
.getTextMapPropagator()
|
||||||
|
.extract(Context.root(), headers, getter);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
|
||||||
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
|
||||||
|
import io.opentelemetry.instrumentation.awslambda.v1_0.AwsLambdaRequest;
|
||||||
|
|
||||||
|
public class AwsLambdaFunctionInstrumenterFactory {
|
||||||
|
|
||||||
|
public static AwsLambdaFunctionInstrumenter createInstrumenter(OpenTelemetry openTelemetry) {
|
||||||
|
return new AwsLambdaFunctionInstrumenter(
|
||||||
|
openTelemetry,
|
||||||
|
Instrumenter.newBuilder(
|
||||||
|
openTelemetry,
|
||||||
|
"io.opentelemetry.aws-lambda-1.0",
|
||||||
|
AwsLambdaFunctionInstrumenterFactory::spanName)
|
||||||
|
.addAttributesExtractors(
|
||||||
|
new AwsLambdaFunctionAttributesExtractor(),
|
||||||
|
new ApiGatewayProxyAttributesExtractor())
|
||||||
|
.newInstrumenter(SpanKindExtractor.alwaysServer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String spanName(AwsLambdaRequest input) {
|
||||||
|
String name = null;
|
||||||
|
if (input.getInput() instanceof APIGatewayProxyRequestEvent) {
|
||||||
|
name = ((APIGatewayProxyRequestEvent) input.getInput()).getResource();
|
||||||
|
}
|
||||||
|
return name == null ? input.getAwsContext().getFunctionName() : name;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
|
||||||
|
import io.opentelemetry.api.OpenTelemetry;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
|
||||||
|
|
||||||
|
public class AwsLambdaSqsInstrumenterFactory {
|
||||||
|
|
||||||
|
public static Instrumenter<SQSEvent, Void> forEvent(OpenTelemetry openTelemetry) {
|
||||||
|
return Instrumenter.<SQSEvent, Void>newBuilder(
|
||||||
|
openTelemetry,
|
||||||
|
"io.opentelemetry.aws-lambda-1.0",
|
||||||
|
AwsLambdaSqsInstrumenterFactory::spanName)
|
||||||
|
.addAttributesExtractors(new SqsEventAttributesExtractor())
|
||||||
|
.addSpanLinksExtractor(new SqsEventSpanLinksExtractor())
|
||||||
|
.newInstrumenter(SpanKindExtractor.alwaysConsumer());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Instrumenter<SQSMessage, Void> forMessage(OpenTelemetry openTelemetry) {
|
||||||
|
return Instrumenter.<SQSMessage, Void>newBuilder(
|
||||||
|
openTelemetry,
|
||||||
|
"io.opentelemetry.aws-lambda-1.0",
|
||||||
|
message -> message.getEventSource() + " process")
|
||||||
|
.addAttributesExtractors(new SqsMessageAttributesExtractor())
|
||||||
|
.addSpanLinksExtractor(new SqsMessageSpanLinksExtractor())
|
||||||
|
.newInstrumenter(SpanKindExtractor.alwaysConsumer());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String spanName(SQSEvent event) {
|
||||||
|
String source = "multiple_sources";
|
||||||
|
if (!event.getRecords().isEmpty()) {
|
||||||
|
String messageSource = event.getRecords().get(0).getEventSource();
|
||||||
|
for (int i = 1; i < event.getRecords().size(); i++) {
|
||||||
|
SQSMessage message = event.getRecords().get(i);
|
||||||
|
if (!message.getEventSource().equals(messageSource)) {
|
||||||
|
messageSource = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (messageSource != null) {
|
||||||
|
source = messageSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source + " process";
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonFactory;
|
import com.fasterxml.jackson.core.JsonFactory;
|
||||||
import com.fasterxml.jackson.core.JsonParser;
|
import com.fasterxml.jackson.core.JsonParser;
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
|
@ -3,25 +3,24 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
import static io.opentelemetry.instrumentation.awslambda.v1_0.MapUtils.lowercaseMap;
|
import static io.opentelemetry.instrumentation.awslambda.v1_0.internal.MapUtils.lowercaseMap;
|
||||||
|
|
||||||
import io.opentelemetry.api.trace.Span;
|
import io.opentelemetry.api.trace.Span;
|
||||||
import io.opentelemetry.api.trace.SpanContext;
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
import io.opentelemetry.context.Context;
|
import io.opentelemetry.context.Context;
|
||||||
import io.opentelemetry.context.propagation.TextMapGetter;
|
import io.opentelemetry.context.propagation.TextMapGetter;
|
||||||
import io.opentelemetry.extension.aws.AwsXrayPropagator;
|
import io.opentelemetry.extension.aws.AwsXrayPropagator;
|
||||||
import io.opentelemetry.instrumentation.api.tracer.BaseTracer;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class ParentContextExtractor {
|
final class ParentContextExtractor {
|
||||||
|
|
||||||
private static final String AWS_TRACE_HEADER_ENV_KEY = "_X_AMZN_TRACE_ID";
|
private static final String AWS_TRACE_HEADER_ENV_KEY = "_X_AMZN_TRACE_ID";
|
||||||
|
|
||||||
static Context extract(Map<String, String> headers, BaseTracer tracer) {
|
static Context extract(Map<String, String> headers, AwsLambdaFunctionInstrumenter instrumenter) {
|
||||||
Context parentContext = null;
|
Context parentContext = null;
|
||||||
String parentTraceHeader = System.getenv(AWS_TRACE_HEADER_ENV_KEY);
|
String parentTraceHeader = System.getenv(AWS_TRACE_HEADER_ENV_KEY);
|
||||||
if (parentTraceHeader != null) {
|
if (parentTraceHeader != null) {
|
||||||
|
@ -29,7 +28,7 @@ public final class ParentContextExtractor {
|
||||||
}
|
}
|
||||||
if (!isValidAndSampled(parentContext)) {
|
if (!isValidAndSampled(parentContext)) {
|
||||||
// try http
|
// try http
|
||||||
parentContext = fromHttpHeaders(headers, tracer);
|
parentContext = fromHttpHeaders(headers, instrumenter);
|
||||||
}
|
}
|
||||||
return parentContext;
|
return parentContext;
|
||||||
}
|
}
|
||||||
|
@ -43,8 +42,9 @@ public final class ParentContextExtractor {
|
||||||
return (parentSpanContext.isValid() && parentSpanContext.isSampled());
|
return (parentSpanContext.isValid() && parentSpanContext.isSampled());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Context fromHttpHeaders(Map<String, String> headers, BaseTracer tracer) {
|
private static Context fromHttpHeaders(
|
||||||
return tracer.extract(lowercaseMap(headers), MapGetter.INSTANCE);
|
Map<String, String> headers, AwsLambdaFunctionInstrumenter instrumenter) {
|
||||||
|
return instrumenter.extract(lowercaseMap(headers), MapGetter.INSTANCE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// lower-case map getter used for extraction
|
// lower-case map getter used for extraction
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class SqsEventAttributesExtractor implements AttributesExtractor<SQSEvent, Void> {
|
||||||
|
@Override
|
||||||
|
public void onStart(AttributesBuilder attributes, SQSEvent event) {
|
||||||
|
attributes.put(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS");
|
||||||
|
attributes.put(SemanticAttributes.MESSAGING_OPERATION, "process");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(
|
||||||
|
AttributesBuilder attributes,
|
||||||
|
SQSEvent event,
|
||||||
|
@Nullable Void unused,
|
||||||
|
@Nullable Throwable error) {}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor;
|
||||||
|
|
||||||
|
class SqsEventSpanLinksExtractor implements SpanLinksExtractor<SQSEvent> {
|
||||||
|
private static final SqsMessageSpanLinksExtractor messageSpanLinksExtractor =
|
||||||
|
new SqsMessageSpanLinksExtractor();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void extract(SpanLinksBuilder spanLinks, Context parentContext, SQSEvent event) {
|
||||||
|
for (SQSEvent.SQSMessage message : event.getRecords()) {
|
||||||
|
messageSpanLinksExtractor.extract(spanLinks, parentContext, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
|
||||||
|
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;
|
||||||
|
|
||||||
|
class SqsMessageAttributesExtractor implements AttributesExtractor<SQSMessage, Void> {
|
||||||
|
@Override
|
||||||
|
public void onStart(AttributesBuilder attributes, SQSMessage message) {
|
||||||
|
attributes.put(SemanticAttributes.MESSAGING_SYSTEM, "AmazonSQS");
|
||||||
|
attributes.put(SemanticAttributes.MESSAGING_OPERATION, "process");
|
||||||
|
attributes.put(SemanticAttributes.MESSAGING_MESSAGE_ID, message.getMessageId());
|
||||||
|
attributes.put(SemanticAttributes.MESSAGING_DESTINATION, message.getEventSource());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnd(
|
||||||
|
AttributesBuilder attributes,
|
||||||
|
SQSMessage message,
|
||||||
|
@Nullable Void unused,
|
||||||
|
@Nullable Throwable error) {}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright The OpenTelemetry Authors
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
|
import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
|
||||||
|
import io.opentelemetry.api.trace.Span;
|
||||||
|
import io.opentelemetry.api.trace.SpanContext;
|
||||||
|
import io.opentelemetry.context.Context;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksBuilder;
|
||||||
|
import io.opentelemetry.instrumentation.api.instrumenter.SpanLinksExtractor;
|
||||||
|
|
||||||
|
class SqsMessageSpanLinksExtractor implements SpanLinksExtractor<SQSMessage> {
|
||||||
|
private static final String AWS_TRACE_HEADER_SQS_ATTRIBUTE_KEY = "AWSTraceHeader";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void extract(SpanLinksBuilder spanLinks, Context parentContext, SQSMessage message) {
|
||||||
|
String parentHeader = message.getAttributes().get(AWS_TRACE_HEADER_SQS_ATTRIBUTE_KEY);
|
||||||
|
if (parentHeader != null) {
|
||||||
|
SpanContext parentCtx =
|
||||||
|
Span.fromContext(ParentContextExtractor.fromXrayHeader(parentHeader)).getSpanContext();
|
||||||
|
if (parentCtx.isValid()) {
|
||||||
|
spanLinks.addLink(parentCtx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.data.MapEntry.entry;
|
import static org.assertj.core.data.MapEntry.entry;
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: Apache-2.0
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.opentelemetry.instrumentation.awslambda.v1_0;
|
package io.opentelemetry.instrumentation.awslambda.v1_0.internal;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
@ -30,7 +30,8 @@ public class ParentContextExtractorTest {
|
||||||
private static final OpenTelemetry OTEL =
|
private static final OpenTelemetry OTEL =
|
||||||
OpenTelemetry.propagating(ContextPropagators.create(B3Propagator.injectingSingleHeader()));
|
OpenTelemetry.propagating(ContextPropagators.create(B3Propagator.injectingSingleHeader()));
|
||||||
|
|
||||||
private static final AwsLambdaTracer TRACER = new AwsLambdaTracer(OTEL);
|
private static final AwsLambdaFunctionInstrumenter INSTRUMENTER =
|
||||||
|
AwsLambdaFunctionInstrumenterFactory.createInstrumenter(OTEL);
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldUseHttpIfAwsParentNotSampled() {
|
public void shouldUseHttpIfAwsParentNotSampled() {
|
||||||
|
@ -48,7 +49,7 @@ public class ParentContextExtractorTest {
|
||||||
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=0");
|
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=0");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Context context = ParentContextExtractor.extract(headers, TRACER);
|
Context context = ParentContextExtractor.extract(headers, INSTRUMENTER);
|
||||||
// then
|
// then
|
||||||
Span span = Span.fromContext(context);
|
Span span = Span.fromContext(context);
|
||||||
SpanContext spanContext = span.getSpanContext();
|
SpanContext spanContext = span.getSpanContext();
|
||||||
|
@ -74,7 +75,7 @@ public class ParentContextExtractorTest {
|
||||||
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=1");
|
"Root=1-8a3c60f7-d188f8fa79d48a391a778fa6;Parent=0000000000000456;Sampled=1");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Context context = ParentContextExtractor.extract(headers, TRACER);
|
Context context = ParentContextExtractor.extract(headers, INSTRUMENTER);
|
||||||
// then
|
// then
|
||||||
Span span = Span.fromContext(context);
|
Span span = Span.fromContext(context);
|
||||||
SpanContext spanContext = span.getSpanContext();
|
SpanContext spanContext = span.getSpanContext();
|
||||||
|
@ -97,7 +98,7 @@ public class ParentContextExtractorTest {
|
||||||
"true");
|
"true");
|
||||||
|
|
||||||
// when
|
// when
|
||||||
Context context = ParentContextExtractor.extract(headers, TRACER);
|
Context context = ParentContextExtractor.extract(headers, INSTRUMENTER);
|
||||||
// then
|
// then
|
||||||
Span span = Span.fromContext(context);
|
Span span = Span.fromContext(context);
|
||||||
SpanContext spanContext = span.getSpanContext();
|
SpanContext spanContext = span.getSpanContext();
|
Loading…
Reference in New Issue