@ogcio/o11y-sdk-node 0.1.0-beta.4 → 0.1.0-beta.7

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 (71) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +59 -0
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +1 -0
  5. package/dist/lib/exporter/console.d.ts +3 -0
  6. package/dist/lib/exporter/console.js +16 -0
  7. package/dist/lib/exporter/grpc.d.ts +3 -0
  8. package/dist/lib/{grpc.js → exporter/grpc.js} +9 -5
  9. package/dist/lib/exporter/http.d.ts +3 -0
  10. package/dist/lib/{http.js → exporter/http.js} +9 -5
  11. package/dist/lib/index.d.ts +28 -5
  12. package/dist/lib/instrumentation.node.js +37 -7
  13. package/dist/lib/options.d.ts +5 -3
  14. package/dist/lib/processor/enrich-span-processor.d.ts +11 -0
  15. package/dist/lib/processor/enrich-span-processor.js +22 -0
  16. package/dist/lib/traces.d.ts +1 -0
  17. package/dist/lib/traces.js +4 -0
  18. package/dist/lib/url-sampler.d.ts +10 -0
  19. package/dist/lib/url-sampler.js +25 -0
  20. package/dist/lib/utils.d.ts +4 -2
  21. package/dist/lib/utils.js +8 -3
  22. package/dist/package.json +20 -17
  23. package/dist/vitest.config.js +15 -1
  24. package/index.ts +2 -2
  25. package/lib/exporter/console.ts +27 -0
  26. package/lib/{grpc.ts → exporter/grpc.ts} +13 -7
  27. package/lib/{http.ts → exporter/http.ts} +13 -7
  28. package/lib/index.ts +36 -4
  29. package/lib/instrumentation.node.ts +59 -9
  30. package/lib/options.ts +5 -3
  31. package/lib/processor/enrich-span-processor.ts +39 -0
  32. package/lib/traces.ts +5 -0
  33. package/lib/url-sampler.ts +52 -0
  34. package/lib/utils.ts +16 -4
  35. package/package.json +20 -17
  36. package/test/enrich-span-processor.test.ts +105 -0
  37. package/test/index.test.ts +9 -0
  38. package/test/integration/README.md +26 -0
  39. package/test/integration/integration.test.ts +58 -0
  40. package/test/integration/run.sh +85 -0
  41. package/test/node-config.test.ts +49 -36
  42. package/test/url-sampler.test.ts +215 -0
  43. package/test/utils/alloy-log-parser.ts +46 -0
  44. package/vitest.config.ts +15 -1
  45. package/coverage/cobertura-coverage.xml +0 -310
  46. package/coverage/lcov-report/base.css +0 -224
  47. package/coverage/lcov-report/block-navigation.js +0 -87
  48. package/coverage/lcov-report/favicon.png +0 -0
  49. package/coverage/lcov-report/index.html +0 -131
  50. package/coverage/lcov-report/prettify.css +0 -1
  51. package/coverage/lcov-report/prettify.js +0 -2
  52. package/coverage/lcov-report/sdk-node/index.html +0 -116
  53. package/coverage/lcov-report/sdk-node/index.ts.html +0 -112
  54. package/coverage/lcov-report/sdk-node/lib/console.ts.html +0 -130
  55. package/coverage/lcov-report/sdk-node/lib/grpc.ts.html +0 -178
  56. package/coverage/lcov-report/sdk-node/lib/http.ts.html +0 -190
  57. package/coverage/lcov-report/sdk-node/lib/index.html +0 -221
  58. package/coverage/lcov-report/sdk-node/lib/index.ts.html +0 -256
  59. package/coverage/lcov-report/sdk-node/lib/instrumentation.node.ts.html +0 -334
  60. package/coverage/lcov-report/sdk-node/lib/metrics.ts.html +0 -295
  61. package/coverage/lcov-report/sdk-node/lib/options.ts.html +0 -106
  62. package/coverage/lcov-report/sdk-node/lib/utils.ts.html +0 -115
  63. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  64. package/coverage/lcov-report/sorter.js +0 -196
  65. package/coverage/lcov.info +0 -312
  66. package/dist/lib/console.d.ts +0 -3
  67. package/dist/lib/console.js +0 -12
  68. package/dist/lib/grpc.d.ts +0 -3
  69. package/dist/lib/http.d.ts +0 -3
  70. package/lib/console.ts +0 -15
  71. package/test-report.xml +0 -71
