@ogcio/o11y-sdk-node 0.4.1 → 0.6.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.
Files changed (136) hide show
  1. package/dist/sdk-core/index.d.ts +1 -0
  2. package/dist/sdk-core/index.js +1 -0
  3. package/dist/sdk-core/lib/index.d.ts +2 -0
  4. package/dist/sdk-core/lib/index.js +2 -0
  5. package/dist/sdk-core/lib/redaction/basic-redactor.d.ts +7 -0
  6. package/dist/sdk-core/lib/redaction/basic-redactor.js +18 -0
  7. package/dist/sdk-core/lib/redaction/email-redactor.d.ts +8 -0
  8. package/dist/sdk-core/lib/redaction/email-redactor.js +17 -0
  9. package/dist/sdk-core/lib/redaction/index.d.ts +9 -0
  10. package/dist/sdk-core/lib/redaction/index.js +4 -0
  11. package/dist/sdk-core/lib/redaction/ip-redactor.d.ts +9 -0
  12. package/dist/sdk-core/lib/redaction/ip-redactor.js +23 -0
  13. package/dist/sdk-core/lib/redaction/ppsn-redactor.d.ts +8 -0
  14. package/dist/sdk-core/lib/redaction/ppsn-redactor.js +17 -0
  15. package/dist/sdk-core/lib/utils/data-structures.d.ts +15 -0
  16. package/dist/{lib/internals/redaction/pii-detection.js → sdk-core/lib/utils/data-structures.js} +4 -27
  17. package/dist/sdk-core/lib/utils/index.d.ts +2 -0
  18. package/dist/sdk-core/lib/utils/index.js +2 -0
  19. package/dist/sdk-core/lib/utils/string-decoding.d.ts +7 -0
  20. package/dist/sdk-core/lib/utils/string-decoding.js +22 -0
  21. package/dist/{index.d.ts → sdk-node/index.d.ts} +1 -0
  22. package/dist/{index.js → sdk-node/index.js} +1 -0
  23. package/dist/sdk-node/lib/config-manager.d.ts +3 -0
  24. package/dist/{lib → sdk-node/lib}/config-manager.js +1 -4
  25. package/dist/sdk-node/lib/exporter/console.d.ts +3 -0
  26. package/dist/sdk-node/lib/exporter/grpc.d.ts +3 -0
  27. package/dist/sdk-node/lib/exporter/http.d.ts +3 -0
  28. package/dist/{lib → sdk-node/lib}/exporter/index.d.ts +2 -2
  29. package/dist/{lib → sdk-node/lib}/exporter/pii-exporter-decorator.d.ts +6 -6
  30. package/dist/{lib → sdk-node/lib}/exporter/pii-exporter-decorator.js +7 -3
  31. package/dist/sdk-node/lib/exporter/processor-config.d.ts +7 -0
  32. package/dist/{lib → sdk-node/lib}/exporter/processor-config.js +4 -0
  33. package/dist/{lib → sdk-node/lib}/index.d.ts +11 -0
  34. package/dist/{lib → sdk-node/lib}/instrumentation.node.js +1 -1
  35. package/dist/sdk-node/lib/internals/redaction/redactors/email.d.ts +8 -0
  36. package/dist/sdk-node/lib/internals/redaction/redactors/email.js +19 -0
  37. package/dist/sdk-node/lib/internals/redaction/redactors/index.d.ts +16 -0
  38. package/dist/sdk-node/lib/internals/redaction/redactors/index.js +13 -0
  39. package/dist/sdk-node/lib/internals/redaction/redactors/ip.d.ts +8 -0
  40. package/dist/sdk-node/lib/internals/redaction/redactors/ip.js +20 -0
  41. package/dist/sdk-node/lib/internals/redaction/redactors/ppsn.d.ts +8 -0
  42. package/dist/sdk-node/lib/internals/redaction/redactors/ppsn.js +18 -0
  43. package/dist/{lib → sdk-node/lib}/metrics.d.ts +1 -1
  44. package/dist/{lib → sdk-node/lib}/processor/enrich-logger-processor.d.ts +3 -3
  45. package/dist/{lib → sdk-node/lib}/processor/enrich-span-processor.d.ts +3 -3
  46. package/dist/sdk-node/lib/processor/nextjs-logger-processor.d.ts +7 -0
  47. package/dist/sdk-node/lib/processor/nextjs-logger-processor.js +30 -0
  48. package/dist/sdk-node/lib/processor/nextjs-span-processor.d.ts +8 -0
  49. package/dist/sdk-node/lib/processor/nextjs-span-processor.js +25 -0
  50. package/dist/{lib → sdk-node/lib}/resource.d.ts +2 -2
  51. package/dist/{lib → sdk-node/lib}/traces.d.ts +1 -1
  52. package/dist/{lib → sdk-node/lib}/traces.js +2 -2
  53. package/dist/{lib → sdk-node/lib}/url-sampler.d.ts +3 -3
  54. package/dist/{lib → sdk-node/lib}/utils.d.ts +1 -1
  55. package/dist/sdk-node/package.json +62 -0
  56. package/package.json +25 -22
  57. package/CHANGELOG.md +0 -226
  58. package/dist/lib/config-manager.d.ts +0 -3
  59. package/dist/lib/exporter/console.d.ts +0 -3
  60. package/dist/lib/exporter/grpc.d.ts +0 -3
  61. package/dist/lib/exporter/http.d.ts +0 -3
  62. package/dist/lib/exporter/processor-config.d.ts +0 -5
  63. package/dist/lib/internals/redaction/pii-detection.d.ts +0 -25
  64. package/dist/lib/internals/redaction/redactors/email.d.ts +0 -8
  65. package/dist/lib/internals/redaction/redactors/email.js +0 -48
  66. package/dist/lib/internals/redaction/redactors/index.d.ts +0 -4
  67. package/dist/lib/internals/redaction/redactors/index.js +0 -6
  68. package/dist/lib/internals/redaction/redactors/ip.d.ts +0 -10
  69. package/dist/lib/internals/redaction/redactors/ip.js +0 -54
  70. package/dist/lib/internals/shared-metrics.d.ts +0 -7
  71. package/dist/lib/internals/shared-metrics.js +0 -18
  72. package/dist/package.json +0 -59
  73. package/dist/vitest.config.d.ts +0 -2
  74. package/dist/vitest.config.js +0 -45
  75. package/index.ts +0 -9
  76. package/lib/config-manager.ts +0 -16
  77. package/lib/exporter/console.ts +0 -33
  78. package/lib/exporter/grpc.ts +0 -65
  79. package/lib/exporter/http.ts +0 -56
  80. package/lib/exporter/index.ts +0 -9
  81. package/lib/exporter/pii-exporter-decorator.ts +0 -187
  82. package/lib/exporter/processor-config.ts +0 -23
  83. package/lib/index.ts +0 -118
  84. package/lib/instrumentation.node.ts +0 -115
  85. package/lib/internals/hooks.ts +0 -14
  86. package/lib/internals/redaction/pii-detection.ts +0 -113
  87. package/lib/internals/redaction/redactors/email.ts +0 -58
  88. package/lib/internals/redaction/redactors/index.ts +0 -12
  89. package/lib/internals/redaction/redactors/ip.ts +0 -68
  90. package/lib/internals/shared-metrics.ts +0 -34
  91. package/lib/metrics.ts +0 -75
  92. package/lib/processor/enrich-logger-processor.ts +0 -34
  93. package/lib/processor/enrich-span-processor.ts +0 -39
  94. package/lib/resource.ts +0 -30
  95. package/lib/traces.ts +0 -78
  96. package/lib/url-sampler.ts +0 -52
  97. package/lib/utils.ts +0 -22
  98. package/test/config-manager.test.ts +0 -34
  99. package/test/exporter/pii-exporter-decorator.test.ts +0 -88
  100. package/test/index.test.ts +0 -70
  101. package/test/integration/README.md +0 -26
  102. package/test/integration/http-tracing.integration.test.ts +0 -56
  103. package/test/integration/pii.integration.test.ts +0 -68
  104. package/test/integration/run.sh +0 -88
  105. package/test/internals/hooks.test.ts +0 -45
  106. package/test/internals/pii-detection.test.ts +0 -265
  107. package/test/internals/redactors/email.test.ts +0 -81
  108. package/test/internals/redactors/ip.test.ts +0 -93
  109. package/test/internals/shared-metrics.test.ts +0 -34
  110. package/test/metrics.test.ts +0 -142
  111. package/test/node-config.test.ts +0 -190
  112. package/test/processor/enrich-logger-processor.test.ts +0 -58
  113. package/test/processor/enrich-span-processor.test.ts +0 -52
  114. package/test/resource.test.ts +0 -33
  115. package/test/traces/active-span.test.ts +0 -28
  116. package/test/traces/with-span.test.ts +0 -340
  117. package/test/url-sampler.test.ts +0 -215
  118. package/test/utils/alloy-log-parser.ts +0 -53
  119. package/test/utils/mock-signals.ts +0 -144
  120. package/test/validation.test.ts +0 -103
  121. package/tsconfig.json +0 -15
  122. package/vitest.config.ts +0 -46
  123. /package/dist/{lib → sdk-node/lib}/exporter/console.js +0 -0
  124. /package/dist/{lib → sdk-node/lib}/exporter/grpc.js +0 -0
  125. /package/dist/{lib → sdk-node/lib}/exporter/http.js +0 -0
  126. /package/dist/{lib → sdk-node/lib}/exporter/index.js +0 -0
  127. /package/dist/{lib → sdk-node/lib}/index.js +0 -0
  128. /package/dist/{lib → sdk-node/lib}/instrumentation.node.d.ts +0 -0
  129. /package/dist/{lib → sdk-node/lib}/internals/hooks.d.ts +0 -0
  130. /package/dist/{lib → sdk-node/lib}/internals/hooks.js +0 -0
  131. /package/dist/{lib → sdk-node/lib}/metrics.js +0 -0
  132. /package/dist/{lib → sdk-node/lib}/processor/enrich-logger-processor.js +0 -0
  133. /package/dist/{lib → sdk-node/lib}/processor/enrich-span-processor.js +0 -0
  134. /package/dist/{lib → sdk-node/lib}/resource.js +0 -0
  135. /package/dist/{lib → sdk-node/lib}/url-sampler.js +0 -0
  136. /package/dist/{lib → sdk-node/lib}/utils.js +0 -0
