Skip to content

Commit

Permalink
add config.decodeAuthorizerContext and httpapi no zero-length span
Browse files Browse the repository at this point in the history
  • Loading branch information
joeyzhao2018 committed Nov 9, 2022
1 parent 73a9132 commit 79668df
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 82 deletions.
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const mergeXrayTracesEnvVar = "DD_MERGE_XRAY_TRACES";
export const traceExtractorEnvVar = "DD_TRACE_EXTRACTOR";
export const defaultSiteURL = "datadoghq.com";
export const encodeAuthorizerContextEnvVar = "DD_ENCODE_AUTHORIZER_CONTEXT";
export const decodeAuthorizerContextEnvVar = "DD_DECODE_AUTHORIZER_CONTEXT";

interface GlobalConfig {
/**
Expand Down Expand Up @@ -66,6 +67,7 @@ export const defaultConfig: Config = {
createInferredSpan: true,
debugLogging: false,
encodeAuthorizerContext: true,
decodeAuthorizerContext: true,
enhancedMetrics: true,
forceWrap: false,
injectLogContext: true,
Expand Down Expand Up @@ -280,6 +282,11 @@ function getConfig(userConfig?: Partial<Config>): Config {
config.encodeAuthorizerContext = result === "true";
}

if (userConfig === undefined || userConfig.decodeAuthorizerContext === undefined) {
const result = getEnvValue(decodeAuthorizerContextEnvVar, "true").toLowerCase();
config.decodeAuthorizerContext = result === "true";
}

return config;
}

Expand Down
32 changes: 17 additions & 15 deletions src/trace/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ import {
xrayTraceEnvVar,
} from "./constants";
import { TraceExtractor } from "./listener";
import { eventTypes, parseEventSource, parseEventSourceSubType, eventSubTypes } from "./trigger";
import { parseEventSourceSubType, eventSubTypes } from "./trigger";
import { authorizingRequestIdHeader } from "./constants";
import { datadog } from "../index";

export interface XRayTraceHeader {
traceID: string;
Expand Down Expand Up @@ -59,6 +58,7 @@ export function extractTraceContext(
event: any,
context: Context,
extractor?: TraceExtractor,
decodeAuthorizerContext: boolean = true,
): TraceContext | undefined {
let trace;

Expand All @@ -74,7 +74,7 @@ export function extractTraceContext(
}

if (!trace) {
trace = readTraceFromEvent(event);
trace = readTraceFromEvent(event, decodeAuthorizerContext);
}

if (!trace) {
Expand Down Expand Up @@ -202,7 +202,7 @@ export function sendXraySubsegment(segment: string) {

export function readTraceFromAppSyncEvent(event: any): TraceContext | undefined {
event.headers = event.request.headers;
return readTraceFromHTTPEvent(event);
return readTraceFromHTTPEvent(event, false);
}

export function readTraceFromSQSEvent(event: SQSEvent): TraceContext | undefined {
Expand Down Expand Up @@ -365,16 +365,18 @@ export function getInjectedAuthorizerData(event: any, eventSourceSubType: eventS
}
}

export function readTraceFromHTTPEvent(event: any): TraceContext | undefined {
// need to set the trace context if using authorizer lambda in authorizing (non-cached) cases
try {
const eventSourceSubType: eventSubTypes = parseEventSourceSubType(event);
const injectedAuthorizerData = getInjectedAuthorizerData(event, eventSourceSubType);
if (injectedAuthorizerData !== null) {
return exportTraceData(injectedAuthorizerData);
export function readTraceFromHTTPEvent(event: any, decodeAuthorizerContext: boolean = true): TraceContext | undefined {
if (decodeAuthorizerContext) {
// need to set the trace context if using authorizer lambda in authorizing (non-cached) cases
try {
const eventSourceSubType: eventSubTypes = parseEventSourceSubType(event);
const injectedAuthorizerData = getInjectedAuthorizerData(event, eventSourceSubType);
if (injectedAuthorizerData !== null) {
return exportTraceData(injectedAuthorizerData);
}
} catch (error) {
logDebug(`unable to extract trace context from authorizer event.`, { error });
}
} catch (error) {
logDebug(`unable to extract trace context from authorizer event.`, { error });
}

const headers = event.headers;
Expand All @@ -390,13 +392,13 @@ export function readTraceFromHTTPEvent(event: any): TraceContext | undefined {
return trace;
}

export function readTraceFromEvent(event: any): TraceContext | undefined {
export function readTraceFromEvent(event: any, decodeAuthorizerContext: boolean = true): TraceContext | undefined {
if (!event || typeof event !== "object") {
return;
}

if (event.headers !== null && typeof event.headers === "object") {
return readTraceFromHTTPEvent(event);
return readTraceFromHTTPEvent(event, decodeAuthorizerContext);
}

if (isSNSEvent(event)) {
Expand Down
1 change: 1 addition & 0 deletions src/trace/listener.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ describe("TraceListener", () => {
captureLambdaPayload: false,
createInferredSpan: true,
encodeAuthorizerContext: true,
decodeAuthorizerContext: true,
mergeDatadogXrayTraces: false,
injectLogContext: false,
};
Expand Down
18 changes: 16 additions & 2 deletions src/trace/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export interface TraceConfig {
* Whether to encode trace context in authorizer metadata
*/
encodeAuthorizerContext: boolean;
/**
* Whether to decode trace context in authorizer metadata
*/
decodeAuthorizerContext: boolean;
/**
* Whether to automatically patch console.log with Datadog's tracing ids.
*/
Expand Down Expand Up @@ -90,7 +94,12 @@ export class TraceListener {
} else {
logDebug("Not patching HTTP libraries", { autoPatchHTTP: this.config.autoPatchHTTP, tracerInitialized });
}
const rootTraceHeaders = this.contextService.extractHeadersFromContext(event, context, this.config.traceExtractor);
const rootTraceHeaders = this.contextService.extractHeadersFromContext(
event,
context,
this.config.traceExtractor,
this.config.decodeAuthorizerContext,
);
// The aws.lambda span needs to have a parented to the Datadog trace context from the
// incoming event if available or the X-Ray trace context if hybrid tracing is enabled
let parentSpanContext: SpanContext | undefined;
Expand All @@ -104,7 +113,12 @@ export class TraceListener {
});
}
if (this.config.createInferredSpan) {
this.inferredSpan = this.inferrer.createInferredSpan(event, context, parentSpanContext);
this.inferredSpan = this.inferrer.createInferredSpan(
event,
context,
parentSpanContext,
this.config.encodeAuthorizerContext,
);
}
this.lambdaSpanParentContext = this.inferredSpan?.span || parentSpanContext;
this.context = context;
Expand Down
29 changes: 3 additions & 26 deletions src/trace/span-inferrer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,36 +510,13 @@ describe("Authorizer Spans", () => {
]);
});

