Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

breaking: Use sha256 to hash lambda traceId that are triggered by Step Functions and set _dd.p.tid #534

Merged
merged 30 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
131f778
v1
kimi-p May 2, 2024
b9032cd
fix test
kimi-p May 2, 2024
aa1a0fa
remove md5 package and regenerate yarn lock
kimi-p May 2, 2024
60e0445
lint
kimi-p May 2, 2024
1617e1a
upgrade node/type
kimi-p May 7, 2024
858cfd5
fix 3 tests
kimi-p May 7, 2024
3338526
fix span ids for the same tests (previous commit was for trace id
kimi-p May 7, 2024
3c7b456
add @aws-crypto/sha256-js
kimi-p May 7, 2024
89a3ce3
remove 8h
kimi-p May 7, 2024
43fe7b8
Empty-Commit
kimi-p May 7, 2024
4ed002f
update yarn.lock
kimi-p May 7, 2024
1a22e0a
update 65th bit to be 0 and tests all accidentally pass
kimi-p May 16, 2024
ee7d6bd
rename
kimi-p May 16, 2024
eb49ebf
rename
kimi-p May 16, 2024
f3c87db
add option to hashing to 64 bits or 128 bits
kimi-p May 16, 2024
e8470cd
merge main
kimi-p May 16, 2024
d9c4072
update test name
kimi-p May 16, 2024
b3d76cd
try log-backend log
kimi-p May 22, 2024
36f3942
remote print
kimi-p May 22, 2024
a3b7bd7
update 64 zeros case
kimi-p May 22, 2024
e3be794
Empty-Commit
kimi-p May 22, 2024
848a3b2
Empty-Commit
kimi-p May 22, 2024
0de000f
lint
kimi-p May 22, 2024
0c3dbbd
Merge branch 'main' of github.com:DataDog/datadog-lambda-js into kimi…
kimi-p May 22, 2024
cf96c98
remove unnecessary edge case check
kimi-p May 31, 2024
8e28404
Merge branch 'main' into kimi/update-sha256
kimi-p Jun 4, 2024
0debb2a
small logic update from comment
kimi-p Jun 4, 2024
1837ffe
use .reduce()
kimi-p Jun 4, 2024
b385614
lazy load require dd-trace/packages/dd-trace/src/opentracing/span_con…
kimi-p Jun 4, 2024
d3e6fbf
lazy load tracer classes
duncanista Jun 5, 2024
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
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@types/aws-sdk": "^2.7.0",
"@types/jest": "^26.0.23",
"@types/mock-fs": "4.13.0",
"@types/node": "^15.6.1",
"@types/node": "^20.12.10",
"@types/promise-retry": "^1.1.3",
"@types/shimmer": "^1.0.1",
"dd-trace": "^4.37.0",
Expand All @@ -38,12 +38,12 @@
"typescript": "^4.3.2"
},
"dependencies": {
"@aws-crypto/sha256-js": "5.2.0",
"dc-polyfill": "^0.1.3",
"hot-shots": "8.5.0",
"promise-retry": "^2.0.1",
"serialize-error": "^8.1.0",
"shimmer": "1.2.1",
"ts-md5": "1.3.1"
"shimmer": "1.2.1"
},
"jest": {
"verbose": true,
Expand Down
4 changes: 2 additions & 2 deletions src/trace/context/extractor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,8 +688,8 @@ describe("TraceContextExtractor", () => {
const traceContext = await extractor.extract(event, {} as Context);
expect(traceContext).not.toBeNull();

expect(traceContext?.toTraceId()).toBe("947965466153612645");
expect(traceContext?.toSpanId()).toBe("4602916161841036335");
expect(traceContext?.toTraceId()).toBe("1139193989631387307");
expect(traceContext?.toSpanId()).toBe("5892738536804826142");
expect(traceContext?.sampleMode()).toBe("1");
expect(traceContext?.source).toBe("event");
});
Expand Down
8 changes: 4 additions & 4 deletions src/trace/context/extractors/step-function.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ describe("StepFunctionEventTraceExtractor", () => {
const traceContext = extractor.extract(payload);
expect(traceContext).not.toBeNull();

expect(traceContext?.toTraceId()).toBe("947965466153612645");
expect(traceContext?.toSpanId()).toBe("4602916161841036335");
expect(traceContext?.toTraceId()).toBe("1139193989631387307");
expect(traceContext?.toSpanId()).toBe("5892738536804826142");
expect(traceContext?.sampleMode()).toBe("1");
expect(traceContext?.source).toBe("event");
});
Expand All @@ -49,8 +49,8 @@ describe("StepFunctionEventTraceExtractor", () => {
const traceContext = extractor.extract(payload);
expect(traceContext).not.toBeNull();

expect(traceContext?.toTraceId()).toBe("947965466153612645");
expect(traceContext?.toSpanId()).toBe("4602916161841036335");
expect(traceContext?.toTraceId()).toBe("1139193989631387307");
expect(traceContext?.toSpanId()).toBe("5892738536804826142");
expect(traceContext?.sampleMode()).toBe("1");
expect(traceContext?.source).toBe("event");
});
Expand Down
79 changes: 44 additions & 35 deletions src/trace/step-function-service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { StepFunctionContextService } from "./step-function-service";
import { PARENT_ID, StepFunctionContextService } from "./step-function-service";

describe("StepFunctionContextService", () => {
const stepFunctionEvent = {
Expand Down Expand Up @@ -194,8 +194,8 @@ describe("StepFunctionContextService", () => {

expect(spanContext).not.toBeNull();

expect(spanContext?.toTraceId()).toBe("947965466153612645");
expect(spanContext?.toSpanId()).toBe("4602916161841036335");
expect(spanContext?.toTraceId()).toBe("1139193989631387307");
expect(spanContext?.toSpanId()).toBe("5892738536804826142");
expect(spanContext?.sampleMode()).toBe("1");
expect(spanContext?.source).toBe("event");
});
Expand All @@ -211,54 +211,55 @@ describe("StepFunctionContextService", () => {
});
});

describe("deterministicMd5HashToBigIntString", () => {
describe("deterministicSha256HashToBigIntString", () => {
it("returns the same hash number generated in `logs backend` for a random string", () => {
const instance = StepFunctionContextService.instance();
const hash = instance["deterministicMd5HashToBigIntString"]("some_testing_random_string");
expect(hash).toEqual("2251275791555400689");
const hash = instance["deterministicSha256HashToBigIntString"]("some_testing_random_string", PARENT_ID);
expect(hash).toEqual("4364271812988819936");
});

it("returns the same hash number generated in `logs backend` for execution id # state name # entered time", () => {
const instance = StepFunctionContextService.instance();
const hash = instance["deterministicMd5HashToBigIntString"](
const hash = instance["deterministicSha256HashToBigIntString"](
"arn:aws:states:sa-east-1:601427271234:express:DatadogStateMachine:acaf1a67-336a-e854-1599-2a627eb2dd8a:c8baf081-31f1-464d-971f-70cb17d01111#step-one#2022-12-08T21:08:19.224Z",
PARENT_ID,
);
expect(hash).toEqual("8034507082463708833");
expect(hash).toEqual("4340734536022949921");
});
});

describe("deterministicMd5HashInBinary", () => {
describe("deterministicSha256Hash", () => {
it.each([
[
"a random string",
"some_testing_random_string",
"0001111100111110001000110110011110010111000110001001001111110001",
"0011110010010001000000100001011101001100011100101101100111100000",
],
[
"an execution id",
"arn:aws:states:sa-east-1:601427271234:express:DatadogStateMachine:acaf1a67-336a-e854-1599-2a627eb2dd8a:c8baf081-31f1-464d-971f-70cb17d041f4",
"0010010000101100100000101011111101111100110110001110111100111101",
"0100010100110010010010100001011001110100111011010100110010000100",
],
[
"another execution id",
"arn:aws:states:sa-east-1:601427271234:express:DatadogStateMachine:acaf1a67-336a-e854-1599-2a627eb2dd8a:c8baf081-31f1-464d-971f-70cb17d01111",
"0010001100110000011011011111010000100111100000110000100100101010",
"0010111110001100100010000101001100110000000000010111011100101011",
],
[
"execution id # state name # entered time",
"arn:aws:states:sa-east-1:601427271234:express:DatadogStateMachine:acaf1a67-336a-e854-1599-2a627eb2dd8a:c8baf081-31f1-464d-971f-70cb17d01111#step-one#2022-12-08T21:08:19.224Z",
"0110111110000000010011011001111101110011100111000000011010100001",
"0011110000111101011000110000111111110011111010110000000000100001",
],
])("returns the same hash number generated in `logs backend` for %s", (_, str, expected) => {
const instance = StepFunctionContextService.instance();
const hash = instance["deterministicMd5HashInBinary"](str);
const hash = instance["deterministicSha256Hash"](str, PARENT_ID);
expect(hash).toEqual(expected);
});

it("returns a hash always leading with 0", () => {
const instance = StepFunctionContextService.instance();
for (let i = 0; i < 20; i++) {
const hash = instance["deterministicMd5HashInBinary"](i.toString());
const hash = instance["deterministicSha256Hash"](i.toString(), PARENT_ID);
expect(hash.substring(0, 1)).toMatch("0");
}
});
Expand All @@ -268,36 +269,44 @@ describe("StepFunctionContextService", () => {
const times = 20;
for (let i = 0; i < times; i++) {
for (let j = i + 1; j < times; j++) {
const hash1 = instance["deterministicMd5HashInBinary"](i.toString());
const hash2 = instance["deterministicMd5HashInBinary"](j.toString());
const hash1 = instance["deterministicSha256Hash"](i.toString(), PARENT_ID);
const hash2 = instance["deterministicSha256Hash"](j.toString(), PARENT_ID);
expect(hash1).not.toMatch(hash2);
}
}
});
});

describe("hexToBinary", () => {
describe("numberToBinaryString", () => {
const instance = StepFunctionContextService.instance();
it.each([
["0", "0000"],
["1", "0001"],
["2", "0010"],
["3", "0011"],
["4", "0100"],
["5", "0101"],
["6", "0110"],
["7", "0111"],
["8", "1000"],
["9", "1001"],
["a", "1010"],
["b", "1011"],
["c", "1100"],
["d", "1101"],
["e", "1110"],
["f", "1111"],
[0, "00000000"],
[1, "00000001"],
[2, "00000010"],
[3, "00000011"],
[4, "00000100"],
])("returns the right binary number for %s => %s", (hex, expected) => {
const binary = instance["hexToBinary"](hex);
const binary = instance["numberToBinaryString"](hex);
expect(binary).toBe(expected);
});
});

describe("test 64 bits deterministicSha256HashToBigIntString for span id", () => {
const instance = StepFunctionContextService.instance();
it("first test of #1", () => {
const actual = instance["deterministicSha256HashToBigIntString"](
"arn:aws:states:sa-east-1:425362996713:stateMachine:MyStateMachine-b276uka1j#lambda#1",
PARENT_ID,
);
expect(actual).toEqual("3711631873188331089");
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


it("test same hashing number is generated as logs-backend for execution id # state name # entered time", () => {
const actual = instance["deterministicSha256HashToBigIntString"](
"arn:aws:states:sa-east-1:425362996713:stateMachine:MyStateMachine-b276uka1j#lambda#2",
PARENT_ID,
);
expect(actual).toEqual("5759173372325510050");
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

});
});
72 changes: 47 additions & 25 deletions src/trace/step-function-service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Md5 } from "ts-md5";
import { logDebug } from "../utils";
import { SampleMode, TraceSource } from "./trace-context-service";
import { SpanContextWrapper } from "./span-context-wrapper";
import { Sha256 } from "@aws-crypto/sha256-js";

export interface StepFunctionContext {
"step_function.execution_name": string;
Expand All @@ -16,6 +16,10 @@ export interface StepFunctionContext {
"step_function.state_retry_count": number;
}

export const TRACE_ID = "traceId";
export const PARENT_ID = "spanId";
export const DD_P_TID = "_dd.p.tid";

export class StepFunctionContextService {
private static _instance: StepFunctionContextService;
public context?: StepFunctionContext;
Expand Down Expand Up @@ -123,51 +127,69 @@ export class StepFunctionContextService {
public get spanContext(): SpanContextWrapper | null {
if (this.context === undefined) return null;

const traceId = this.deterministicMd5HashToBigIntString(this.context["step_function.execution_id"]);
const parentId = this.deterministicMd5HashToBigIntString(
const traceId = this.deterministicSha256HashToBigIntString(this.context["step_function.execution_id"], TRACE_ID);
const parentId = this.deterministicSha256HashToBigIntString(
this.context["step_function.execution_id"] +
"#" +
this.context["step_function.state_name"] +
"#" +
this.context["step_function.state_entered_time"],
PARENT_ID,
);
const sampleMode = SampleMode.AUTO_KEEP;

const spanContext = SpanContextWrapper.fromTraceContext({
traceId,
parentId,
sampleMode,
source: TraceSource.Event,
});

if (spanContext === null) return null;
logDebug(`Extracted trace context from StepFunctionContext`, { traceContext: this.context });
return spanContext;
try {
// Try requiring class from the tracer.
const _DatadogSpanContext = require("dd-trace/packages/dd-trace/src/opentracing/span_context");
const id = require("dd-trace/packages/dd-trace/src/id");

const ddSpanContext = new _DatadogSpanContext({
traceId: id(traceId, 10),
spanId: id(parentId, 10),
sampling: { priority: sampleMode.toString(2) },
});

const ptid = this.deterministicSha256HashToBigIntString(this.context["step_function.execution_id"], DD_P_TID);
ddSpanContext._trace.tags["_dd.p.tid"] = id(ptid, 10).toString(16);
if (ddSpanContext === null) return null;

logDebug(`Extracted trace context from StepFunctionContext`, { traceContext: ddSpanContext });

return new SpanContextWrapper(ddSpanContext, TraceSource.Event);
} catch (error) {
if (error instanceof Error) {
logDebug("Couldn't generate SpanContext with tracer.", error);
}
return null;
}
}

private deterministicMd5HashToBigIntString(s: string): string {
const binaryString = this.deterministicMd5HashInBinary(s);
private deterministicSha256HashToBigIntString(s: string, type: string): string {
const binaryString = this.deterministicSha256Hash(s, type);
return BigInt("0b" + binaryString).toString();
}

private deterministicMd5HashInBinary(s: string): string {
// Md5 here is used here because we don't need a cryptographically secure hashing method but to generate the same trace/span ids as the backend does
const hex = Md5.hashStr(s);
private deterministicSha256Hash(s: string, type: string): string {
// returns 128 bits hash unless mostSignificant64Bits options is set to true.

let binary = "";
for (let i = 0; i < hex.length; i++) {
const ch = hex.charAt(i);
binary = binary + this.hexToBinary(ch);
const hash = new Sha256();
hash.update(s);
const uint8Array = hash.digestSync();
// type === SPAN_ID || type === DD_P_TID
let intArray = uint8Array.subarray(0, 8);
if (type === TRACE_ID) {
intArray = uint8Array.subarray(8, 16);
}
const binaryString = intArray.reduce((acc, num) => acc + this.numberToBinaryString(num), "");

const res = "0" + binary.substring(1, 64);
const res = "0" + binaryString.substring(1, 64);
if (res === "0".repeat(64)) {
return "1";
}
return res;
}

private hexToBinary(hex: string) {
return parseInt(hex, 16).toString(2).padStart(4, "0");
private numberToBinaryString(num: number): string {
return num.toString(2).padStart(8, "0");
}
}
Loading
Loading