@@ -1,11 +1,12 @@
1
- import type { NodeSDKConfig } from "./index.js";
2
- import type { Exporters } from "./options.js";
3
- import { LogRecordProcessorMap } from "./utils.js";
4
1
  import { metrics } from "@opentelemetry/sdk-node";
5
2
  import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base";
6
3
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
7
4
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
8
5
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
6
+ import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
7
+ import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
8
+ import { Exporters } from "../options.js";
9
+ import { NodeSDKConfig } from "../index.js";
9
10
 
10
11
  export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
11
12
  if (config.collectorUrl.endsWith("/")) {
@@ -13,10 +14,15 @@ export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
13
14
  }
14
15
 
15
16
  return {
16
- traces: new OTLPTraceExporter({
17
- url: `${config.collectorUrl}/v1/traces`,
18
- compression: CompressionAlgorithm.GZIP,
19
- }),
17
+ spans: [
18
+ new SpanProcessorMap[config.collectorMode ?? "batch"](
19
+ new OTLPTraceExporter({
20
+ url: `${config.collectorUrl}/v1/traces`,
21
+ compression: CompressionAlgorithm.GZIP,
22
+ }),
23
+ ),
24
+ new EnrichSpanProcessor(config.spanAttributes),
25
+ ],
20
26
  metrics: new metrics.PeriodicExportingMetricReader({
21
27
  exporter: new OTLPMetricExporter({
22
28
  url: `${config.collectorUrl}/v1/metrics`,
package/lib/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- interface SDKConfig {
1
+ export interface NodeSDKConfig {
2
2
  /**
3
3
  * The opentelemetry collector entrypoint GRPC url.
4
4
  * If the collectoUrl is null or undefined, the instrumentation will not be activated.
@@ -25,9 +25,34 @@ interface SDKConfig {
25
25
  * @default batch
26
26
  */
27
27
  collectorMode?: SDKCollectorMode;
28
- }
28
+ /**
29
+ * Array of not traced urls.
30
+ *
31
+ * @type {SamplerCondition}
32
+ * @default []
33
+ */
34
+ ignoreUrls?: SamplerCondition[];
35
+
36
+ /**
37
+ * Object containing static properties or functions used to evaluate custom attributes for every logs and traces.
38
+ */
39
+ spanAttributes?: Record<
40
+ string,
41
+ SignalAttributeValue | (() => SignalAttributeValue)
42
+ >;
43
+
44
+ /**
45
+ * Object containing static properties used as resources attributes for the Node SDK initialization.
46
+ */
47
+ resourceAttributes?: Record<string, SignalAttributeValue>;
48
+
49
+ /**
50
+ * Faction value from 0 to 1, used by TraceIdRatioBasedSampler which it deterministically samples a percentage of traces that you pass in as a parameter.
51
+ *
52
+ * @default 1
53
+ */
54
+ traceRatio?: number;
29
55
 
30
- export interface NodeSDKConfig extends SDKConfig {
31
56
  /**
32
57
  * Flag to enable or disable the tracing for node:fs module
33
58
  *
@@ -36,13 +61,20 @@ export interface NodeSDKConfig extends SDKConfig {
36
61
  enableFS?: boolean;
37
62
 
38
63
  /**
39
- * protocol used to send signals.
64
+ * Protocol used to send signals.
40
65
  *
41
66
  * @default grpc
42
67
  */
43
68
  protocol?: SDKProtocol;
44
69
  }
45
70
 
71
+ export interface SamplerCondition {
72
+ type: "endsWith" | "includes" | "equals";
73
+ url: string;
74
+ }
75
+
76
+ export type SignalAttributeValue = string | number | boolean;
77
+
46
78
  export type SDKCollectorMode = "single" | "batch";
47
79
 
48
80
  export type SDKProtocol = "grpc" | "http" | "console";
@@ -1,14 +1,24 @@
1
- import { NodeSDK, resources } from "@opentelemetry/sdk-node";
2
- import type { NodeSDKConfig } from "./index.js";
3
- import type { Exporters } from "./options.js";
4
- import isUrl from "is-url";
5
- import buildHttpExporters from "./http.js";
6
- import buildGrpcExporters from "./grpc.js";
7
- import buildConsoleExporters from "./console.js";
8
- import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
1
+ import {
2
+ diag,
3
+ DiagConsoleLogger,
4
+ DiagLogLevel,
5
+ Span,
6
+ } from "@opentelemetry/api";
9
7
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
10
8
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
9
+ import { NodeSDK, resources } from "@opentelemetry/sdk-node";
10
+ import {
11
+ AlwaysOffSampler,
12
+ ParentBasedSampler,
13
+ TraceIdRatioBasedSampler,
14
+ } from "@opentelemetry/sdk-trace-base";
11
15
  import packageJson from "../package.json" with { type: "json" };
16
+ import buildConsoleExporters from "./exporter/console.js";
17
+ import buildGrpcExporters from "./exporter/grpc.js";
18
+ import buildHttpExporters from "./exporter/http.js";
19
+ import type { NodeSDKConfig } from "./index.js";
20
+ import type { Exporters } from "./options.js";
21
+ import { UrlSampler } from "./url-sampler.js";
12
22
 
13
23
  export default function buildNodeInstrumentation(
14
24
  config?: NodeSDKConfig,
@@ -44,6 +54,19 @@ export default function buildNodeInstrumentation(
44
54
  exporter = buildGrpcExporters(config);
45
55
  }
46
56
 
57
+ const urlSampler = new UrlSampler(
58
+ config.ignoreUrls,
59
+ new TraceIdRatioBasedSampler(config.traceRatio ?? 1),
60
+ );
61
+
62
+ const mainSampler = new ParentBasedSampler({
63
+ root: urlSampler,
64
+ remoteParentSampled: urlSampler,
65
+ remoteParentNotSampled: new AlwaysOffSampler(),
66
+ localParentSampled: urlSampler,
67
+ localParentNotSampled: new AlwaysOffSampler(),
68
+ });
69
+
47
70
  try {
48
71
  diag.setLogger(
49
72
  new DiagConsoleLogger(),
@@ -56,17 +79,34 @@ export default function buildNodeInstrumentation(
56
79
  resource: new resources.Resource({
57
80
  "o11y.sdk.name": packageJson.name,
58
81
  "o11y.sdk.version": packageJson.version,
82
+ ...config.resourceAttributes,
59
83
  }),
84
+ spanProcessors: exporter.spans,
60
85
  serviceName: config.serviceName,
61
- traceExporter: exporter.traces,
62
86
  metricReader: exporter.metrics,
63
87
  logRecordProcessors: exporter.logs,
88
+ sampler: mainSampler,
64
89
  textMapPropagator: new W3CTraceContextPropagator(),
65
90
  instrumentations: [
66
91
  getNodeAutoInstrumentations({
67
92
  "@opentelemetry/instrumentation-fs": {
68
93
  enabled: config.enableFS ?? false,
69
94
  },
95
+ "@opentelemetry/instrumentation-pino": {
96
+ logHook: (
97
+ _span: Span,
98
+ record: Record<string, unknown>,
99
+ _level?: number,
100
+ ) => {
101
+ if (config.spanAttributes != undefined) {
102
+ for (const [key, value] of Object.entries(
103
+ config.spanAttributes,
104
+ )) {
105
+ record[key] = typeof value === "function" ? value() : value;
106
+ }
107
+ }
108
+ },
109
+ },
70
110
  }),
71
111
  ],
72
112
  });
@@ -81,3 +121,13 @@ export default function buildNodeInstrumentation(
81
121
  );
82
122
  }
83
123
  }
124
+
125
+ function isUrl(url: string): boolean {
126
+ try {
127
+ new URL(url);
128
+ return true;
129
+ } catch (err) {
130
+ console.error(err);
131
+ return false;
132
+ }
133
+ }
package/lib/options.ts CHANGED
@@ -1,7 +1,9 @@
1
- import type { logs, tracing, metrics } from "@opentelemetry/sdk-node";
1
+ import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
+ import type { metrics } from "@opentelemetry/sdk-node";
3
+ import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
2
4
 
3
5
  export type Exporters = {
4
- traces: tracing.SpanExporter;
6
+ spans: SpanProcessor[];
5
7
  metrics: metrics.MetricReader;
6
- logs: logs.LogRecordProcessor[];
8
+ logs: LogRecordProcessor[];
7
9
  };
@@ -0,0 +1,39 @@
1
+ import { Context } from "@opentelemetry/api";
2
+ import {
3
+ ReadableSpan,
4
+ Span,
5
+ SpanProcessor,
6
+ } from "@opentelemetry/sdk-trace-base";
7
+ import { SignalAttributeValue } from "../index.js";
8
+
9
+ export class EnrichSpanProcessor implements SpanProcessor {
10
+ private _spanAttributes:
11
+ | Record<string, SignalAttributeValue | (() => SignalAttributeValue)>
12
+ | undefined;
13
+
14
+ constructor(
15
+ spanAttributes?: Record<
16
+ string,
17
+ SignalAttributeValue | (() => SignalAttributeValue)
18
+ >,
19
+ ) {
20
+ this._spanAttributes = spanAttributes;
21
+ }
22
+
23
+ forceFlush(): Promise<void> {
24
+ return Promise.resolve();
25
+ }
26
+ onStart(span: Span, _context: Context): void {
27
+ if (this._spanAttributes != undefined) {
28
+ for (const [key, value] of Object.entries(this._spanAttributes)) {
29
+ span.setAttribute(key, typeof value === "function" ? value() : value);
30
+ }
31
+ }
32
+ }
33
+ onEnd(_span: ReadableSpan): void {
34
+ return;
35
+ }
36
+ shutdown(): Promise<void> {
37
+ return Promise.resolve();
38
+ }
39
+ }
package/lib/traces.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { trace } from "@opentelemetry/api";
2
+
3
+ export function getActiveSpan() {
4
+ return trace.getActiveSpan();
5
+ }
@@ -0,0 +1,52 @@
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 CHANGED
@@ -1,10 +1,22 @@
1
+ import {
2
+ BatchLogRecordProcessor,
3
+ SimpleLogRecordProcessor,
4
+ } from "@opentelemetry/sdk-logs";
1
5
  import { SDKCollectorMode } from "./index.js";
2
- import { logs } from "@opentelemetry/sdk-node";
6
+ import { tracing } from "@opentelemetry/sdk-node";
3
7
 
4
8
  export const LogRecordProcessorMap: Record<
5
9
  SDKCollectorMode,
6
- typeof logs.SimpleLogRecordProcessor | typeof logs.BatchLogRecordProcessor
10
+ typeof SimpleLogRecordProcessor | typeof BatchLogRecordProcessor
7
11
  > = {
8
- single: logs.SimpleLogRecordProcessor,
9
- batch: logs.BatchLogRecordProcessor,
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,
10
22
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.4",
3
+ "version": "0.1.0-beta.7",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -20,30 +20,33 @@
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
22
  "@opentelemetry/api": "^1.9.0",
23
- "@opentelemetry/auto-instrumentations-node": "^0.55.3",
23
+ "@opentelemetry/auto-instrumentations-node": "^0.56.0",
24
24
  "@opentelemetry/core": "1.30.1",
25
- "@opentelemetry/exporter-logs-otlp-grpc": "^0.57.1",
26
- "@opentelemetry/exporter-logs-otlp-http": "^0.57.1",
27
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.1",
28
- "@opentelemetry/exporter-metrics-otlp-http": "^0.57.1",
29
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.57.1",
30
- "@opentelemetry/exporter-trace-otlp-http": "^0.57.1",
31
- "@opentelemetry/instrumentation": "^0.57.1",
32
- "@opentelemetry/otlp-exporter-base": "^0.57.1",
25
+ "@opentelemetry/exporter-logs-otlp-grpc": "^0.57.2",
26
+ "@opentelemetry/exporter-logs-otlp-http": "^0.57.2",
27
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.2",
28
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.57.2",
29
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.57.2",
30
+ "@opentelemetry/exporter-trace-otlp-http": "^0.57.2",
31
+ "@opentelemetry/instrumentation": "^0.57.2",
32
+ "@opentelemetry/otlp-exporter-base": "^0.57.2",
33
+ "@opentelemetry/sdk-logs": "^0.57.2",
33
34
  "@opentelemetry/sdk-metrics": "^1.30.1",
34
- "@opentelemetry/sdk-node": "^0.57.1",
35
- "is-url": "^1.2.4"
35
+ "@opentelemetry/sdk-node": "^0.57.2",
36
+ "@opentelemetry/sdk-trace-base": "^1.30.1"
36
37
  },
37
38
  "devDependencies": {
38
- "@types/is-url": "^1.2.32",
39
- "@types/node": "^22.12.0",
40
- "@vitest/coverage-v8": "^3.0.4",
39
+ "@types/node": "^22.13.4",
40
+ "@vitest/coverage-v8": "^3.0.5",
41
41
  "tsx": "^4.19.2",
42
42
  "typescript": "^5.7.3",
43
- "vitest": "^3.0.4"
43
+ "vitest": "^3.0.5"
44
44
  },
45
45
  "scripts": {
46
46
  "build": "rm -rf dist && tsc -p tsconfig.json",
47
- "test": "vitest"
47
+ "test": "vitest",
48
+ "test:unit": "vitest --project unit",
49
+ "test:integration": "pnpm --filter @ogcio/o11y run prepare:integration && vitest --project integration",
50
+ "test:integration:dryrun": "vitest --project integration"
48
51
  }
49
52
  }
@@ -0,0 +1,105 @@
1
+ import {
2
+ AttributeValue,
3
+ Context,
4
+ Exception,
5
+ Link,
6
+ Span,
7
+ SpanAttributes,
8
+ SpanAttributeValue,
9
+ SpanContext,
10
+ SpanStatus,
11
+ TimeInput,
12
+ } from "@opentelemetry/api";
13
+ import { describe, expect, it } from "vitest";
14
+ import { EnrichSpanProcessor } from "../lib/processor/enrich-span-processor.js";
15
+
16
+ class MockSpan implements Span {
17
+ public attributes: Record<string, AttributeValue> = {};
18
+
19
+ spanContext(): SpanContext {
20
+ throw new Error("Method not implemented.");
21
+ }
22
+ setAttribute(key: string, value: AttributeValue): this {
23
+ this.attributes[key] = value;
24
+ return this;
25
+ }
26
+ setAttributes(attributes: SpanAttributes): this {
27
+ throw new Error("Method not implemented.");
28
+ }
29
+ addEvent(
30
+ name: string,
31
+ attributesOrStartTime?: SpanAttributes | TimeInput,
32
+ startTime?: TimeInput,
33
+ ): this {
34
+ throw new Error("Method not implemented.");
35
+ }
36
+ addLink(link: Link): this {
37
+ throw new Error("Method not implemented.");
38
+ }
39
+ addLinks(links: Link[]): this {
40
+ throw new Error("Method not implemented.");
41
+ }
42
+ setStatus(status: SpanStatus): this {
43
+ throw new Error("Method not implemented.");
44
+ }
45
+ updateName(name: string): this {
46
+ throw new Error("Method not implemented.");
47
+ }
48
+ end(endTime?: TimeInput): void {
49
+ throw new Error("Method not implemented.");
50
+ }
51
+ isRecording(): boolean {
52
+ throw new Error("Method not implemented.");
53
+ }
54
+ recordException(exception: Exception, time?: TimeInput): void {
55
+ throw new Error("Method not implemented.");
56
+ }
57
+ }
58
+
59
+ describe("EnrichSpanProcessor", () => {
60
+ it("should set static attributes on span", () => {
61
+ const spanAttributes = {
62
+ key1: "value1",
63
+ key2: 123,
64
+ };
65
+ const processor = new EnrichSpanProcessor(spanAttributes);
66
+ const mockSpan = new MockSpan();
67
+ const mockContext = {} as Context;
68
+
69
+ processor.onStart(mockSpan, mockContext);
70
+
71
+ expect(mockSpan.attributes["key1"]).toBe("value1");
72
+ expect(mockSpan.attributes["key2"]).toBe(123);
73
+ });
74
+
75
+ it("should set dynamic attributes on span", () => {
76
+ const spanAttributes = {
77
+ dynamicKey: () => "dynamicValue",
78
+ };
79
+ const processor = new EnrichSpanProcessor(spanAttributes);
80
+ const mockSpan = new MockSpan();
81
+ const mockContext = {} as Context;
82
+
83
+ processor.onStart(mockSpan, mockContext);
84
+
85
+ expect(mockSpan.attributes["dynamicKey"]).toBe("dynamicValue");
86
+ });
87
+
88
+ it("should not set attributes if none are provided", () => {
89
+ const processor = new EnrichSpanProcessor();
90
+ const mockSpan = new MockSpan();
91
+ const mockContext = {} as Context;
92
+
93
+ processor.onStart(mockSpan, mockContext);
94
+
95
+ expect(mockSpan.attributes["key1"]).toBeUndefined();
96
+ });
97
+
98
+ it("default method, should maintain default behaviour", async () => {
99
+ const processor = new EnrichSpanProcessor();
100
+
101
+ expect(processor.onEnd(null!)).toBeUndefined();
102
+ expect(await processor.shutdown()).toBeUndefined();
103
+ expect(await processor.forceFlush()).toBeUndefined();
104
+ });
105
+ });
@@ -12,6 +12,15 @@ describe("instrumentNode", () => {
12
12
  serviceName: "custom-service",
13
13
  collectorUrl: "http://custom-collector.com",
14
14
  protocol: "grpc",
15
+ resourceAttributes: {
16
+ "team.infra.cluster": "dev-01",
17
+ "team.infra.pod": "01",
18
+ "team.service.type": "fastify",
19
+ },
20
+ spanAttributes: {
21
+ "signal.namespace": "example",
22
+ "signal.number": () => "callback",
23
+ },
15
24
  };
16
25
 
17
26
  const buildNodeInstrumentation = await import(
@@ -0,0 +1,26 @@
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
+ - persit alloy log to a file and save to following path `/packages/sdk-node/test/integration/`
26
+ - docker turn down process (containers/network/image)
@@ -0,0 +1,58 @@
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
+ console.log(data.split(/\nts=/).length);
14
+
15
+ for (const line of data.split(/\nts=/)) {
16
+ const parsedLine: Record<string, object | string | number> =
17
+ parseLog(line);
18
+
19
+ if (
20
+ parsedLine["attributes"] &&
21
+ parsedLine["attributes"]["span_kind"] &&
22
+ parsedLine["attributes"]["span_kind"] === "trace"
23
+ ) {
24
+ if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
25
+ dummy_traces_counter++;
26
+
27
+ // verify global sdk span resource
28
+ assert.equal(
29
+ parsedLine["resource_attributes"]["team.infra.pod"],
30
+ "01",
31
+ );
32
+ assert.equal(
33
+ parsedLine["resource_attributes"]["team.service.type"],
34
+ "fastify",
35
+ );
36
+
37
+ // verify global sdk span attributes
38
+ assert.equal(parsedLine["attributes"]["signal.namespace"], "example");
39
+
40
+ // verify runtime custom span inside dev application
41
+ assert.equal(parsedLine["attributes"]["business.info"], "dummy");
42
+ assert.equal(
43
+ parsedLine["attributes"]["business.request_type"],
44
+ "application/json",
45
+ );
46
+
47
+ continue;
48
+ }
49
+ if (parsedLine["attributes"]["http.target"] === "/api/health") {
50
+ health_traces_counter++;
51
+ }
52
+ }
53
+ }
54
+
55
+ assert.equal(health_traces_counter, 0);
56
+ assert.equal(dummy_traces_counter, 2);
57
+ });
58
+ });