it("creates an inferred span for API Gateway V2 event with traced authorizers [Request Type]", () => {
it("connects the inferred span for API Gateway V2 event with traced authorizers [Request Type]", () => {
const inferrer = new SpanInferrer(mockWrapperWithFinish as unknown as TracerWrapper);
inferrer.createInferredSpan(apiGatewayV2RequestAuthorizer, {} as any, {} as SpanContext);
expect(mockWrapperWithFinish.startSpan.mock.calls[0]).toEqual([
"aws.apigateway.authorizer",
{
childOf: {},
startTime: 1665596771812,
tags: {
_inferred_span: { synchronicity: "sync", tag_source: "self" },
apiid: "l9flvsey83",
domain_name: "l9flvsey83.execute-api.sa-east-1.amazonaws.com",
endpoint: "/hello",
"http.method": "GET",
"http.url": "l9flvsey83.execute-api.sa-east-1.amazonaws.com/hello",
operation_name: "aws.apigateway",
request_id: undefined,
"resource.name": "GET /hello",
resource_names: "GET /hello",
service: "l9flvsey83.execute-api.sa-east-1.amazonaws.com",
"service.name": "l9flvsey83.execute-api.sa-east-1.amazonaws.com",
"span.type": "http",
stage: "$default",
},
},
]);
expect(mockWrapperWithFinish.startSpan.mock.calls[1]).toEqual([
"aws.apigateway",
{
childOf: { finish: mockFinish }, // Hack around jest mocks
childOf: {},
startTime: 1665596771812,
tags: {
_inferred_span: { synchronicity: "sync", tag_source: "self" },
Expand All @@ -548,7 +525,7 @@ describe("Authorizer Spans", () => {
endpoint: "/hello",
"http.method": "GET",
"http.url": "l9flvsey83.execute-api.sa-east-1.amazonaws.com/hello",
operation_name: "aws.apigateway",
operation_name: "aws.httpapi",
request_id: undefined,
"resource.name": "GET /hello",
resource_names: "GET /hello",
Expand Down
83 changes: 45 additions & 38 deletions src/trace/span-inferrer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,26 @@ import { SpanWrapper } from "./span-wrapper";
import { parentSpanFinishTimeHeader } from "./constants";
import { logDebug } from "../utils";
import { getInjectedAuthorizerData } from "./context";
import { decodeAuthorizerContextEnvVar } from "../index";

export class SpanInferrer {
traceWrapper: TracerWrapper;
constructor(traceWrapper: TracerWrapper) {
this.traceWrapper = traceWrapper;
}

public createInferredSpan(event: any, context: Context | undefined, parentSpanContext: SpanContext | undefined): any {
public createInferredSpan(
event: any,
context: Context | undefined,
parentSpanContext: SpanContext | undefined,
decodeAuthorizerContext: boolean = true,
): any {
const eventSource = parseEventSource(event);
if (eventSource === eventTypes.lambdaUrl) {
return this.createInferredSpanForLambdaUrl(event, context);
}
if (eventSource === eventTypes.apiGateway) {
return this.createInferredSpanForApiGateway(event, context, parentSpanContext);
return this.createInferredSpanForApiGateway(event, context, parentSpanContext, decodeAuthorizerContext);
}
if (eventSource === eventTypes.sns) {
return this.createInferredSpanForSns(event, context, parentSpanContext);
Expand Down Expand Up @@ -60,6 +66,7 @@ export class SpanInferrer {
event: any,
context: Context | undefined,
parentSpanContext: SpanContext | undefined,
decodeAuthorizerContext: boolean = true,
): SpanWrapper {
const options: SpanOptions = {};
const domain = event.requestContext.domainName;
Expand Down Expand Up @@ -102,47 +109,47 @@ export class SpanInferrer {
options.tags.event_type = event.requestContext.eventType;
}
let upstreamAuthorizerSpan: SpanWrapper | undefined;
try {
const eventSourceSubType: eventSubTypes = parseEventSourceSubType(event);
const parsedUpstreamContext = getInjectedAuthorizerData(event, eventSourceSubType);

if (parsedUpstreamContext) {
let upstreamSpanOptions: SpanOptions = {};
const startTime = parsedUpstreamContext[parentSpanFinishTimeHeader] / 1e6;
upstreamSpanOptions = {
startTime,
tags: { operation_name: "aws.apigateway.authorizer", ...options.tags },
};

let endTime: number;
// getting an approximated endTime
if (eventSourceSubType === eventSubTypes.apiGatewayV2) {
endTime = startTime;
} else {
endTime = event.requestContext.requestTimeEpoch + event.requestContext.authorizer.integrationLatency;
const eventSourceSubType: eventSubTypes = parseEventSourceSubType(event);
if (decodeAuthorizerContext) {
try {
const parsedUpstreamContext = getInjectedAuthorizerData(event, eventSourceSubType);
if (parsedUpstreamContext) {
let upstreamSpanOptions: SpanOptions = {};
const startTime = parsedUpstreamContext[parentSpanFinishTimeHeader] / 1e6;
// getting an approximated endTime
if (eventSourceSubType === eventSubTypes.apiGatewayV2) {
options.startTime = startTime; // not inserting authorizer span
options.tags.operation_name = "aws.httpapi";
} else {
upstreamSpanOptions = {
startTime,
childOf: parentSpanContext,
tags: { operation_name: "aws.apigateway.authorizer", ...options.tags },
};
upstreamAuthorizerSpan = new SpanWrapper(
this.traceWrapper.startSpan("aws.apigateway.authorizer", upstreamSpanOptions),
{ isAsync: false },
);
const endTime = event.requestContext.requestTimeEpoch + event.requestContext.authorizer.integrationLatency;
upstreamAuthorizerSpan.finish(endTime);
options.startTime = endTime; // For the main function's inferred span
}
}
} catch (error) {
logDebug("Error decoding authorizer span", error as Error);
}
}

upstreamSpanOptions.childOf = parentSpanContext;
upstreamAuthorizerSpan = new SpanWrapper(
this.traceWrapper.startSpan("aws.apigateway.authorizer", upstreamSpanOptions),
{ isAsync: false },
);
upstreamAuthorizerSpan.finish(endTime);
options.startTime = endTime;
if (!options.startTime) {
if (
eventSourceSubType === eventSubTypes.apiGatewayV1 ||
eventSourceSubType === eventSubTypes.apiGatewayWebsocket
) {
options.startTime = event.requestContext.requestTimeEpoch;
} else {
if (
eventSourceSubType === eventSubTypes.apiGatewayV1 ||
eventSourceSubType === eventSubTypes.apiGatewayWebsocket
) {
options.startTime = event.requestContext.requestTimeEpoch;
} else {
options.startTime = event.requestContext.timeEpoch;
}
options.startTime = event.requestContext.timeEpoch;
}
} catch (error) {
logDebug("Error decoding authorizer span", error as Error);
}

options.childOf = upstreamAuthorizerSpan ? upstreamAuthorizerSpan.span : parentSpanContext;

const spanWrapperOptions = {
Expand Down
3 changes: 2 additions & 1 deletion src/trace/trace-context-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export class TraceContextService {
event: any,
context: Context,
extractor?: TraceExtractor,
decodeAuthorizerContext: boolean = true,
): Partial<TraceHeaders> | undefined {
this.rootTraceContext = extractTraceContext(event, context, extractor);
this.rootTraceContext = extractTraceContext(event, context, extractor, decodeAuthorizerContext);
return this.currentTraceHeaders;
}

Expand Down

0 comments on commit 79668df

Please sign in to comment.