Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ Follow the [configuration instructions](https://docs.datadoghq.com/serverless/co

For additional tracing configuration options, check out the [official documentation for Datadog trace client](https://datadoghq.dev/dd-trace-js/).

Besides the environment variables supported by dd-trace-js, the datadog-lambda-js library added following environment variables.


| Environment Variables | Description | Default Value |
| -------------------- | ------------ | ------------- |
| DD_ENCODE_AUTHORIZER_CONTEXT | When set to `true` for Lambda authorizers, the tracing context will be encoded into the response for propagation. Supported for NodeJS and Python. | `true` |
| DD_DECODE_AUTHORIZER_CONTEXT | When set to `true` for Lambdas that are authorized via Lambda authorizers, it will parse and use the encoded tracing context (if found). Supported for NodeJS and Python. | `true` |
| DD_COLD_START_TRACING | Set to `false` to disable Cold Start Tracing. Used in NodeJS and Python. | `true` |
| DD_MIN_COLD_START_DURATION | Sets the minimum duration (in milliseconds) for a module load event to be traced via Cold Start Tracing. Number. | `3` |
| DD_COLD_START_TRACE_SKIP_LIB | optionally skip creating Cold Start Spans for a comma-separated list of libraries. Useful to limit depth or skip known libraries. | `./opentracing/tracer` |
| DD_CAPTURE_LAMBDA_PAYLOAD | [Captures incoming and outgoing AWS Lambda payloads][1] in the Datadog APM spans for Lambda invocations. | `false` |
| DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH | Determines the level of detail captured from AWS Lambda payloads, which are then assigned as tags for the `aws.lambda` span. It specifies the nesting depth of the JSON payload structure to process. Once the specified maximum depth is reached, the tag's value is set to the stringified value of any nested elements beyond this level. <br> For example, given the input payload: <pre>{<br> "lv1" : {<br> "lv2": {<br> "lv3": "val"<br> }<br> }<br>}</pre> If the depth is set to `2`, the resulting tag's key is set to `function.request.lv1.lv2` and the value is `{\"lv3\": \"val\"}`. <br> If the depth is set to `0`, the resulting tag's key is set to `function.request` and value is `{\"lv1\":{\"lv2\":{\"lv3\": \"val\"}}}` | `10` |


## Lambda Profiling Beta

Datadog's [Continuous Profiler](https://www.datadoghq.com/product/code-profiling/) is now available in beta for NodeJS in version 6.87.0 and layer version 87 and above. This optional feature is enabled by setting the `DD_PROFILING_ENABLED` environment variable to `true`. During the beta period, profiling is available at no additional cost.
Expand All @@ -32,7 +46,7 @@ The first 5.x.x version was released with Lambda Layer version `69`.

### 6.x.x

The 6.x.x release introduces support for the node 16 runtime and esm modules.
The 6.x.x release introduces support for the node 16 runtime and esm modules.

### 7.x.x

Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export { DatadogTraceHeaders as TraceHeaders } from "./trace/context/extractor";
export const apiKeyEnvVar = "DD_API_KEY";
export const apiKeyKMSEnvVar = "DD_KMS_API_KEY";
export const captureLambdaPayloadEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD";
export const captureLambdaPayloadMaxDepthEnvVar = "DD_CAPTURE_LAMBDA_PAYLOAD_MAX_DEPTH";
export const traceManagedServicesEnvVar = "DD_TRACE_MANAGED_SERVICES";
export const siteURLEnvVar = "DD_SITE";
export const logLevelEnvVar = "DD_LOG_LEVEL";
Expand Down Expand Up @@ -71,6 +72,7 @@ export const defaultConfig: Config = {
apiKeyKMS: "",
autoPatchHTTP: true,
captureLambdaPayload: false,
captureLambdaPayloadMaxDepth: 10,
createInferredSpan: true,
debugLogging: false,
encodeAuthorizerContext: true,
Expand Down Expand Up @@ -359,6 +361,10 @@ function getConfig(userConfig?: Partial<Config>): Config {
config.coldStartTraceSkipLib = getEnvValue(coldStartTraceSkipLibEnvVar, "./opentracing/tracer");
}

if (userConfig === undefined || userConfig.captureLambdaPayloadMaxDepth === undefined) {
config.captureLambdaPayloadMaxDepth = Number(getEnvValue(captureLambdaPayloadMaxDepthEnvVar, "10"));
}

return config;
}

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 @@ -70,6 +70,7 @@ describe("TraceListener", () => {
const defaultConfig = {
autoPatchHTTP: true,
captureLambdaPayload: false,
captureLambdaPayloadMaxDepth: 10,
createInferredSpan: true,
encodeAuthorizerContext: true,
decodeAuthorizerContext: true,
Expand Down
17 changes: 14 additions & 3 deletions src/trace/listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { patchHttp, unpatchHttp } from "./patch-http";

import { extractTriggerTags, extractHTTPStatusCodeTag } from "./trigger";
import { ColdStartTracerConfig, ColdStartTracer } from "./cold-start-tracer";

import { logDebug, tagObject } from "../utils";
import { didFunctionColdStart, isProactiveInitialization } from "../utils/cold-start";
import { datadogLambdaVersion } from "../constants";
Expand All @@ -30,6 +29,12 @@ export interface TraceConfig {
* Whether to capture the lambda payload and response in Datadog.
*/
captureLambdaPayload: boolean;
/**
* The captured AWS Lambda payloads will become tags of the `aws.lambda` span. This sets how deep
* it fathoms the JSON structure. When the max depth reached, the tag's value will be the
* stringified value of the deeper nested items.
*/
captureLambdaPayloadMaxDepth: number;
/**
* Whether to create inferred spans for managed services
*/
Expand Down Expand Up @@ -146,8 +151,14 @@ export class TraceListener {
if (!this.tracerWrapper.currentSpan) return false;
this.wrappedCurrentSpan = new SpanWrapper(this.tracerWrapper.currentSpan, {});
if (this.config.captureLambdaPayload) {
tagObject(this.tracerWrapper.currentSpan, "function.request", event);
tagObject(this.tracerWrapper.currentSpan, "function.response", result);
tagObject(this.tracerWrapper.currentSpan, "function.request", event, 0, this.config.captureLambdaPayloadMaxDepth);
tagObject(
this.tracerWrapper.currentSpan,
"function.response",
result,
0,
this.config.captureLambdaPayloadMaxDepth,
);
}
const coldStartNodes = getTraceTree();
if (coldStartNodes.length > 0) {
Expand Down
33 changes: 33 additions & 0 deletions src/utils/tag-object.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,39 @@ describe("tagObject", () => {
["lambda_payload.request.vals.1.thingTwo", "2"],
]);
});
it("tags reach max depth", () => {
const span = {
setTag,
};

tagObject(
span,
"function.request",
{
hello: "world",
level1: {
level2_dict: {
level3: 3,
},
level2_list: [null, true, "nice", { l3: "v3" }],
level2_bool: true,
level2_int: 2,
},
vals: [{ thingOne: 1 }, { thingTwo: 2 }],
},
0,
2,
);
expect(setTag.mock.calls).toEqual([
["function.request.hello", "world"],
["function.request.level1.level2_dict", '{"level3":3}'],
["function.request.level1.level2_list", '[null,true,"nice",{"l3":"v3"}]'],
["function.request.level1.level2_bool", "true"],
["function.request.level1.level2_int", "2"],
["function.request.vals.0", '{"thingOne":1}'],
["function.request.vals.1", '{"thingTwo":2}'],
]);
});
it("redacts common secret keys", () => {
const span = {
setTag,
Expand Down
13 changes: 5 additions & 8 deletions src/utils/tag-object.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
const redactableKeys = ["authorization", "x-authorization", "password", "token"];
const maxDepth = 10;

export function tagObject(currentSpan: any, key: string, obj: any, depth = 0): any {
export function tagObject(currentSpan: any, key: string, obj: any, depth = 0, maxDepth = 10): any {
if (depth >= maxDepth) {
return;
} else {
depth += 1;
return currentSpan.setTag(key, redactVal(key, JSON.stringify(obj).substring(0, 5000)));
}
if (obj === null) {
return currentSpan.setTag(key, obj);
}
depth += 1;
if (typeof obj === "string") {
let parsed: string;
try {
Expand All @@ -18,16 +16,15 @@ export function tagObject(currentSpan: any, key: string, obj: any, depth = 0): a
const redacted = redactVal(key, obj.substring(0, 5000));
return currentSpan.setTag(key, redacted);
}
return tagObject(currentSpan, key, parsed, depth);
return tagObject(currentSpan, key, parsed, depth, maxDepth);
}
if (typeof obj === "number" || typeof obj === "boolean") {
return currentSpan.setTag(key, obj.toString());
}
if (typeof obj === "object") {
for (const [k, v] of Object.entries(obj)) {
tagObject(currentSpan, `${key}.${k}`, v, depth);
tagObject(currentSpan, `${key}.${k}`, v, depth, maxDepth);
}
return;
}
}

Expand Down