@ogcio/o11y-sdk-node 0.1.0-beta.9 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +128 -0
- package/README.md +158 -13
- package/dist/lib/config-manager.d.ts +3 -0
- package/dist/lib/config-manager.js +11 -0
- package/dist/lib/exporter/console.js +3 -4
- package/dist/lib/exporter/grpc.d.ts +1 -1
- package/dist/lib/exporter/grpc.js +24 -14
- package/dist/lib/exporter/http.d.ts +1 -1
- package/dist/lib/exporter/http.js +14 -13
- package/dist/lib/exporter/pii-exporter-decorator.d.ts +20 -0
- package/dist/lib/exporter/pii-exporter-decorator.js +103 -0
- package/dist/lib/exporter/processor-config.d.ts +5 -0
- package/dist/lib/exporter/processor-config.js +16 -0
- package/dist/lib/index.d.ts +25 -4
- package/dist/lib/instrumentation.node.d.ts +1 -1
- package/dist/lib/instrumentation.node.js +29 -19
- package/dist/lib/internals/hooks.d.ts +3 -0
- package/dist/lib/internals/hooks.js +12 -0
- package/dist/lib/internals/pii-detection.d.ts +17 -0
- package/dist/lib/internals/pii-detection.js +116 -0
- package/dist/lib/internals/shared-metrics.d.ts +7 -0
- package/dist/lib/internals/shared-metrics.js +18 -0
- package/dist/lib/resource.js +2 -2
- package/dist/lib/traces.d.ts +20 -1
- package/dist/lib/traces.js +47 -1
- package/dist/package.json +23 -21
- package/dist/vitest.config.js +8 -2
- package/lib/config-manager.ts +16 -0
- package/lib/exporter/console.ts +6 -4
- package/lib/exporter/grpc.ts +46 -20
- package/lib/exporter/http.ts +33 -20
- package/lib/exporter/pii-exporter-decorator.ts +152 -0
- package/lib/exporter/processor-config.ts +23 -0
- package/lib/index.ts +28 -4
- package/lib/instrumentation.node.ts +37 -22
- package/lib/internals/hooks.ts +14 -0
- package/lib/internals/pii-detection.ts +145 -0
- package/lib/internals/shared-metrics.ts +34 -0
- package/lib/resource.ts +3 -2
- package/lib/traces.ts +74 -1
- package/package.json +23 -21
- package/test/config-manager.test.ts +34 -0
- package/test/exporter/pii-exporter-decorator.test.ts +139 -0
- package/test/index.test.ts +44 -12
- package/test/integration/README.md +1 -1
- package/test/integration/{integration.test.ts → http-tracing.integration.test.ts} +0 -2
- package/test/integration/pii.integration.test.ts +68 -0
- package/test/integration/run.sh +2 -2
- package/test/internals/hooks.test.ts +45 -0
- package/test/internals/pii-detection.test.ts +141 -0
- package/test/internals/shared-metrics.test.ts +34 -0
- package/test/node-config.test.ts +68 -30
- package/test/processor/enrich-span-processor.test.ts +2 -54
- package/test/resource.test.ts +12 -1
- package/test/traces/active-span.test.ts +28 -0
- package/test/traces/with-span.test.ts +340 -0
- package/test/utils/alloy-log-parser.ts +7 -0
- package/test/utils/mock-signals.ts +144 -0
- package/test/validation.test.ts +22 -16
- package/vitest.config.ts +8 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
vi.mock("../../lib/metrics", () => {
|
|
2
|
+
return {
|
|
3
|
+
getMetric: vi.fn(),
|
|
4
|
+
};
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
8
|
+
import { _getPIICounterRedactionMetric } from "../../lib/internals/shared-metrics.js";
|
|
9
|
+
import { getMetric } from "../../lib/metrics"; // Get the mocked function
|
|
10
|
+
|
|
11
|
+
describe("shared metrics", () => {
|
|
12
|
+
const mockMetric = { add: vi.fn() };
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.resetModules(); // Clear module-level cache
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
(getMetric as vi.Mock).mockClear(); // clear call history
|
|
18
|
+
(getMetric as vi.Mock).mockReturnValue(mockMetric);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("calls getMetric with correct arguments and caches result", () => {
|
|
22
|
+
const metric1 = _getPIICounterRedactionMetric();
|
|
23
|
+
const metric2 = _getPIICounterRedactionMetric();
|
|
24
|
+
|
|
25
|
+
expect(getMetric).toHaveBeenCalledOnce();
|
|
26
|
+
expect(getMetric).toHaveBeenCalledWith("counter", {
|
|
27
|
+
meterName: "o11y",
|
|
28
|
+
metricName: "o11y_pii_redaction",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(metric1).toBe(mockMetric);
|
|
32
|
+
expect(metric2).toBe(mockMetric); // should be cached
|
|
33
|
+
});
|
|
34
|
+
});
|
package/test/node-config.test.ts
CHANGED
|
@@ -1,25 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { NodeSDK, metrics } from "@opentelemetry/sdk-node";
|
|
2
|
+
import { afterAll, assert, describe, expect, test } from "vitest";
|
|
2
3
|
import buildNodeInstrumentation from "../lib/instrumentation.node.js";
|
|
3
|
-
|
|
4
|
-
import { OTLPTraceExporter as GRPC_OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
|
|
5
|
-
import { OTLPMetricExporter as GRPC_OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
|
|
6
|
-
import { OTLPLogExporter as GRPC_OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
|
|
7
|
-
|
|
8
|
-
import { OTLPTraceExporter as HTTP_OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
9
|
-
import { OTLPMetricExporter as HTTP_OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
10
|
-
import { OTLPLogExporter as HTTP_OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
11
|
-
import { NodeSDKConfig } from "../lib/index.js";
|
|
12
|
-
import buildHttpExporters from "../lib/exporter/http.js";
|
|
13
|
-
import {
|
|
14
|
-
BatchSpanProcessor,
|
|
15
|
-
ConsoleSpanExporter,
|
|
16
|
-
SimpleSpanProcessor,
|
|
17
|
-
} from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
|
|
18
5
|
import {
|
|
19
6
|
BatchLogRecordProcessor,
|
|
20
7
|
SimpleLogRecordProcessor,
|
|
21
8
|
} from "@opentelemetry/sdk-logs";
|
|
9
|
+
import {
|
|
10
|
+
BatchSpanProcessor,
|
|
11
|
+
SimpleSpanProcessor,
|
|
12
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
13
|
+
import buildHttpExporters from "../lib/exporter/http.js";
|
|
14
|
+
import { NodeSDKConfig } from "../lib/index.js";
|
|
15
|
+
import { EnrichLogProcessor } from "../lib/processor/enrich-logger-processor.js";
|
|
22
16
|
import { EnrichSpanProcessor } from "../lib/processor/enrich-span-processor.js";
|
|
17
|
+
import { PIIExporterDecorator } from "../lib/exporter/pii-exporter-decorator.js";
|
|
23
18
|
|
|
24
19
|
describe("verify config settings", () => {
|
|
25
20
|
const commonConfig = {
|
|
@@ -27,7 +22,7 @@ describe("verify config settings", () => {
|
|
|
27
22
|
serviceName: "test",
|
|
28
23
|
};
|
|
29
24
|
|
|
30
|
-
test("grpc config", () => {
|
|
25
|
+
test("grpc config", async () => {
|
|
31
26
|
const config: NodeSDKConfig = {
|
|
32
27
|
...commonConfig,
|
|
33
28
|
protocol: "grpc",
|
|
@@ -35,7 +30,7 @@ describe("verify config settings", () => {
|
|
|
35
30
|
diagLogLevel: "NONE",
|
|
36
31
|
};
|
|
37
32
|
|
|
38
|
-
const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
|
|
33
|
+
const sdk: NodeSDK | undefined = await buildNodeInstrumentation(config);
|
|
39
34
|
|
|
40
35
|
assert.ok(sdk);
|
|
41
36
|
|
|
@@ -44,14 +39,13 @@ describe("verify config settings", () => {
|
|
|
44
39
|
|
|
45
40
|
const logs = _configuration.logRecordProcessors;
|
|
46
41
|
|
|
47
|
-
assert.equal(logs.length,
|
|
48
|
-
assert.ok(logs[1] instanceof BatchLogRecordProcessor);
|
|
42
|
+
assert.equal(logs.length, 1);
|
|
43
|
+
assert.ok(logs[logs.length - 1] instanceof BatchLogRecordProcessor);
|
|
49
44
|
|
|
50
45
|
const spans = _configuration.spanProcessors;
|
|
51
46
|
|
|
52
|
-
assert.equal(spans.length,
|
|
47
|
+
assert.equal(spans.length, 1);
|
|
53
48
|
assert.ok(spans[0] instanceof BatchSpanProcessor);
|
|
54
|
-
assert.ok(spans[1] instanceof EnrichSpanProcessor);
|
|
55
49
|
|
|
56
50
|
assert.ok(
|
|
57
51
|
_configuration.metricReader instanceof
|
|
@@ -59,14 +53,17 @@ describe("verify config settings", () => {
|
|
|
59
53
|
);
|
|
60
54
|
});
|
|
61
55
|
|
|
62
|
-
test("http config", () => {
|
|
56
|
+
test("http config", async () => {
|
|
63
57
|
const config: NodeSDKConfig = {
|
|
64
58
|
...commonConfig,
|
|
65
59
|
protocol: "http",
|
|
66
60
|
diagLogLevel: "NONE",
|
|
61
|
+
spanAttributes: {
|
|
62
|
+
name: "custom-value",
|
|
63
|
+
},
|
|
67
64
|
};
|
|
68
65
|
|
|
69
|
-
const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
|
|
66
|
+
const sdk: NodeSDK | undefined = await buildNodeInstrumentation(config);
|
|
70
67
|
assert.ok(sdk);
|
|
71
68
|
|
|
72
69
|
const _configuration = sdk["_configuration"];
|
|
@@ -75,6 +72,7 @@ describe("verify config settings", () => {
|
|
|
75
72
|
const logs = _configuration.logRecordProcessors;
|
|
76
73
|
|
|
77
74
|
assert.equal(logs.length, 2);
|
|
75
|
+
assert.ok(logs[0] instanceof EnrichLogProcessor);
|
|
78
76
|
assert.ok(logs[1] instanceof BatchLogRecordProcessor);
|
|
79
77
|
|
|
80
78
|
const spans = _configuration.spanProcessors;
|
|
@@ -89,14 +87,17 @@ describe("verify config settings", () => {
|
|
|
89
87
|
);
|
|
90
88
|
});
|
|
91
89
|
|
|
92
|
-
test("console - console config", () => {
|
|
90
|
+
test("console - console config", async () => {
|
|
93
91
|
const config: NodeSDKConfig = {
|
|
94
92
|
...commonConfig,
|
|
95
93
|
protocol: "console",
|
|
96
94
|
diagLogLevel: "NONE",
|
|
95
|
+
spanAttributes: {
|
|
96
|
+
name: "custom-name",
|
|
97
|
+
},
|
|
97
98
|
};
|
|
98
99
|
|
|
99
|
-
const sdk: NodeSDK = buildNodeInstrumentation(config)!;
|
|
100
|
+
const sdk: NodeSDK = await buildNodeInstrumentation(config)!;
|
|
100
101
|
assert.ok(sdk);
|
|
101
102
|
|
|
102
103
|
const _configuration = sdk["_configuration"];
|
|
@@ -106,6 +107,7 @@ describe("verify config settings", () => {
|
|
|
106
107
|
|
|
107
108
|
// verify simple log processor for instant console logging
|
|
108
109
|
assert.equal(logs.length, 2);
|
|
110
|
+
assert.ok(logs[0] instanceof EnrichLogProcessor);
|
|
109
111
|
assert.ok(logs[1] instanceof SimpleLogRecordProcessor);
|
|
110
112
|
|
|
111
113
|
const spans = _configuration.spanProcessors;
|
|
@@ -121,23 +123,54 @@ describe("verify config settings", () => {
|
|
|
121
123
|
);
|
|
122
124
|
});
|
|
123
125
|
|
|
124
|
-
test("single log sending config", () => {
|
|
126
|
+
test("single log sending config", async () => {
|
|
127
|
+
const config: NodeSDKConfig = {
|
|
128
|
+
...commonConfig,
|
|
129
|
+
protocol: "grpc",
|
|
130
|
+
diagLogLevel: "NONE",
|
|
131
|
+
collectorMode: "single",
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const sdk: NodeSDK | undefined = await buildNodeInstrumentation(config);
|
|
135
|
+
|
|
136
|
+
assert.ok(sdk);
|
|
137
|
+
|
|
138
|
+
const _configuration = sdk["_configuration"];
|
|
139
|
+
|
|
140
|
+
const logRecordProcessors = _configuration.logRecordProcessors;
|
|
141
|
+
assert.equal(logRecordProcessors.length, 1);
|
|
142
|
+
assert.ok(
|
|
143
|
+
logRecordProcessors[logRecordProcessors.length - 1] instanceof
|
|
144
|
+
SimpleLogRecordProcessor,
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("pii exporter decorator injected by default", async () => {
|
|
125
149
|
const config: NodeSDKConfig = {
|
|
126
150
|
...commonConfig,
|
|
127
151
|
protocol: "grpc",
|
|
128
152
|
diagLogLevel: "NONE",
|
|
129
153
|
collectorMode: "single",
|
|
154
|
+
detection: {},
|
|
130
155
|
};
|
|
131
156
|
|
|
132
|
-
const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
|
|
157
|
+
const sdk: NodeSDK | undefined = await buildNodeInstrumentation(config);
|
|
133
158
|
|
|
134
159
|
assert.ok(sdk);
|
|
135
160
|
|
|
136
161
|
const _configuration = sdk["_configuration"];
|
|
137
162
|
|
|
138
163
|
const logRecordProcessors = _configuration.logRecordProcessors;
|
|
139
|
-
assert.equal(logRecordProcessors.length,
|
|
140
|
-
assert.ok(
|
|
164
|
+
assert.equal(logRecordProcessors.length, 1);
|
|
165
|
+
assert.ok(
|
|
166
|
+
logRecordProcessors[logRecordProcessors.length - 1] instanceof
|
|
167
|
+
SimpleLogRecordProcessor,
|
|
168
|
+
);
|
|
169
|
+
assert.ok(
|
|
170
|
+
logRecordProcessors[logRecordProcessors.length - 1][
|
|
171
|
+
"_exporter"
|
|
172
|
+
] instanceof PIIExporterDecorator,
|
|
173
|
+
);
|
|
141
174
|
});
|
|
142
175
|
|
|
143
176
|
test("check if clear base endpoint final slash", () => {
|
|
@@ -149,4 +182,9 @@ describe("verify config settings", () => {
|
|
|
149
182
|
|
|
150
183
|
expect(config.collectorUrl).toBe("http://example.com");
|
|
151
184
|
});
|
|
185
|
+
|
|
186
|
+
afterAll(() => {
|
|
187
|
+
// shutdown every instrumentation
|
|
188
|
+
process.emit("SIGTERM");
|
|
189
|
+
});
|
|
152
190
|
});
|
|
@@ -1,59 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
AttributeValue,
|
|
3
|
-
Context,
|
|
4
|
-
Exception,
|
|
5
|
-
Link,
|
|
6
|
-
Span,
|
|
7
|
-
SpanAttributes,
|
|
8
|
-
SpanContext,
|
|
9
|
-
SpanStatus,
|
|
10
|
-
TimeInput,
|
|
11
|
-
} from "@opentelemetry/api";
|
|
1
|
+
import { Context } from "@opentelemetry/api";
|
|
12
2
|
import { describe, expect, it } from "vitest";
|
|
13
3
|
import { EnrichSpanProcessor } from "../../lib/processor/enrich-span-processor.js";
|
|
14
|
-
|
|
15
|
-
class MockSpan implements Span {
|
|
16
|
-
public attributes: Record<string, AttributeValue> = {};
|
|
17
|
-
|
|
18
|
-
spanContext(): SpanContext {
|
|
19
|
-
throw new Error("Method not implemented.");
|
|
20
|
-
}
|
|
21
|
-
setAttribute(key: string, value: AttributeValue): this {
|
|
22
|
-
this.attributes[key] = value;
|
|
23
|
-
return this;
|
|
24
|
-
}
|
|
25
|
-
setAttributes(attributes: SpanAttributes): this {
|
|
26
|
-
throw new Error("Method not implemented.");
|
|
27
|
-
}
|
|
28
|
-
addEvent(
|
|
29
|
-
name: string,
|
|
30
|
-
attributesOrStartTime?: SpanAttributes | TimeInput,
|
|
31
|
-
startTime?: TimeInput,
|
|
32
|
-
): this {
|
|
33
|
-
throw new Error("Method not implemented.");
|
|
34
|
-
}
|
|
35
|
-
addLink(link: Link): this {
|
|
36
|
-
throw new Error("Method not implemented.");
|
|
37
|
-
}
|
|
38
|
-
addLinks(links: Link[]): this {
|
|
39
|
-
throw new Error("Method not implemented.");
|
|
40
|
-
}
|
|
41
|
-
setStatus(status: SpanStatus): this {
|
|
42
|
-
throw new Error("Method not implemented.");
|
|
43
|
-
}
|
|
44
|
-
updateName(name: string): this {
|
|
45
|
-
throw new Error("Method not implemented.");
|
|
46
|
-
}
|
|
47
|
-
end(endTime?: TimeInput): void {
|
|
48
|
-
throw new Error("Method not implemented.");
|
|
49
|
-
}
|
|
50
|
-
isRecording(): boolean {
|
|
51
|
-
throw new Error("Method not implemented.");
|
|
52
|
-
}
|
|
53
|
-
recordException(exception: Exception, time?: TimeInput): void {
|
|
54
|
-
throw new Error("Method not implemented.");
|
|
55
|
-
}
|
|
56
|
-
}
|
|
4
|
+
import { MockSpan } from "../utils/mock-signals.js";
|
|
57
5
|
|
|
58
6
|
describe("EnrichSpanProcessor", () => {
|
|
59
7
|
it("should set static attributes on span", () => {
|
package/test/resource.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { describe, test, expect
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
2
|
import { ObservabilityResourceDetector } from "../lib/resource";
|
|
3
3
|
|
|
4
4
|
describe("ObservabilityResourceDetector", () => {
|
|
@@ -19,4 +19,15 @@ describe("ObservabilityResourceDetector", () => {
|
|
|
19
19
|
expect(result.attributes!["o11y.sdk.name"]).eq("@ogcio/o11y-sdk-node");
|
|
20
20
|
expect(result.attributes).toHaveProperty("o11y.sdk.version");
|
|
21
21
|
});
|
|
22
|
+
|
|
23
|
+
test("should return default resource attribute", () => {
|
|
24
|
+
const detector = new ObservabilityResourceDetector();
|
|
25
|
+
const result = detector.detect();
|
|
26
|
+
|
|
27
|
+
expect(result.attributes).not.toBeNull();
|
|
28
|
+
// default
|
|
29
|
+
expect(result.attributes).toHaveProperty("o11y.sdk.name");
|
|
30
|
+
expect(result.attributes!["o11y.sdk.name"]).eq("@ogcio/o11y-sdk-node");
|
|
31
|
+
expect(result.attributes).toHaveProperty("o11y.sdk.version");
|
|
32
|
+
});
|
|
22
33
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import * as piiDetection from "../../lib/internals/pii-detection.js";
|
|
3
|
+
import { getActiveSpan } from "../../lib/traces.js";
|
|
4
|
+
import { MockSpan } from "../utils/mock-signals.js";
|
|
5
|
+
import { setNodeSdkConfig } from "../../lib/config-manager.js";
|
|
6
|
+
|
|
7
|
+
describe("getActiveSpan", () => {
|
|
8
|
+
it("returns undefined if no active span", async () => {
|
|
9
|
+
setNodeSdkConfig({
|
|
10
|
+
serviceName: "unit-test",
|
|
11
|
+
collectorUrl: "http://collector",
|
|
12
|
+
detection: {
|
|
13
|
+
email: false,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Temporarily override
|
|
18
|
+
const opentelemetry = await import("@opentelemetry/api");
|
|
19
|
+
|
|
20
|
+
vi.spyOn(opentelemetry.trace, "getActiveSpan").mockImplementationOnce(
|
|
21
|
+
() => undefined,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const span = getActiveSpan();
|
|
25
|
+
|
|
26
|
+
expect(span).toBeUndefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { Span, SpanOptions, SpanStatusCode, trace } from "@opentelemetry/api";
|
|
2
|
+
import { TraceState } from "@opentelemetry/core";
|
|
3
|
+
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
4
|
+
import {
|
|
5
|
+
InMemorySpanExporter,
|
|
6
|
+
SimpleSpanProcessor,
|
|
7
|
+
SpanProcessor,
|
|
8
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
9
|
+
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
10
|
+
import { setNodeSdkConfig } from "../../lib/config-manager.js";
|
|
11
|
+
import { getActiveSpan, withSpan } from "../../lib/traces.js";
|
|
12
|
+
|
|
13
|
+
describe("withSpan", () => {
|
|
14
|
+
let memoryExporter: InMemorySpanExporter;
|
|
15
|
+
let spanProcessor: SpanProcessor;
|
|
16
|
+
let sdk: NodeSDK;
|
|
17
|
+
|
|
18
|
+
beforeAll(() => {
|
|
19
|
+
memoryExporter = new InMemorySpanExporter();
|
|
20
|
+
spanProcessor = new SimpleSpanProcessor(memoryExporter);
|
|
21
|
+
sdk = new NodeSDK({
|
|
22
|
+
spanProcessors: [spanProcessor],
|
|
23
|
+
instrumentations: [],
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
setNodeSdkConfig({
|
|
27
|
+
collectorUrl: "http://localhost:4317",
|
|
28
|
+
});
|
|
29
|
+
sdk.start();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(async () => {
|
|
33
|
+
// Flush any remaining spans
|
|
34
|
+
await spanProcessor.forceFlush();
|
|
35
|
+
// Clean up
|
|
36
|
+
memoryExporter.reset();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
afterAll(async () => {
|
|
40
|
+
await sdk.shutdown();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should handle simple synchronous usage", async ({}) => {
|
|
44
|
+
let capturedSpan: Span;
|
|
45
|
+
|
|
46
|
+
await withSpan({
|
|
47
|
+
spanName: "test-sync-span",
|
|
48
|
+
fn: (span: Span) => {
|
|
49
|
+
capturedSpan = span;
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await spanProcessor.forceFlush();
|
|
54
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
55
|
+
expect(spans).toHaveLength(1);
|
|
56
|
+
expect(spans[0].name).toBe("test-sync-span");
|
|
57
|
+
expect(spans[0].status.code).toBe(SpanStatusCode.OK);
|
|
58
|
+
expect(capturedSpan).toBeTruthy();
|
|
59
|
+
expect(capturedSpan.spanContext().traceId).toBe(
|
|
60
|
+
spans[0].spanContext().traceId,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should handle synchronous functions that throw errors", async ({}) => {
|
|
65
|
+
const error = new Error("Sync error");
|
|
66
|
+
|
|
67
|
+
await expect(
|
|
68
|
+
withSpan({
|
|
69
|
+
spanName: "test-sync-error-span",
|
|
70
|
+
fn: () => {
|
|
71
|
+
throw error;
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
).rejects.toThrow(error.message);
|
|
75
|
+
|
|
76
|
+
await spanProcessor.forceFlush();
|
|
77
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
78
|
+
expect(spans).toHaveLength(1);
|
|
79
|
+
expect(spans[0].name).toBe("test-sync-error-span");
|
|
80
|
+
expect(spans[0].status.code).toBe(SpanStatusCode.ERROR);
|
|
81
|
+
expect(spans[0].status.message).toBe(error.message);
|
|
82
|
+
expect(spans[0].events).toHaveLength(1);
|
|
83
|
+
expect(spans[0].events[0].name).toBe("exception");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle asynchronous functions correctly", async ({}) => {
|
|
87
|
+
let capturedSpan: Span;
|
|
88
|
+
|
|
89
|
+
await withSpan({
|
|
90
|
+
spanName: "test-async-span",
|
|
91
|
+
fn: async (span: Span) => {
|
|
92
|
+
capturedSpan = span;
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
94
|
+
return "async-result";
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await spanProcessor.forceFlush();
|
|
99
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
100
|
+
expect(spans).toHaveLength(1);
|
|
101
|
+
expect(spans[0].name).toBe("test-async-span");
|
|
102
|
+
expect(spans[0].status.code).toBe(SpanStatusCode.OK);
|
|
103
|
+
expect(capturedSpan).toBeTruthy();
|
|
104
|
+
expect(capturedSpan.spanContext().traceId).toBe(
|
|
105
|
+
spans[0].spanContext().traceId,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("should handle asynchronous functions that reject", async () => {
|
|
110
|
+
const error = new Error("Async error");
|
|
111
|
+
|
|
112
|
+
await expect(
|
|
113
|
+
withSpan({
|
|
114
|
+
spanName: "test-async-error-span",
|
|
115
|
+
fn: async () => {
|
|
116
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
117
|
+
throw error;
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
).rejects.toThrow(error.message);
|
|
121
|
+
|
|
122
|
+
await spanProcessor.forceFlush();
|
|
123
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
124
|
+
expect(spans).toHaveLength(1);
|
|
125
|
+
expect(spans[0].name).toBe("test-async-error-span");
|
|
126
|
+
expect(spans[0].status.code).toBe(SpanStatusCode.ERROR);
|
|
127
|
+
expect(spans[0].status.message).toBe(error.message);
|
|
128
|
+
expect(spans[0].events).toHaveLength(1);
|
|
129
|
+
expect(spans[0].events[0].name).toBe("exception");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should handle non-Error exceptions", async () => {
|
|
133
|
+
const nonErrorException = { message: "Not an error object", code: 500 };
|
|
134
|
+
|
|
135
|
+
await expect(
|
|
136
|
+
withSpan({
|
|
137
|
+
spanName: "test-non-error-span",
|
|
138
|
+
fn: () => {
|
|
139
|
+
throw nonErrorException;
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
).rejects.toEqual(nonErrorException);
|
|
143
|
+
|
|
144
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
145
|
+
expect(spans).toHaveLength(1);
|
|
146
|
+
expect(spans[0].status.code).toBe(SpanStatusCode.ERROR);
|
|
147
|
+
expect(spans[0].status.message).toBe(JSON.stringify(nonErrorException));
|
|
148
|
+
expect(spans[0].events).toHaveLength(1);
|
|
149
|
+
expect(spans[0].events[0].name).toBe("exception");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should ensure span is ended even when errors occur", async () => {
|
|
153
|
+
const error = new Error("Test error");
|
|
154
|
+
|
|
155
|
+
await expect(
|
|
156
|
+
withSpan({
|
|
157
|
+
spanName: "test-finally-span",
|
|
158
|
+
fn: () => {
|
|
159
|
+
throw error;
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
).rejects.toThrow("Test error");
|
|
163
|
+
|
|
164
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
165
|
+
expect(spans).toHaveLength(1);
|
|
166
|
+
expect(spans[0].ended).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should pass span options to tracer", async () => {
|
|
170
|
+
const spanOptions: SpanOptions = {
|
|
171
|
+
attributes: { "custom.attribute": "custom-value" },
|
|
172
|
+
kind: 1,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
await withSpan({
|
|
176
|
+
spanName: "test-options-span",
|
|
177
|
+
spanOptions,
|
|
178
|
+
fn: () => "result",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await spanProcessor.forceFlush();
|
|
182
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
183
|
+
expect(spans).toHaveLength(1);
|
|
184
|
+
expect(spans[0].attributes["custom.attribute"]).toBe("custom-value");
|
|
185
|
+
expect(spans[0].kind).toBe(1);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("should use custom tracer name", async () => {
|
|
189
|
+
const customTracerName = "custom-tracer";
|
|
190
|
+
|
|
191
|
+
await withSpan({
|
|
192
|
+
traceName: customTracerName,
|
|
193
|
+
spanName: "test-custom-tracer-span",
|
|
194
|
+
fn: () => "result",
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
await spanProcessor.forceFlush();
|
|
198
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
199
|
+
expect(spans).toHaveLength(1);
|
|
200
|
+
expect(spans[0].name).toBe("test-custom-tracer-span");
|
|
201
|
+
expect(spans[0].instrumentationScope.name).toBe(customTracerName);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should use default tracer name when not specified", async () => {
|
|
205
|
+
await withSpan({
|
|
206
|
+
spanName: "test-default-tracer-span",
|
|
207
|
+
fn: () => "result",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
await spanProcessor.forceFlush();
|
|
211
|
+
const defaultSpans = memoryExporter.getFinishedSpans();
|
|
212
|
+
expect(defaultSpans).toHaveLength(1);
|
|
213
|
+
expect(defaultSpans[0].name).toBe("test-default-tracer-span");
|
|
214
|
+
expect(defaultSpans[0].instrumentationScope.name).toBe("o11y-sdk");
|
|
215
|
+
|
|
216
|
+
memoryExporter.reset();
|
|
217
|
+
|
|
218
|
+
setNodeSdkConfig({
|
|
219
|
+
collectorUrl: "",
|
|
220
|
+
serviceName: "test-service",
|
|
221
|
+
serviceVersion: "v1.0.0",
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
await withSpan({
|
|
225
|
+
spanName: "test-default-tracer-span",
|
|
226
|
+
fn: () => "result",
|
|
227
|
+
});
|
|
228
|
+
await spanProcessor.forceFlush();
|
|
229
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
230
|
+
expect(spans).toHaveLength(1);
|
|
231
|
+
expect(spans[0].name).toBe("test-default-tracer-span");
|
|
232
|
+
expect(spans[0].instrumentationScope.name).toBe("test-service");
|
|
233
|
+
expect(spans[0].instrumentationScope.version).toBe("v1.0.0");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should allow function to interact with span context", async () => {
|
|
237
|
+
let receivedSpan: Span;
|
|
238
|
+
|
|
239
|
+
await withSpan({
|
|
240
|
+
spanName: "test-span-context",
|
|
241
|
+
fn: (span: Span) => {
|
|
242
|
+
receivedSpan = span;
|
|
243
|
+
span.spanContext().traceState = new TraceState(
|
|
244
|
+
"alpha=aaaaaaaaaaaa,bravo=bbbbbbbbbbbb",
|
|
245
|
+
);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
await spanProcessor.forceFlush();
|
|
250
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
251
|
+
expect(spans).toHaveLength(1);
|
|
252
|
+
expect(receivedSpan).toBeTruthy();
|
|
253
|
+
expect(receivedSpan.spanContext().traceId).toBe(
|
|
254
|
+
spans[0].spanContext().traceId,
|
|
255
|
+
);
|
|
256
|
+
expect(receivedSpan.spanContext().spanId).toBe(
|
|
257
|
+
spans[0].spanContext().spanId,
|
|
258
|
+
);
|
|
259
|
+
expect(spans[0].spanContext().traceState.serialize()).toStrictEqual(
|
|
260
|
+
"alpha=aaaaaaaaaaaa,bravo=bbbbbbbbbbbb",
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should preserve context across setTimeout", async () => {
|
|
265
|
+
await withSpan({
|
|
266
|
+
spanName: "test-timeout-context",
|
|
267
|
+
fn: async (span: Span) => {
|
|
268
|
+
return new Promise((resolve) => {
|
|
269
|
+
setTimeout(() => {
|
|
270
|
+
getActiveSpan().addEvent("promise-resolved", {
|
|
271
|
+
result: "timeout-result",
|
|
272
|
+
});
|
|
273
|
+
resolve("timeout-result");
|
|
274
|
+
}, 10);
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
let newSpan: Span;
|
|
280
|
+
await trace
|
|
281
|
+
.getTracer("some-tracer")
|
|
282
|
+
.startActiveSpan("other-context", async (span) => {
|
|
283
|
+
newSpan = span;
|
|
284
|
+
span.addEvent("other-context-event", {
|
|
285
|
+
result: "other-context-result",
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
newSpan.addEvent("another-context-event", {
|
|
290
|
+
result: "another-context-result",
|
|
291
|
+
});
|
|
292
|
+
newSpan.setStatus({ code: SpanStatusCode.OK });
|
|
293
|
+
newSpan.end();
|
|
294
|
+
await spanProcessor.forceFlush();
|
|
295
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
296
|
+
expect(spans).toHaveLength(2);
|
|
297
|
+
|
|
298
|
+
const timeoutSpan = spans.find((s) => s.name === "test-timeout-context");
|
|
299
|
+
expect(timeoutSpan.status.code).toBe(SpanStatusCode.OK);
|
|
300
|
+
expect(timeoutSpan.events).toHaveLength(1);
|
|
301
|
+
expect(timeoutSpan.events[0].name).toStrictEqual("promise-resolved");
|
|
302
|
+
|
|
303
|
+
const otherTrackedSpan = spans.find((s) => s.name === "other-context");
|
|
304
|
+
expect(otherTrackedSpan.status.code).toBe(SpanStatusCode.OK);
|
|
305
|
+
expect(otherTrackedSpan.events).toHaveLength(2);
|
|
306
|
+
expect(otherTrackedSpan.events[0].name).toStrictEqual(
|
|
307
|
+
"other-context-event",
|
|
308
|
+
);
|
|
309
|
+
expect(otherTrackedSpan.events[1].name).toStrictEqual(
|
|
310
|
+
"another-context-event",
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("should handle nested spans correctly", async () => {
|
|
315
|
+
await withSpan({
|
|
316
|
+
spanName: "outer-span",
|
|
317
|
+
fn: async () => {
|
|
318
|
+
await withSpan({
|
|
319
|
+
spanName: "inner-span",
|
|
320
|
+
fn: async () => {
|
|
321
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
await spanProcessor.forceFlush();
|
|
328
|
+
const spans = memoryExporter.getFinishedSpans();
|
|
329
|
+
expect(spans).toHaveLength(2);
|
|
330
|
+
|
|
331
|
+
const innerSpan = spans.find((s) => s.name === "inner-span");
|
|
332
|
+
const outerSpan = spans.find((s) => s.name === "outer-span");
|
|
333
|
+
|
|
334
|
+
expect(innerSpan).toBeTruthy();
|
|
335
|
+
expect(outerSpan).toBeTruthy();
|
|
336
|
+
expect(innerSpan!.parentSpanContext.spanId).toBe(
|
|
337
|
+
outerSpan!.spanContext().spanId,
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
@@ -40,6 +40,13 @@ export function parseLog(
|
|
|
40
40
|
// Additional metadata at the end, store it separately
|
|
41
41
|
jsonObject["metadata"] = line;
|
|
42
42
|
}
|
|
43
|
+
|
|
44
|
+
if (line.includes("Body:")) {
|
|
45
|
+
const match = line.match(/Body:\s+(\w+)\((.+)\)/);
|
|
46
|
+
if (match) {
|
|
47
|
+
jsonObject["log_body"] = match[2];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
43
50
|
});
|
|
44
51
|
|
|
45
52
|
return jsonObject;
|