package/lib/resource.ts DELETED
@@ -1,30 +0,0 @@
1
- import {
2
- ResourceDetector,
3
- DetectedResource,
4
- DetectedResourceAttributes,
5
- } from "@opentelemetry/resources";
6
- import { SignalAttributeValue } from "./index.js";
7
- import packageJson from "../package.json" with { type: "json" };
8
-
9
- export class ObservabilityResourceDetector implements ResourceDetector {
10
- private _resourceAttributes: Record<string, SignalAttributeValue> | undefined;
11
-
12
- constructor(resourceAttributes?: Record<string, SignalAttributeValue>) {
13
- this._resourceAttributes = resourceAttributes;
14
- }
15
-
16
- detect(): DetectedResource {
17
- let attributes: DetectedResourceAttributes = {};
18
-
19
- if (this._resourceAttributes) {
20
- attributes = {
21
- ...this._resourceAttributes,
22
- };
23
- }
24
-
25
- attributes["o11y.sdk.name"] = packageJson.name;
26
- attributes["o11y.sdk.version"] = packageJson.version;
27
-
28
- return { attributes };
29
- }
30
- }
package/lib/traces.ts DELETED
@@ -1,78 +0,0 @@
1
- import { Span, SpanOptions, SpanStatusCode, trace } from "@opentelemetry/api";
2
- import { getNodeSdkConfig } from "./config-manager.js";
3
-
4
- export type WithSpanParams<T> = {
5
- /**
6
- * The name of the trace the span should belong to.
7
- * NOTE: If you want the new span to belong to an already existing trace, you should provide the same tracer name
8
- */
9
- traceName?: string;
10
- spanName: string;
11
- spanOptions?: SpanOptions;
12
- /** A function defining the task you want to be wrapped by this span */
13
- fn: (span: Span) => T | Promise<T>;
14
- };
15
-
16
- /**
17
- * Generates a function wrapping a given Callable `fn` into an error handling block.
18
- * Setting Span status and recording any caught exception before bubbling it up.
19
- *
20
- * Marks the span as ended once the provided callable has ended or an error has been caught.
21
- *
22
- * @returns {Promise<T>} where T is the type returned by the Callable.
23
- * @throws any error thrown by the original Callable `fn` provided.
24
- */
25
- function selfContainedSpanHandlerGenerator<T>(
26
- fn: (span: Span) => T | Promise<T>,
27
- ): (span: Span) => Promise<T> {
28
- return async (span: Span) => {
29
- try {
30
- const fnResult = await fn(span);
31
- span.setStatus({ code: SpanStatusCode.OK });
32
- return fnResult;
33
- } catch (err) {
34
- if (err instanceof Error) {
35
- span.recordException(err);
36
- span.setStatus({ code: SpanStatusCode.ERROR, message: err.message });
37
- throw err;
38
- }
39
-
40
- span.recordException({ message: JSON.stringify(err) });
41
- span.setStatus({
42
- code: SpanStatusCode.ERROR,
43
- message: JSON.stringify(err),
44
- });
45
- throw err;
46
- } finally {
47
- span.end();
48
- }
49
- };
50
- }
51
-
52
- /**
53
- * Gets the currently active OpenTelemetry span.
54
- *
55
- * @returns {Span | undefined} The active span with redaction logic applied,
56
- * or `undefined` if there is no active span in context.
57
- */
58
- export function getActiveSpan() {
59
- return trace.getActiveSpan();
60
- }
61
-
62
- export function withSpan<T>({
63
- traceName,
64
- spanName,
65
- spanOptions = {},
66
- fn,
67
- }: WithSpanParams<T>) {
68
- const sdkConfig = getNodeSdkConfig();
69
- const tracer = trace.getTracer(
70
- traceName ?? sdkConfig.serviceName ?? "o11y-sdk",
71
- sdkConfig.serviceVersion,
72
- );
73
- return tracer.startActiveSpan(
74
- spanName,
75
- spanOptions,
76
- selfContainedSpanHandlerGenerator<T>(fn),
77
- );
78
- }
@@ -1,52 +0,0 @@
1
- import { Attributes, Context, Link, SpanKind } from "@opentelemetry/api";
2
- import {
3
- Sampler,
4
- SamplingDecision,
5
- SamplingResult,
6
- } from "@opentelemetry/sdk-trace-base";
7
- import { SamplerCondition } from "./index.js";
8
-
9
- export class UrlSampler implements Sampler {
10
- private _samplerCondition: SamplerCondition[];
11
- private _nextSampler: Sampler;
12
-
13
- constructor(samplerCondition: SamplerCondition[] = [], nextSampler: Sampler) {
14
- this._samplerCondition = samplerCondition;
15
- this._nextSampler = nextSampler;
16
- }
17
-
18
- shouldSample(
19
- _context: Context,
20
- traceId: string,
21
- _spanName: string,
22
- _spanKind: SpanKind,
23
- attributes: Attributes,
24
- _links: Link[],
25
- ): SamplingResult {
26
- const url: string | undefined = attributes["http.target"]?.toString();
27
-
28
- if (url) {
29
- for (const condition of this._samplerCondition) {
30
- if (
31
- (condition.type === "equals" && url === condition.url) ||
32
- (condition.type === "endsWith" && url.endsWith(condition.url)) ||
33
- (condition.type === "includes" && url.includes(condition.url))
34
- ) {
35
- return { decision: SamplingDecision.NOT_RECORD };
36
- }
37
- }
38
- }
39
-
40
- return this._nextSampler.shouldSample(
41
- _context,
42
- traceId,
43
- _spanName,
44
- _spanKind,
45
- attributes,
46
- _links,
47
- );
48
- }
49
- toString(): string {
50
- return "UrlSampler";
51
- }
52
- }
package/lib/utils.ts DELETED
@@ -1,22 +0,0 @@
1
- import {
2
- BatchLogRecordProcessor,
3
- SimpleLogRecordProcessor,
4
- } from "@opentelemetry/sdk-logs";
5
- import { SDKCollectorMode } from "./index.js";
6
- import { tracing } from "@opentelemetry/sdk-node";
7
-
8
- export const LogRecordProcessorMap: Record<
9
- SDKCollectorMode,
10
- typeof SimpleLogRecordProcessor | typeof BatchLogRecordProcessor
11
- > = {
12
- single: SimpleLogRecordProcessor,
13
- batch: BatchLogRecordProcessor,
14
- };
15
-
16
- export const SpanProcessorMap: Record<
17
- SDKCollectorMode,
18
- typeof tracing.SimpleSpanProcessor | typeof tracing.BatchSpanProcessor
19
- > = {
20
- single: tracing.SimpleSpanProcessor,
21
- batch: tracing.BatchSpanProcessor,
22
- };
@@ -1,34 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { getNodeSdkConfig, setNodeSdkConfig } from "../lib/config-manager";
3
- import { NodeSDKConfig } from "../lib";
4
-
5
- describe("Config Manager", () => {
6
- it("throws if getConfig is called before initialization", () => {
7
- expect(() => getNodeSdkConfig()).toThrow();
8
- });
9
-
10
- it("sdk defined config is not pollutable", () => {
11
- const config: NodeSDKConfig = {
12
- collectorUrl: "http://example.com",
13
- serviceName: "MyService",
14
- spanAttributes: {
15
- "my.attribute": "value",
16
- },
17
- };
18
-
19
- setNodeSdkConfig(config);
20
-
21
- const cfg = getNodeSdkConfig();
22
-
23
- // Top level
24
- cfg.collectorUrl = "http://example.com/changed";
25
- // Subfield
26
- cfg.spanAttributes["my.attribute"] = "another-attribute";
27
-
28
- // Ensure config values remain unchanged
29
- expect(getNodeSdkConfig().collectorUrl).toStrictEqual(config.collectorUrl);
30
- expect(getNodeSdkConfig().spanAttributes["my.attribute"]).toStrictEqual(
31
- config.spanAttributes["my.attribute"],
32
- );
33
- });
34
- });
@@ -1,88 +0,0 @@
1
- import { ReadableLogRecord } from "@opentelemetry/sdk-logs";
2
- import { ResourceMetrics } from "@opentelemetry/sdk-metrics";
3
- import { ReadableSpan } from "@opentelemetry/sdk-trace-base";
4
- import { beforeEach, describe, expect, it, vi } from "vitest";
5
- import { PIIExporterDecorator } from "../../lib/exporter/pii-exporter-decorator";
6
-
7
- describe("PIIExporterDecorator", () => {
8
- let exporterMock: any;
9
- let config: any;
10
- let piiExporter: PIIExporterDecorator;
11
-
12
- beforeEach(() => {
13
- exporterMock = {
14
- export: vi.fn(),
15
- shutdown: vi.fn(() => Promise.resolve()),
16
- forceFlush: vi.fn(() => Promise.resolve()),
17
- _delegate: {},
18
- };
19
-
20
- config = { detection: { email: true } };
21
- piiExporter = new PIIExporterDecorator(exporterMock, config);
22
- });
23
-
24
- it("should redact emails in span name and attributes", () => {
25
- const items: ReadableSpan[] = [
26
- {
27
- name: "user@example.com",
28
- kind: 0,
29
- spanContext: () => ({}),
30
- attributes: { email: "user@example.com" },
31
- resource: { attributes: { owner: "user@example.com" } },
32
- events: [
33
- {
34
- name: "Login from user@example.com",
35
- attributes: { email: "user@example.com" },
36
- },
37
- ],
38
- } as any,
39
- ];
40
-
41
- const callback = vi.fn();
42
- piiExporter.export(items, callback);
43
-
44
- const exportedSpan = exporterMock.export.mock.calls[0][0][0];
45
- expect(exportedSpan.name).toBe("[REDACTED EMAIL]");
46
- expect(exportedSpan.attributes.email).toBe("[REDACTED EMAIL]");
47
- expect(exportedSpan.resource.attributes.owner).toBe("[REDACTED EMAIL]");
48
- expect(exportedSpan.events[0].name).toBe("Login from [REDACTED EMAIL]");
49
- expect(exportedSpan.events[0].attributes.email).toBe("[REDACTED EMAIL]");
50
- });
51
-
52
- it("should redact emails in log records", () => {
53
- const items: ReadableLogRecord[] = [
54
- {
55
- body: "Error from user@example.com",
56
- attributes: { email: "user@example.com" },
57
- severityText: "INFO",
58
- severityNumber: 1,
59
- resource: { attributes: { owner: "user@example.com" } },
60
- } as any,
61
- ];
62
-
63
- const callback = vi.fn();
64
- piiExporter.export(items, callback);
65
-
66
- const exportedLog = exporterMock.export.mock.calls[0][0][0];
67
- expect(exportedLog.body).toBe("Error from [REDACTED EMAIL]");
68
- expect(exportedLog.attributes.email).toBe("[REDACTED EMAIL]");
69
- expect(exportedLog.resource.attributes.owner).toBe("[REDACTED EMAIL]");
70
- });
71
-
72
- it("should redact emails in resource metrics", () => {
73
- const metrics: ResourceMetrics = {
74
- resource: {
75
- attributes: { maintainer: "user@example.com" },
76
- },
77
- scopeMetrics: [],
78
- } as any;
79
-
80
- const callback = vi.fn();
81
- piiExporter.export(metrics, callback);
82
-
83
- const exportedMetric = exporterMock.export.mock.calls[0][0];
84
- expect(exportedMetric.resource.attributes.maintainer).toBe(
85
- "[REDACTED EMAIL]",
86
- );
87
- });
88
- });
@@ -1,70 +0,0 @@
1
- import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
2
- import { NodeSDKConfig } from "../index";
3
- import { instrumentNode } from "../index";
4
- import * as buildNodeInstrumentationModule from "../lib/instrumentation.node";
5
- import { metrics } from "@opentelemetry/sdk-node";
6
-
7
- describe("instrumentNode", () => {
8
- beforeEach(() => {
9
- // @ts-ignore Avoid actually running exporters at any time in tests (overriding private method)
10
- vi.spyOn(
11
- metrics.PeriodicExportingMetricReader.prototype,
12
- "_doRun",
13
- ).mockImplementation(vi.fn());
14
- });
15
-
16
- afterEach(() => {
17
- vi.restoreAllMocks();
18
- });
19
-
20
- test("should call buildNodeInstrumentation with the provided config", async () => {
21
- const instrumentationMock = vi
22
- .spyOn(buildNodeInstrumentationModule, "default")
23
- .mockImplementation(vi.fn());
24
-
25
- const config: NodeSDKConfig = {
26
- serviceName: "custom-service",
27
- collectorUrl: "http://custom-collector.com",
28
- protocol: "grpc",
29
- resourceAttributes: {
30
- "team.infra.cluster": "dev-01",
31
- "team.infra.pod": "01",
32
- "team.service.type": "fastify",
33
- },
34
- spanAttributes: {
35
- "signal.namespace": "example",
36
- "signal.number": () => "callback",
37
- },
38
- };
39
-
40
- await instrumentNode(config);
41
-
42
- expect(instrumentationMock).toHaveBeenCalledWith(config);
43
- });
44
-
45
- test("should not throw when called without arguments", async () => {
46
- await expect(instrumentNode()).resolves.not.toThrow();
47
- });
48
-
49
- test("should invoke instrumentation shutdown on SIGTERM", async () => {
50
- const config: NodeSDKConfig = {
51
- serviceName: "custom-service",
52
- collectorUrl: "http://custom-collector.com",
53
- protocol: "grpc",
54
- resourceAttributes: {
55
- "team.infra.cluster": "dev-01",
56
- "team.infra.pod": "01",
57
- "team.service.type": "fastify",
58
- },
59
- spanAttributes: {
60
- "signal.namespace": "example",
61
- "signal.number": () => "callback",
62
- },
63
- };
64
-
65
- const sdk = await instrumentNode(config);
66
- const shutdownMock = vi.spyOn(sdk, "shutdown");
67
- process.emit("SIGTERM");
68
- expect(shutdownMock).toHaveBeenCalled();
69
- });
70
- });
@@ -1,26 +0,0 @@
1
- # Integration Test
2
-
3
- This folder contains a setup for integration test with o11y node sdk.
4
-
5
- ## Workflow
6
-
7
- - Docker must be in running state
8
- - Run the sh script `sh ./packages/sdk-node/test/integration/run.sh 1 .` from project root with following params
9
- 1. pipeline build number, for local development, any number or string is fine
10
- 2. root folder for docker context
11
- - Change dir to `packages/sdk-node/`
12
- - Run full test suite with `pnpm test`
13
-
14
- ## Script
15
-
16
- The `run.sh` script performs the following steps:
17
-
18
- - build a docker image of a fastify app `/examples/fastify`
19
- - setup an temporary test docker network
20
- - run grafana alloy inside a docker container with a test configuration `/alloy/integration-test.alloy`
21
- - ensure is running otherwise exit process
22
- - run fastify app in a docker container
23
- - ensure is running otherwise exit process
24
- - execute some curl to the fastify microservice
25
- - persist alloy log to a file and save to following path `/packages/sdk-node/test/integration/`
26
- - docker turn down process (containers/network/image)
@@ -1,56 +0,0 @@
1
- import { describe, test, assert } from "vitest";
2
- import { parseLog } from "../utils/alloy-log-parser";
3
- import { readFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
-
6
- describe("instrumentation integration test", () => {
7
- test("should exclude health url and process only dummy calls", async () => {
8
- const data = await readFile(join(__dirname, "logs.txt"), "utf-8");
9
-
10
- let health_traces_counter = 0;
11
- let dummy_traces_counter = 0;
12
-
13
- for (const line of data.split(/\nts=/)) {
14
- const parsedLine: Record<string, object | string | number> =
15
- parseLog(line);
16
-
17
- if (
18
- parsedLine["attributes"] &&
19
- parsedLine["attributes"]["span_kind"] &&
20
- parsedLine["attributes"]["span_kind"] === "trace"
21
- ) {
22
- if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
23
- dummy_traces_counter++;
24
-
25
- // verify global sdk span resource
26
- assert.equal(
27
- parsedLine["resource_attributes"]["team.infra.pod"],
28
- "01",
29
- );
30
- assert.equal(
31
- parsedLine["resource_attributes"]["team.service.type"],
32
- "fastify",
33
- );
34
-
35
- // verify global sdk span attributes
36
- assert.equal(parsedLine["attributes"]["signal.namespace"], "example");
37
-
38
- // verify runtime custom span inside dev application
39
- assert.equal(parsedLine["attributes"]["business.info"], "dummy");
40
- assert.equal(
41
- parsedLine["attributes"]["business.request_type"],
42
- "application/json",
43
- );
44
-
45
- continue;
46
- }
47
- if (parsedLine["attributes"]["http.target"] === "/api/health") {
48
- health_traces_counter++;
49
- }
50
- }
51
- }
52
-
53
- assert.equal(health_traces_counter, 0);
54
- assert.equal(dummy_traces_counter, 2);
55
- });
56
- });
@@ -1,68 +0,0 @@
1
- import { describe, test, assert } from "vitest";
2
- import { parseLog } from "../utils/alloy-log-parser";
3
- import { readFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
-
6
- describe("pii integration test", () => {
7
- test("should check redacted attributes and detect zero emails in logs", async () => {
8
- const data = await readFile(join(__dirname, "logs.txt"), "utf-8");
9
-
10
- let health_traces_counter = 0;
11
- let dummy_traces_counter = 0;
12
- let email_not_redacted = 0;
13
-
14
- for (const line of data.split(/\nts=/)) {
15
- const parsedLine: Record<string, object | string | number> =
16
- parseLog(line);
17
-
18
- if (
19
- parsedLine["attributes"] &&
20
- parsedLine["attributes"]["span_kind"] &&
21
- parsedLine["attributes"]["span_kind"] === "log"
22
- ) {
23
- const matches = (parsedLine["log_body"] as string).match(
24
- /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
25
- );
26
- if (matches) {
27
- email_not_redacted++;
28
- }
29
- }
30
-
31
- if (
32
- parsedLine["attributes"] &&
33
- parsedLine["attributes"]["span_kind"] &&
34
- parsedLine["attributes"]["span_kind"] === "trace"
35
- ) {
36
- if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
37
- dummy_traces_counter++;
38
-
39
- // verify global sdk span resource
40
- assert.equal(
41
- parsedLine["resource_attributes"]["email"],
42
- "[REDACTED EMAIL]",
43
- );
44
-
45
- // verify global sdk span attributes
46
- assert.equal(parsedLine["attributes"]["email"], "[REDACTED EMAIL]");
47
- // verify custom runtime span attributes
48
- assert.equal(
49
- parsedLine["attributes"]["multiple.email"],
50
- "[REDACTED EMAIL]",
51
- );
52
-
53
- // verify global sdk span attributes
54
- assert.equal(parsedLine["events"]["email"], "[REDACTED EMAIL]");
55
-
56
- continue;
57
- }
58
- if (parsedLine["attributes"]["http.target"] === "/api/health") {
59
- health_traces_counter++;
60
- }
61
- }
62
- }
63
-
64
- assert.equal(email_not_redacted, 0);
65
- assert.equal(health_traces_counter, 0);
66
- assert.equal(dummy_traces_counter, 2);
67
- });
68
- });
@@ -1,88 +0,0 @@
1
- BUILD_ID=$1
2
- ROOT_PATH=$2
3
-
4
- NETWORK_NAME="${BUILD_ID}_testnetwork"
5
- ALLOY_CONTAINER_NAME="integrationalloy"
6
- NODE_CONTAINER_NAME="${BUILD_ID}_fastify_app"
7
- ERROR_CODE=0
8
-
9
- docker build -t ${NODE_CONTAINER_NAME}:${BUILD_ID} -f $ROOT_PATH/examples/fastify/Dockerfile $ROOT_PATH/
10
-
11
- docker network create $NETWORK_NAME
12
-
13
- docker run -d \
14
- -v "$ROOT_PATH/alloy/integration-test.alloy:/etc/alloy/config.alloy:ro" \
15
- --network $NETWORK_NAME \
16
- --name $ALLOY_CONTAINER_NAME \
17
- -p 4317:4317 \
18
- -p 4318:4318 \
19
- grafana/alloy:v1.9.2 \
20
- run --server.http.listen-addr=0.0.0.0:12345 --stability.level=experimental /etc/alloy/config.alloy
21
-
22
- MAX_RETRIES=10
23
- COUNTER=0
24
- echo "$ALLOY_CONTAINER_NAME container status"
25
- until [ "$(docker inspect -f {{.State.Running}} $ALLOY_CONTAINER_NAME)" = true ]; do
26
- sleep 2
27
- docker inspect -f {{.State.Running}} $ALLOY_CONTAINER_NAME
28
- COUNTER=$((COUNTER + 1))
29
- if [ $COUNTER -ge $MAX_RETRIES ]; then
30
- echo "Exceeded maximum retries. Exiting."
31
- ERROR_CODE=1
32
- break
33
- fi
34
- done
35
-
36
- if [[ $ERROR_CODE -eq 0 ]]; then
37
- docker run --detach \
38
- --network $NETWORK_NAME \
39
- --name $NODE_CONTAINER_NAME \
40
- -e DB_DISABLED="true" \
41
- -e SERVER_HOST="0.0.0.0" \
42
- -e OTEL_COLLECTOR_URL="http://integrationalloy:4317" \
43
- --health-cmd="curl -f http://${NODE_CONTAINER_NAME}:9091/api/health > /dev/null || exit 1" \
44
- --health-start-period=1s \
45
- --health-retries=10 \
46
- --health-interval=1s \
47
- -p 9091:9091 \
48
- ${NODE_CONTAINER_NAME}:${BUILD_ID}
49
-
50
- COUNTER=0
51
- echo "$NODE_CONTAINER_NAME container status"
52
- until [ "$(docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME)" = "healthy" ]; do
53
- sleep 1
54
- docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME
55
- COUNTER=$((COUNTER + 1))
56
- if [ $COUNTER -ge $MAX_RETRIES ]; then
57
- echo "Exceeded maximum retries. Exiting."
58
- ERROR_CODE=1
59
- break
60
- fi
61
- done
62
- fi
63
-
64
- if [[ $ERROR_CODE -eq 0 ]]; then
65
- sleep 2
66
- curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
67
- sleep 2
68
- curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
69
- fi
70
-
71
- # sleep N seconds to await instrumentation flow send and receiving signals
72
- sleep 1
73
-
74
- # Copy logs from container to file
75
- docker container logs $ALLOY_CONTAINER_NAME >&$ROOT_PATH/packages/sdk-node/test/integration/logs.txt
76
- echo "log file at $ROOT_PATH/packages/sdk-node/test/integration/logs.txt"
77
-
78
- docker container stop $ALLOY_CONTAINER_NAME
79
- docker container stop $NODE_CONTAINER_NAME
80
-
81
- docker container rm -f $ALLOY_CONTAINER_NAME
82
- docker container rm -f $NODE_CONTAINER_NAME
83
-
84
- docker image rm ${NODE_CONTAINER_NAME}:${BUILD_ID}
85
-
86
- docker network rm $NETWORK_NAME
87
-
88
- exit $ERROR_CODE
@@ -1,45 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { _shutdownHook } from "../../lib/internals/hooks";
3
-
4
- describe("shutdown hook", () => {
5
- it("should call sdk.shutdown and log success on SIGTERM", async () => {
6
- const shutdownMock = vi.fn().mockResolvedValue(undefined);
7
- const consoleLogMock = vi
8
- .spyOn(console, "log")
9
- .mockImplementation(() => {});
10
-
11
- _shutdownHook({ shutdown: shutdownMock });
12
-
13
- process.emit("SIGTERM");
14
- // Wait for async logic
15
- await Promise.resolve();
16
-
17
- expect(shutdownMock).toHaveBeenCalled();
18
- expect(consoleLogMock).toHaveBeenCalledWith(
19
- "NodeJS OpenTelemetry instrumentation shutdown successfully",
20
- );
21
-
22
- consoleLogMock.mockRestore();
23
- });
24
-
25
- it("should catch and log errors on shutdown failure", async () => {
26
- const error = new Error("Shutdown failed");
27
- const shutdownMock = vi.fn().mockRejectedValue(error);
28
- const consoleErrorMock = vi
29
- .spyOn(console, "error")
30
- .mockImplementation(() => {});
31
-
32
- _shutdownHook({ shutdown: shutdownMock });
33
-
34
- process.emit("SIGTERM");
35
- // Wait for async logic
36
- await Promise.resolve();
37
-
38
- expect(consoleErrorMock).toHaveBeenCalledWith(
39
- "Error shutting down NodeJS OpenTelemetry instrumentation:",
40
- error,
41
- );
42
-
43
- consoleErrorMock.mockRestore();
44
- });
45
- });