@ogcio/o11y-sdk-node 0.1.0-beta.8 → 0.2.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 (35) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/lib/exporter/console.d.ts +1 -1
  3. package/dist/lib/exporter/console.js +5 -1
  4. package/dist/lib/exporter/grpc.d.ts +2 -2
  5. package/dist/lib/exporter/grpc.js +12 -1
  6. package/dist/lib/exporter/http.d.ts +1 -1
  7. package/dist/lib/exporter/http.js +2 -0
  8. package/dist/lib/index.d.ts +7 -0
  9. package/dist/lib/instrumentation.node.d.ts +1 -1
  10. package/dist/lib/instrumentation.node.js +35 -38
  11. package/dist/lib/processor/enrich-logger-processor.d.ts +10 -0
  12. package/dist/lib/processor/enrich-logger-processor.js +19 -0
  13. package/dist/lib/resource.d.ts +7 -0
  14. package/dist/lib/resource.js +18 -0
  15. package/dist/package.json +25 -20
  16. package/dist/vitest.config.js +2 -2
  17. package/lib/exporter/console.ts +6 -2
  18. package/lib/exporter/grpc.ts +17 -2
  19. package/lib/exporter/http.ts +3 -1
  20. package/lib/index.ts +9 -0
  21. package/lib/instrumentation.node.ts +47 -54
  22. package/lib/processor/enrich-logger-processor.ts +34 -0
  23. package/lib/resource.ts +30 -0
  24. package/package.json +25 -20
  25. package/test/index.test.ts +44 -12
  26. package/test/integration/README.md +1 -1
  27. package/test/node-config.test.ts +18 -25
  28. package/test/processor/enrich-logger-processor.test.ts +58 -0
  29. package/test/{enrich-span-processor.test.ts → processor/enrich-span-processor.test.ts} +1 -2
  30. package/test/resource.test.ts +33 -0
  31. package/test/validation.test.ts +22 -16
  32. package/vitest.config.ts +2 -2
  33. /package/dist/lib/{options.d.ts → exporter/index.d.ts} +0 -0
  34. /package/dist/lib/{options.js → exporter/index.js} +0 -0
  35. /package/lib/{options.ts → exporter/index.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,79 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.2.0](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.13...@ogcio/o11y-sdk-node@v0.2.0) (2025-06-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * late june deps update ([#154](https://github.com/ogcio/o11y/issues/154)) ([a772622](https://github.com/ogcio/o11y/commit/a7726225a59b2dbcd4326919323d935843b2f93f))
9
+
10
+
11
+ ### Miscellaneous Chores
12
+
13
+ * **deps-dev:** bump @types/node to `24` AB[#29294](https://github.com/ogcio/o11y/issues/29294) ([3c74c28](https://github.com/ogcio/o11y/commit/3c74c28af41af8403220368ac72a90e40c3e15ae))
14
+
15
+ ## [0.1.0-beta.13](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.12...@ogcio/o11y-sdk-node@v0.1.0-beta.13) (2025-06-16)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **sdk-node:** allow graceful shutdown of instrumentation on SIGTERM AB[#28329](https://github.com/ogcio/o11y/issues/28329) ([#151](https://github.com/ogcio/o11y/issues/151)) ([991ee15](https://github.com/ogcio/o11y/commit/991ee1559898b721823cb9d1e0490e2ad82e6d0a))
21
+
22
+ ## [0.1.0-beta.12](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.11...@ogcio/o11y-sdk-node@v0.1.0-beta.12) (2025-06-04)
23
+
24
+
25
+ ### Features
26
+
27
+ * add custom log processor for span enrich ([#102](https://github.com/ogcio/o11y/issues/102)) ([bbf8334](https://github.com/ogcio/o11y/commit/bbf83340940ed651dff63bbe7aaa52881d1e8c8c))
28
+ * add opentelemetry sampler ([#66](https://github.com/ogcio/o11y/issues/66)) ([48a1761](https://github.com/ogcio/o11y/commit/48a1761d68fbccf7b63e4232a62376caecf01fbc))
29
+ * april depedency upgrade AB[#27200](https://github.com/ogcio/o11y/issues/27200) ([#116](https://github.com/ogcio/o11y/issues/116)) ([d792fe5](https://github.com/ogcio/o11y/commit/d792fe5a783b0b495912b5bef2babfe11ef5e01d))
30
+ * june deps update ([#147](https://github.com/ogcio/o11y/issues/147)) ([b365a40](https://github.com/ogcio/o11y/commit/b365a4099cdfd8533ffa4948620d29f0044bbd70))
31
+ * may deps update ([#136](https://github.com/ogcio/o11y/issues/136)) ([3edd8b1](https://github.com/ogcio/o11y/commit/3edd8b1d823740d555fc4d93be427e9dc1438a95))
32
+ * o11y sdk repo setup ([#15](https://github.com/ogcio/o11y/issues/15)) ([c5816ba](https://github.com/ogcio/o11y/commit/c5816baff1454353f12539949959e84964ed6401))
33
+ * o11y showcase AB[#25895](https://github.com/ogcio/o11y/issues/25895) ([#84](https://github.com/ogcio/o11y/issues/84)) ([f8f10af](https://github.com/ogcio/o11y/commit/f8f10af97d9f5c188e3e65f7d62d5c673edce25a))
34
+ * remove pnpm test on prepublishOnly script ([#68](https://github.com/ogcio/o11y/issues/68)) ([41f6f57](https://github.com/ogcio/o11y/commit/41f6f57fa415c4f7adc29f49f983539274ef7320))
35
+ * **sdk-node:** add span customization AB[#25358](https://github.com/ogcio/o11y/issues/25358) ([46ba97b](https://github.com/ogcio/o11y/commit/46ba97bac4004ff326a954592f45213ce0e4d683))
36
+ * **sdk-node:** custom metrics ([#53](https://github.com/ogcio/o11y/issues/53)) ([3cb40b1](https://github.com/ogcio/o11y/commit/3cb40b1add3d80615c8d123d233724094559c7ff))
37
+ * **sdk-node:** renaming MetricsParams interface properties AB[#26188](https://github.com/ogcio/o11y/issues/26188) ([#96](https://github.com/ogcio/o11y/issues/96)) ([244aee0](https://github.com/ogcio/o11y/commit/244aee01ae6e7f319ae7f9fdeff27eb40ee3c863))
38
+ * upgrade to opentelemetry 2 AB[#25863](https://github.com/ogcio/o11y/issues/25863) ([#106](https://github.com/ogcio/o11y/issues/106)) ([3ff0314](https://github.com/ogcio/o11y/commit/3ff0314fef9f4d7b5db76da3b94e9035801384c7))
39
+
40
+
41
+ ### Bug Fixes
42
+
43
+ * add readme file inside submodule ([#27](https://github.com/ogcio/o11y/issues/27)) ([dc518d5](https://github.com/ogcio/o11y/commit/dc518d5dde573368443bc0f8619e80e331880c78))
44
+ * default attributes in resource ([#112](https://github.com/ogcio/o11y/issues/112)) ([f0f0b9d](https://github.com/ogcio/o11y/commit/f0f0b9d555ef321d31f7171e25dc4b8a5b044522))
45
+ * improve getMetric attributes types ([#76](https://github.com/ogcio/o11y/issues/76)) ([243649c](https://github.com/ogcio/o11y/commit/243649c4bfe750687a729fbd836772cce16e0cb1))
46
+ * prepublishOnly hook ([#60](https://github.com/ogcio/o11y/issues/60)) ([9fbd3ad](https://github.com/ogcio/o11y/commit/9fbd3ad0b45a1604cf2eccc26b2f8855640417a1))
47
+ * **sdk-node:** add waitForReady for grpc-client to handle transient init connection errors AB[#28329](https://github.com/ogcio/o11y/issues/28329) ([#144](https://github.com/ogcio/o11y/issues/144)) ([6f260da](https://github.com/ogcio/o11y/commit/6f260da15fcbcd2c629ec6d4bb9fb3592803f399))
48
+ * **sdk-node:** update sdk init examples AB[#28329](https://github.com/ogcio/o11y/issues/28329) ([#146](https://github.com/ogcio/o11y/issues/146)) ([81de295](https://github.com/ogcio/o11y/commit/81de2957a7bef7430a46c6677f3c876255ef7c88))
49
+
50
+ ## [0.1.0-beta.11](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.10...@ogcio/o11y-sdk-node@v0.1.0-beta.11) (2025-05-21)
51
+
52
+
53
+ ### Features
54
+
55
+ * may deps update ([#136](https://github.com/ogcio/o11y/issues/136)) ([3edd8b1](https://github.com/ogcio/o11y/commit/3edd8b1d823740d555fc4d93be427e9dc1438a95))
56
+
57
+ ## [0.1.0-beta.10](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.9...@ogcio/o11y-sdk-node@v0.1.0-beta.10) (2025-04-09)
58
+
59
+
60
+ ### Features
61
+
62
+ * april depedency upgrade AB[#27200](https://github.com/ogcio/o11y/issues/27200) ([#116](https://github.com/ogcio/o11y/issues/116)) ([d792fe5](https://github.com/ogcio/o11y/commit/d792fe5a783b0b495912b5bef2babfe11ef5e01d))
63
+
64
+
65
+ ### Bug Fixes
66
+
67
+ * default attributes in resource ([#112](https://github.com/ogcio/o11y/issues/112)) ([f0f0b9d](https://github.com/ogcio/o11y/commit/f0f0b9d555ef321d31f7171e25dc4b8a5b044522))
68
+
69
+ ## [0.1.0-beta.9](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.8...@ogcio/o11y-sdk-node@v0.1.0-beta.9) (2025-03-24)
70
+
71
+
72
+ ### Features
73
+
74
+ * add custom log processor for span enrich ([#102](https://github.com/ogcio/o11y/issues/102)) ([bbf8334](https://github.com/ogcio/o11y/commit/bbf83340940ed651dff63bbe7aaa52881d1e8c8c))
75
+ * upgrade to opentelemetry 2 AB[#25863](https://github.com/ogcio/o11y/issues/25863) ([#106](https://github.com/ogcio/o11y/issues/106)) ([3ff0314](https://github.com/ogcio/o11y/commit/3ff0314fef9f4d7b5db76da3b94e9035801384c7))
76
+
3
77
  ## [0.1.0-beta.8](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.7...@ogcio/o11y-sdk-node@v0.1.0-beta.8) (2025-03-07)
4
78
 
5
79
 
@@ -1,3 +1,3 @@
1
1
  import { NodeSDKConfig } from "../index.js";
2
- import { Exporters } from "../options.js";
2
+ import { Exporters } from "./index.js";
3
3
  export default function buildConsoleExporters(config: NodeSDKConfig): Exporters;
@@ -2,6 +2,7 @@ import { ConsoleLogRecordExporter, SimpleLogRecordProcessor, } from "@openteleme
2
2
  import { metrics } from "@opentelemetry/sdk-node";
3
3
  import { ConsoleSpanExporter, SimpleSpanProcessor, } from "@opentelemetry/sdk-trace-base";
4
4
  import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
5
+ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
5
6
  export default function buildConsoleExporters(config) {
6
7
  return {
7
8
  spans: [
@@ -11,6 +12,9 @@ export default function buildConsoleExporters(config) {
11
12
  metrics: new metrics.PeriodicExportingMetricReader({
12
13
  exporter: new metrics.ConsoleMetricExporter(),
13
14
  }),
14
- logs: [new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())],
15
+ logs: [
16
+ new EnrichLogProcessor(config.spanAttributes),
17
+ new SimpleLogRecordProcessor(new ConsoleLogRecordExporter()),
18
+ ],
15
19
  };
16
20
  }
@@ -1,3 +1,3 @@
1
1
  import { NodeSDKConfig } from "../index.js";
2
- import { Exporters } from "../options.js";
3
- export default function buildGrpcExporters(config: NodeSDKConfig): Exporters;
2
+ import { Exporters } from "./index.js";
3
+ export default function buildGrpcExporters(config: NodeSDKConfig): Promise<Exporters>;
@@ -5,12 +5,20 @@ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
5
5
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
6
6
  import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
7
7
  import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
8
- export default function buildGrpcExporters(config) {
8
+ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
9
+ async function defaultMetadata() {
10
+ const { Metadata } = await import("@grpc/grpc-js");
11
+ return new Metadata({
12
+ waitForReady: true,
13
+ });
14
+ }
15
+ export default async function buildGrpcExporters(config) {
9
16
  return {
10
17
  spans: [
11
18
  new SpanProcessorMap[config.collectorMode ?? "batch"](new OTLPTraceExporter({
12
19
  url: `${config.collectorUrl}`,
13
20
  compression: CompressionAlgorithm.GZIP,
21
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
14
22
  })),
15
23
  new EnrichSpanProcessor(config.spanAttributes),
16
24
  ],
@@ -18,12 +26,15 @@ export default function buildGrpcExporters(config) {
18
26
  exporter: new OTLPMetricExporter({
19
27
  url: `${config.collectorUrl}`,
20
28
  compression: CompressionAlgorithm.GZIP,
29
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
21
30
  }),
22
31
  }),
23
32
  logs: [
33
+ new EnrichLogProcessor(config.spanAttributes),
24
34
  new LogRecordProcessorMap[config.collectorMode ?? "batch"](new OTLPLogExporter({
25
35
  url: `${config.collectorUrl}`,
26
36
  compression: CompressionAlgorithm.GZIP,
37
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
27
38
  })),
28
39
  ],
29
40
  };
@@ -1,3 +1,3 @@
1
- import { Exporters } from "../options.js";
1
+ import { Exporters } from "./index.js";
2
2
  import { NodeSDKConfig } from "../index.js";
3
3
  export default function buildHttpExporters(config: NodeSDKConfig): Exporters;
@@ -5,6 +5,7 @@ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
5
5
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
6
6
  import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
7
7
  import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
8
+ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
8
9
  export default function buildHttpExporters(config) {
9
10
  if (config.collectorUrl.endsWith("/")) {
10
11
  config.collectorUrl = config.collectorUrl.slice(0, -1);
@@ -24,6 +25,7 @@ export default function buildHttpExporters(config) {
24
25
  }),
25
26
  }),
26
27
  logs: [
28
+ new EnrichLogProcessor(config.spanAttributes),
27
29
  new LogRecordProcessorMap[config.collectorMode ?? "batch"](new OTLPLogExporter({
28
30
  url: `${config.collectorUrl}/v1/logs`,
29
31
  compression: CompressionAlgorithm.GZIP,
@@ -1,3 +1,4 @@
1
+ import type { Metadata } from "@grpc/grpc-js";
1
2
  export interface NodeSDKConfig {
2
3
  /**
3
4
  * The opentelemetry collector entrypoint GRPC url.
@@ -58,6 +59,12 @@ export interface NodeSDKConfig {
58
59
  * @default grpc
59
60
  */
60
61
  protocol?: SDKProtocol;
62
+ /**
63
+ * Grpc Metadata for the grpc-js client.
64
+ *
65
+ * @default { waitForReady: true }
66
+ */
67
+ grpcMetadata?: Metadata;
61
68
  }
62
69
  export interface SamplerCondition {
63
70
  type: "endsWith" | "includes" | "equals";
@@ -1,3 +1,3 @@
1
1
  import { NodeSDK } from "@opentelemetry/sdk-node";
2
2
  import type { NodeSDKConfig } from "./index.js";
3
- export default function buildNodeInstrumentation(config?: NodeSDKConfig): NodeSDK | undefined;
3
+ export default function buildNodeInstrumentation(config?: NodeSDKConfig): Promise<NodeSDK | undefined>;
@@ -1,14 +1,15 @@
1
- import { diag, DiagConsoleLogger, DiagLogLevel, } from "@opentelemetry/api";
1
+ import process from "process";
2
+ import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
2
3
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
3
4
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
4
- import { NodeSDK, resources } from "@opentelemetry/sdk-node";
5
+ import { NodeSDK } from "@opentelemetry/sdk-node";
5
6
  import { AlwaysOffSampler, ParentBasedSampler, TraceIdRatioBasedSampler, } from "@opentelemetry/sdk-trace-base";
6
- import packageJson from "../package.json" with { type: "json" };
7
7
  import buildConsoleExporters from "./exporter/console.js";
8
8
  import buildGrpcExporters from "./exporter/grpc.js";
9
9
  import buildHttpExporters from "./exporter/http.js";
10
+ import { ObservabilityResourceDetector } from "./resource.js";
10
11
  import { UrlSampler } from "./url-sampler.js";
11
- export default function buildNodeInstrumentation(config) {
12
+ export default async function buildNodeInstrumentation(config) {
12
13
  if (!config) {
13
14
  console.warn("observability config not set. Skipping NodeJS OpenTelemetry instrumentation.");
14
15
  return;
@@ -21,16 +22,6 @@ export default function buildNodeInstrumentation(config) {
21
22
  console.error("collectorUrl does not use a valid format. Skipping NodeJS OpenTelemetry instrumentation.");
22
23
  return;
23
24
  }
24
- let exporter;
25
- if (config.protocol === "http") {
26
- exporter = buildHttpExporters(config);
27
- }
28
- else if (config.protocol === "console") {
29
- exporter = buildConsoleExporters(config);
30
- }
31
- else {
32
- exporter = buildGrpcExporters(config);
33
- }
34
25
  const urlSampler = new UrlSampler(config.ignoreUrls, new TraceIdRatioBasedSampler(config.traceRatio ?? 1));
35
26
  const mainSampler = new ParentBasedSampler({
36
27
  root: urlSampler,
@@ -39,41 +30,47 @@ export default function buildNodeInstrumentation(config) {
39
30
  localParentSampled: urlSampler,
40
31
  localParentNotSampled: new AlwaysOffSampler(),
41
32
  });
33
+ diag.setLogger(new DiagConsoleLogger(), config.diagLogLevel ? DiagLogLevel[config.diagLogLevel] : DiagLogLevel.INFO);
42
34
  try {
43
- diag.setLogger(new DiagConsoleLogger(), config.diagLogLevel
44
- ? DiagLogLevel[config.diagLogLevel]
45
- : DiagLogLevel.INFO);
35
+ const nodeSdkInstrumentation = getNodeAutoInstrumentations({
36
+ "@opentelemetry/instrumentation-fs": {
37
+ enabled: config.enableFS ?? false,
38
+ },
39
+ });
40
+ let exporter;
41
+ if (config.protocol === "http") {
42
+ exporter = buildHttpExporters(config);
43
+ }
44
+ else if (config.protocol === "console") {
45
+ exporter = buildConsoleExporters(config);
46
+ }
47
+ else {
48
+ exporter = await buildGrpcExporters(config);
49
+ }
46
50
  const sdk = new NodeSDK({
47
- resource: new resources.Resource({
48
- "o11y.sdk.name": packageJson.name,
49
- "o11y.sdk.version": packageJson.version,
50
- ...config.resourceAttributes,
51
- }),
51
+ resourceDetectors: [
52
+ new ObservabilityResourceDetector(config.resourceAttributes),
53
+ ],
52
54
  spanProcessors: exporter.spans,
53
55
  serviceName: config.serviceName,
54
56
  metricReader: exporter.metrics,
55
57
  logRecordProcessors: exporter.logs,
56
58
  sampler: mainSampler,
57
59
  textMapPropagator: new W3CTraceContextPropagator(),
58
- instrumentations: [
59
- getNodeAutoInstrumentations({
60
- "@opentelemetry/instrumentation-fs": {
61
- enabled: config.enableFS ?? false,
62
- },
63
- "@opentelemetry/instrumentation-pino": {
64
- logHook: (_span, record, _level) => {
65
- if (config.spanAttributes != undefined) {
66
- for (const [key, value] of Object.entries(config.spanAttributes)) {
67
- record[key] = typeof value === "function" ? value() : value;
68
- }
69
- }
70
- },
71
- },
72
- }),
73
- ],
60
+ instrumentations: [nodeSdkInstrumentation],
74
61
  });
75
62
  sdk.start();
76
63
  console.log("NodeJS OpenTelemetry instrumentation started successfully.");
64
+ process.on("SIGTERM", async () => {
65
+ try {
66
+ // Flushing before shutdown is implemented on a per-exporter basis.
67
+ await sdk.shutdown();
68
+ console.log("NodeJS OpenTelemetry instrumentation shutdown successfully");
69
+ }
70
+ catch (error) {
71
+ console.error("Error shutting down NodeJS OpenTelemetry instrumentation:", error);
72
+ }
73
+ });
77
74
  return sdk;
78
75
  }
79
76
  catch (error) {
@@ -0,0 +1,10 @@
1
+ import { LogRecord, LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
+ import { Context } from "@opentelemetry/api";
3
+ import { SignalAttributeValue } from "../index.js";
4
+ export declare class EnrichLogProcessor implements LogRecordProcessor {
5
+ private _spanAttributes?;
6
+ constructor(spanAttributes?: Record<string, SignalAttributeValue | (() => SignalAttributeValue)>);
7
+ forceFlush(): Promise<void>;
8
+ onEmit(logRecord: LogRecord, _context?: Context): void;
9
+ shutdown(): Promise<void>;
10
+ }
@@ -0,0 +1,19 @@
1
+ export class EnrichLogProcessor {
2
+ _spanAttributes = undefined;
3
+ constructor(spanAttributes) {
4
+ this._spanAttributes = spanAttributes;
5
+ }
6
+ forceFlush() {
7
+ return Promise.resolve();
8
+ }
9
+ onEmit(logRecord, _context) {
10
+ if (this._spanAttributes) {
11
+ for (const [key, value] of Object.entries(this._spanAttributes)) {
12
+ logRecord.setAttribute(key, typeof value === "function" ? value() : value);
13
+ }
14
+ }
15
+ }
16
+ shutdown() {
17
+ return Promise.resolve();
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ import { ResourceDetector, DetectedResource } from "@opentelemetry/resources";
2
+ import { SignalAttributeValue } from "./index.js";
3
+ export declare class ObservabilityResourceDetector implements ResourceDetector {
4
+ private _resourceAttributes;
5
+ constructor(resourceAttributes?: Record<string, SignalAttributeValue>);
6
+ detect(): DetectedResource;
7
+ }
@@ -0,0 +1,18 @@
1
+ import packageJson from "../package.json" with { type: "json" };
2
+ export class ObservabilityResourceDetector {
3
+ _resourceAttributes;
4
+ constructor(resourceAttributes) {
5
+ this._resourceAttributes = resourceAttributes;
6
+ }
7
+ detect() {
8
+ let attributes = {};
9
+ if (this._resourceAttributes) {
10
+ attributes = {
11
+ ...this._resourceAttributes,
12
+ };
13
+ }
14
+ attributes["o11y.sdk.name"] = packageJson.name;
15
+ attributes["o11y.sdk.version"] = packageJson.version;
16
+ return { attributes };
17
+ }
18
+ }
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.8",
3
+ "version": "0.2.0",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -27,27 +27,32 @@
27
27
  "author": "team:ogcio/observability",
28
28
  "license": "ISC",
29
29
  "dependencies": {
30
+ "@grpc/grpc-js": "^1.13.4",
30
31
  "@opentelemetry/api": "^1.9.0",
31
- "@opentelemetry/auto-instrumentations-node": "^0.56.1",
32
- "@opentelemetry/core": "1.30.1",
33
- "@opentelemetry/exporter-logs-otlp-grpc": "^0.57.2",
34
- "@opentelemetry/exporter-logs-otlp-http": "^0.57.2",
35
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.2",
36
- "@opentelemetry/exporter-metrics-otlp-http": "^0.57.2",
37
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.57.2",
38
- "@opentelemetry/exporter-trace-otlp-http": "^0.57.2",
39
- "@opentelemetry/instrumentation": "^0.57.2",
40
- "@opentelemetry/otlp-exporter-base": "^0.57.2",
41
- "@opentelemetry/sdk-logs": "^0.57.2",
42
- "@opentelemetry/sdk-metrics": "^1.30.1",
43
- "@opentelemetry/sdk-node": "^0.57.2",
44
- "@opentelemetry/sdk-trace-base": "^1.30.1"
32
+ "@opentelemetry/auto-instrumentations-node": "^0.60.1",
33
+ "@opentelemetry/core": "^2.0.1",
34
+ "@opentelemetry/exporter-logs-otlp-grpc": "^0.202.0",
35
+ "@opentelemetry/exporter-logs-otlp-http": "^0.202.0",
36
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0.202.0",
37
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.202.0",
38
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.202.0",
39
+ "@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
40
+ "@opentelemetry/instrumentation": "^0.202.0",
41
+ "@opentelemetry/otlp-exporter-base": "^0.202.0",
42
+ "@opentelemetry/resources": "^2.0.1",
43
+ "@opentelemetry/sdk-logs": "^0.202.0",
44
+ "@opentelemetry/sdk-metrics": "^2.0.1",
45
+ "@opentelemetry/sdk-node": "^0.202.0",
46
+ "@opentelemetry/sdk-trace-base": "^2.0.1"
45
47
  },
46
48
  "devDependencies": {
47
- "@types/node": "^22.13.9",
48
- "@vitest/coverage-v8": "^3.0.8",
49
- "tsx": "^4.19.3",
50
- "typescript": "^5.8.2",
51
- "vitest": "^3.0.8"
49
+ "@types/node": "^24.0.3",
50
+ "@vitest/coverage-v8": "^3.2.4",
51
+ "tsx": "^4.20.3",
52
+ "typescript": "^5.8.3",
53
+ "vitest": "^3.2.4"
54
+ },
55
+ "engines": {
56
+ "node": ">=20.6.0"
52
57
  }
53
58
  }
@@ -21,10 +21,10 @@ export default defineConfig({
21
21
  reporters: ["default", ["junit", { outputFile: "test-report.xml" }]],
22
22
  environment: "node",
23
23
  pool: "threads",
24
- workspace: [
24
+ projects: [
25
25
  {
26
26
  test: {
27
- include: ["**/test/*.test.ts"],
27
+ include: ["**/test/*.test.ts", "**/test/processor/*.test.ts"],
28
28
  name: "unit",
29
29
  },
30
30
  },
@@ -8,8 +8,9 @@ import {
8
8
  SimpleSpanProcessor,
9
9
  } from "@opentelemetry/sdk-trace-base";
10
10
  import { NodeSDKConfig } from "../index.js";
11
- import { Exporters } from "../options.js";
11
+ import { Exporters } from "./index.js";
12
12
  import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
13
+ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
13
14
 
14
15
  export default function buildConsoleExporters(
15
16
  config: NodeSDKConfig,
@@ -22,6 +23,9 @@ export default function buildConsoleExporters(
22
23
  metrics: new metrics.PeriodicExportingMetricReader({
23
24
  exporter: new metrics.ConsoleMetricExporter(),
24
25
  }),
25
- logs: [new SimpleLogRecordProcessor(new ConsoleLogRecordExporter())],
26
+ logs: [
27
+ new EnrichLogProcessor(config.spanAttributes),
28
+ new SimpleLogRecordProcessor(new ConsoleLogRecordExporter()),
29
+ ],
26
30
  };
27
31
  }
@@ -4,17 +4,29 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
4
4
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
5
5
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
6
6
  import { NodeSDKConfig } from "../index.js";
7
- import { Exporters } from "../options.js";
7
+ import { Exporters } from "./index.js";
8
8
  import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
9
9
  import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
10
+ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
10
11
 
11
- export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
12
+ async function defaultMetadata() {
13
+ const { Metadata } = await import("@grpc/grpc-js");
14
+
15
+ return new Metadata({
16
+ waitForReady: true,
17
+ });
18
+ }
19
+
20
+ export default async function buildGrpcExporters(
21
+ config: NodeSDKConfig,
22
+ ): Promise<Exporters> {
12
23
  return {
13
24
  spans: [
14
25
  new SpanProcessorMap[config.collectorMode ?? "batch"](
15
26
  new OTLPTraceExporter({
16
27
  url: `${config.collectorUrl}`,
17
28
  compression: CompressionAlgorithm.GZIP,
29
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
18
30
  }),
19
31
  ),
20
32
  new EnrichSpanProcessor(config.spanAttributes),
@@ -23,13 +35,16 @@ export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
23
35
  exporter: new OTLPMetricExporter({
24
36
  url: `${config.collectorUrl}`,
25
37
  compression: CompressionAlgorithm.GZIP,
38
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
26
39
  }),
27
40
  }),
28
41
  logs: [
42
+ new EnrichLogProcessor(config.spanAttributes),
29
43
  new LogRecordProcessorMap[config.collectorMode ?? "batch"](
30
44
  new OTLPLogExporter({
31
45
  url: `${config.collectorUrl}`,
32
46
  compression: CompressionAlgorithm.GZIP,
47
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
33
48
  }),
34
49
  ),
35
50
  ],
@@ -5,8 +5,9 @@ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
5
5
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
6
6
  import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
7
7
  import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
8
- import { Exporters } from "../options.js";
8
+ import { Exporters } from "./index.js";
9
9
  import { NodeSDKConfig } from "../index.js";
10
+ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
10
11
 
11
12
  export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
12
13
  if (config.collectorUrl.endsWith("/")) {
@@ -30,6 +31,7 @@ export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
30
31
  }),
31
32
  }),
32
33
  logs: [
34
+ new EnrichLogProcessor(config.spanAttributes),
33
35
  new LogRecordProcessorMap[config.collectorMode ?? "batch"](
34
36
  new OTLPLogExporter({
35
37
  url: `${config.collectorUrl}/v1/logs`,
package/lib/index.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { Metadata } from "@grpc/grpc-js";
2
+
1
3
  export interface NodeSDKConfig {
2
4
  /**
3
5
  * The opentelemetry collector entrypoint GRPC url.
@@ -66,6 +68,13 @@ export interface NodeSDKConfig {
66
68
  * @default grpc
67
69
  */
68
70
  protocol?: SDKProtocol;
71
+
72
+ /**
73
+ * Grpc Metadata for the grpc-js client.
74
+ *
75
+ * @default { waitForReady: true }
76
+ */
77
+ grpcMetadata?: Metadata;
69
78
  }
70
79
 
71
80
  export interface SamplerCondition {
@@ -1,28 +1,24 @@
1
- import {
2
- diag,
3
- DiagConsoleLogger,
4
- DiagLogLevel,
5
- Span,
6
- } from "@opentelemetry/api";
1
+ import process from "process";
2
+ import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
7
3
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
8
4
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
9
- import { NodeSDK, resources } from "@opentelemetry/sdk-node";
5
+ import { NodeSDK } from "@opentelemetry/sdk-node";
10
6
  import {
11
7
  AlwaysOffSampler,
12
8
  ParentBasedSampler,
13
9
  TraceIdRatioBasedSampler,
14
10
  } from "@opentelemetry/sdk-trace-base";
15
- import packageJson from "../package.json" with { type: "json" };
16
11
  import buildConsoleExporters from "./exporter/console.js";
17
12
  import buildGrpcExporters from "./exporter/grpc.js";
18
13
  import buildHttpExporters from "./exporter/http.js";
14
+ import type { Exporters } from "./exporter/index.js";
19
15
  import type { NodeSDKConfig } from "./index.js";
20
- import type { Exporters } from "./options.js";
16
+ import { ObservabilityResourceDetector } from "./resource.js";
21
17
  import { UrlSampler } from "./url-sampler.js";
22
18
 
23
- export default function buildNodeInstrumentation(
19
+ export default async function buildNodeInstrumentation(
24
20
  config?: NodeSDKConfig,
25
- ): NodeSDK | undefined {
21
+ ): Promise<NodeSDK | undefined> {
26
22
  if (!config) {
27
23
  console.warn(
28
24
  "observability config not set. Skipping NodeJS OpenTelemetry instrumentation.",
@@ -44,16 +40,6 @@ export default function buildNodeInstrumentation(
44
40
  return;
45
41
  }
46
42
 
47
- let exporter: Exporters;
48
-
49
- if (config.protocol === "http") {
50
- exporter = buildHttpExporters(config);
51
- } else if (config.protocol === "console") {
52
- exporter = buildConsoleExporters(config);
53
- } else {
54
- exporter = buildGrpcExporters(config);
55
- }
56
-
57
43
  const urlSampler = new UrlSampler(
58
44
  config.ignoreUrls,
59
45
  new TraceIdRatioBasedSampler(config.traceRatio ?? 1),
@@ -67,52 +53,59 @@ export default function buildNodeInstrumentation(
67
53
  localParentNotSampled: new AlwaysOffSampler(),
68
54
  });
69
55
 
56
+ diag.setLogger(
57
+ new DiagConsoleLogger(),
58
+ config.diagLogLevel ? DiagLogLevel[config.diagLogLevel] : DiagLogLevel.INFO,
59
+ );
60
+
70
61
  try {
71
- diag.setLogger(
72
- new DiagConsoleLogger(),
73
- config.diagLogLevel
74
- ? DiagLogLevel[config.diagLogLevel]
75
- : DiagLogLevel.INFO,
76
- );
62
+ const nodeSdkInstrumentation = getNodeAutoInstrumentations({
63
+ "@opentelemetry/instrumentation-fs": {
64
+ enabled: config.enableFS ?? false,
65
+ },
66
+ });
67
+
68
+ let exporter: Exporters;
69
+
70
+ if (config.protocol === "http") {
71
+ exporter = buildHttpExporters(config);
72
+ } else if (config.protocol === "console") {
73
+ exporter = buildConsoleExporters(config);
74
+ } else {
75
+ exporter = await buildGrpcExporters(config);
76
+ }
77
77
 
78
78
  const sdk = new NodeSDK({
79
- resource: new resources.Resource({
80
- "o11y.sdk.name": packageJson.name,
81
- "o11y.sdk.version": packageJson.version,
82
- ...config.resourceAttributes,
83
- }),
79
+ resourceDetectors: [
80
+ new ObservabilityResourceDetector(config.resourceAttributes),
81
+ ],
84
82
  spanProcessors: exporter.spans,
85
83
  serviceName: config.serviceName,
86
84
  metricReader: exporter.metrics,
87
85
  logRecordProcessors: exporter.logs,
88
86
  sampler: mainSampler,
89
87
  textMapPropagator: new W3CTraceContextPropagator(),
90
- instrumentations: [
91
- getNodeAutoInstrumentations({
92
- "@opentelemetry/instrumentation-fs": {
93
- enabled: config.enableFS ?? false,
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
- },
110
- }),
111
- ],
88
+ instrumentations: [nodeSdkInstrumentation],
112
89
  });
113
90
 
114
91
  sdk.start();
115
92
  console.log("NodeJS OpenTelemetry instrumentation started successfully.");
93
+
94
+ process.on("SIGTERM", async () => {
95
+ try {
96
+ // Flushing before shutdown is implemented on a per-exporter basis.
97
+ await sdk.shutdown();
98
+ console.log(
99
+ "NodeJS OpenTelemetry instrumentation shutdown successfully",
100
+ );
101
+ } catch (error) {
102
+ console.error(
103
+ "Error shutting down NodeJS OpenTelemetry instrumentation:",
104
+ error,
105
+ );
106
+ }
107
+ });
108
+
116
109
  return sdk;
117
110
  } catch (error) {
118
111
  console.error(
@@ -0,0 +1,34 @@
1
+ import { LogRecord, LogRecordProcessor } from "@opentelemetry/sdk-logs";
2
+ import { Context } from "@opentelemetry/api";
3
+ import { SignalAttributeValue } from "../index.js";
4
+
5
+ export class EnrichLogProcessor implements LogRecordProcessor {
6
+ private _spanAttributes?:
7
+ | Record<string, SignalAttributeValue | (() => SignalAttributeValue)>
8
+ | undefined = undefined;
9
+
10
+ constructor(
11
+ spanAttributes?: Record<
12
+ string,
13
+ SignalAttributeValue | (() => SignalAttributeValue)
14
+ >,
15
+ ) {
16
+ this._spanAttributes = spanAttributes;
17
+ }
18
+ forceFlush(): Promise<void> {
19
+ return Promise.resolve();
20
+ }
21
+ onEmit(logRecord: LogRecord, _context?: Context): void {
22
+ if (this._spanAttributes) {
23
+ for (const [key, value] of Object.entries(this._spanAttributes)) {
24
+ logRecord.setAttribute(
25
+ key,
26
+ typeof value === "function" ? value() : value,
27
+ );
28
+ }
29
+ }
30
+ }
31
+ shutdown(): Promise<void> {
32
+ return Promise.resolve();
33
+ }
34
+ }
@@ -0,0 +1,30 @@
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.8",
3
+ "version": "0.2.0",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -19,28 +19,33 @@
19
19
  "author": "team:ogcio/observability",
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
+ "@grpc/grpc-js": "^1.13.4",
22
23
  "@opentelemetry/api": "^1.9.0",
23
- "@opentelemetry/auto-instrumentations-node": "^0.56.1",
24
- "@opentelemetry/core": "1.30.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",
34
- "@opentelemetry/sdk-metrics": "^1.30.1",
35
- "@opentelemetry/sdk-node": "^0.57.2",
36
- "@opentelemetry/sdk-trace-base": "^1.30.1"
24
+ "@opentelemetry/auto-instrumentations-node": "^0.60.1",
25
+ "@opentelemetry/core": "^2.0.1",
26
+ "@opentelemetry/exporter-logs-otlp-grpc": "^0.202.0",
27
+ "@opentelemetry/exporter-logs-otlp-http": "^0.202.0",
28
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0.202.0",
29
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.202.0",
30
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.202.0",
31
+ "@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
32
+ "@opentelemetry/instrumentation": "^0.202.0",
33
+ "@opentelemetry/otlp-exporter-base": "^0.202.0",
34
+ "@opentelemetry/resources": "^2.0.1",
35
+ "@opentelemetry/sdk-logs": "^0.202.0",
36
+ "@opentelemetry/sdk-metrics": "^2.0.1",
37
+ "@opentelemetry/sdk-node": "^0.202.0",
38
+ "@opentelemetry/sdk-trace-base": "^2.0.1"
37
39
  },
38
40
  "devDependencies": {
39
- "@types/node": "^22.13.9",
40
- "@vitest/coverage-v8": "^3.0.8",
41
- "tsx": "^4.19.3",
42
- "typescript": "^5.8.2",
43
- "vitest": "^3.0.8"
41
+ "@types/node": "^24.0.3",
42
+ "@vitest/coverage-v8": "^3.2.4",
43
+ "tsx": "^4.20.3",
44
+ "typescript": "^5.8.3",
45
+ "vitest": "^3.2.4"
46
+ },
47
+ "engines": {
48
+ "node": ">=20.6.0"
44
49
  },
45
50
  "scripts": {
46
51
  "build": "rm -rf dist && tsc -p tsconfig.json",
@@ -1,13 +1,27 @@
1
- import { describe, test, expect, vi } from "vitest";
1
+ import { describe, test, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import { NodeSDKConfig } from "../index";
3
3
  import { instrumentNode } from "../index";
4
-
5
- vi.mock("../lib/instrumentation.node", () => ({
6
- default: vi.fn(),
7
- }));
4
+ import * as buildNodeInstrumentationModule from "../lib/instrumentation.node";
5
+ import { metrics } from "@opentelemetry/sdk-node";
8
6
 
9
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
+
10
20
  test("should call buildNodeInstrumentation with the provided config", async () => {
21
+ const instrumentationMock = vi
22
+ .spyOn(buildNodeInstrumentationModule, "default")
23
+ .mockImplementation(vi.fn());
24
+
11
25
  const config: NodeSDKConfig = {
12
26
  serviceName: "custom-service",
13
27
  collectorUrl: "http://custom-collector.com",
@@ -23,16 +37,34 @@ describe("instrumentNode", () => {
23
37
  },
24
38
  };
25
39
 
26
- const buildNodeInstrumentation = await import(
27
- "../lib/instrumentation.node"
28
- );
40
+ await instrumentNode(config);
29
41
 
30
- instrumentNode(config);
42
+ expect(instrumentationMock).toHaveBeenCalledWith(config);
43
+ });
31
44
 
32
- expect(buildNodeInstrumentation.default).toHaveBeenCalledWith(config);
45
+ test("should not throw when called without arguments", async () => {
46
+ await expect(instrumentNode()).resolves.not.toThrow();
33
47
  });
34
48
 
35
- test("should not throw when called without arguments", () => {
36
- expect(() => instrumentNode()).not.toThrow();
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();
37
69
  });
38
70
  });
@@ -22,5 +22,5 @@ The `run.sh` script performs the following steps:
22
22
  - run fastify app in a docker container
23
23
  - ensure is running otherwise exit process
24
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/`
25
+ - persist alloy log to a file and save to following path `/packages/sdk-node/test/integration/`
26
26
  - docker turn down process (containers/network/image)
@@ -1,18 +1,11 @@
1
1
  import { test, describe, assert, expect } from "vitest";
2
2
  import buildNodeInstrumentation from "../lib/instrumentation.node.js";
3
- import { NodeSDK, logs, metrics, tracing } from "@opentelemetry/sdk-node";
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";
3
+ import { NodeSDK, metrics } from "@opentelemetry/sdk-node";
4
+
11
5
  import { NodeSDKConfig } from "../lib/index.js";
12
6
  import buildHttpExporters from "../lib/exporter/http.js";
13
7
  import {
14
8
  BatchSpanProcessor,
15
- ConsoleSpanExporter,
16
9
  SimpleSpanProcessor,
17
10
  } from "@opentelemetry/sdk-trace-base";
18
11
  import {
@@ -27,7 +20,7 @@ describe("verify config settings", () => {
27
20
  serviceName: "test",
28
21
  };
29
22
 
30
- test("grpc config", () => {
23
+ test("grpc config", async () => {
31
24
  const config: NodeSDKConfig = {
32
25
  ...commonConfig,
33
26
  protocol: "grpc",
@@ -35,7 +28,7 @@ describe("verify config settings", () => {
35
28
  diagLogLevel: "NONE",
36
29
  };
37
30
 
38
- const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
31
+ const sdk: NodeSDK | undefined = await buildNodeInstrumentation(config);
39
32
 
40
33
  assert.ok(sdk);
41
34
 
@@ -44,8 +37,8 @@ describe("verify config settings", () => {
44
37
 
45
38
  const logs = _configuration.logRecordProcessors;
46
39
 
47
- assert.equal(logs.length, 1);
48
- assert.ok(logs[0] instanceof BatchLogRecordProcessor);
40
+ assert.equal(logs.length, 2);
41
+ assert.ok(logs[1] instanceof BatchLogRecordProcessor);
49
42
 
50
43
  const spans = _configuration.spanProcessors;
51
44
 
@@ -59,14 +52,14 @@ describe("verify config settings", () => {
59
52
  );
60
53
  });
61
54
 
62
- test("http config", () => {
55
+ test("http config", async () => {
63
56
  const config: NodeSDKConfig = {
64
57
  ...commonConfig,
65
58
  protocol: "http",
66
59
  diagLogLevel: "NONE",
67
60
  };
68
61
 
69
- const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
62
+ const sdk: NodeSDK | undefined = await buildNodeInstrumentation(config);
70
63
  assert.ok(sdk);
71
64
 
72
65
  const _configuration = sdk["_configuration"];
@@ -74,8 +67,8 @@ describe("verify config settings", () => {
74
67
 
75
68
  const logs = _configuration.logRecordProcessors;
76
69
 
77
- assert.equal(logs.length, 1);
78
- assert.ok(logs[0] instanceof BatchLogRecordProcessor);
70
+ assert.equal(logs.length, 2);
71
+ assert.ok(logs[1] instanceof BatchLogRecordProcessor);
79
72
 
80
73
  const spans = _configuration.spanProcessors;
81
74
 
@@ -89,14 +82,14 @@ describe("verify config settings", () => {
89
82
  );
90
83
  });
91
84
 
92
- test("console - console config", () => {
85
+ test("console - console config", async () => {
93
86
  const config: NodeSDKConfig = {
94
87
  ...commonConfig,
95
88
  protocol: "console",
96
89
  diagLogLevel: "NONE",
97
90
  };
98
91
 
99
- const sdk: NodeSDK = buildNodeInstrumentation(config)!;
92
+ const sdk: NodeSDK = await buildNodeInstrumentation(config)!;
100
93
  assert.ok(sdk);
101
94
 
102
95
  const _configuration = sdk["_configuration"];
@@ -105,8 +98,8 @@ describe("verify config settings", () => {
105
98
  const logs = _configuration.logRecordProcessors;
106
99
 
107
100
  // verify simple log processor for instant console logging
108
- assert.equal(logs.length, 1);
109
- assert.ok(logs[0] instanceof SimpleLogRecordProcessor);
101
+ assert.equal(logs.length, 2);
102
+ assert.ok(logs[1] instanceof SimpleLogRecordProcessor);
110
103
 
111
104
  const spans = _configuration.spanProcessors;
112
105
 
@@ -121,7 +114,7 @@ describe("verify config settings", () => {
121
114
  );
122
115
  });
123
116
 
124
- test("single log sending config", () => {
117
+ test("single log sending config", async () => {
125
118
  const config: NodeSDKConfig = {
126
119
  ...commonConfig,
127
120
  protocol: "grpc",
@@ -129,15 +122,15 @@ describe("verify config settings", () => {
129
122
  collectorMode: "single",
130
123
  };
131
124
 
132
- const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
125
+ const sdk: NodeSDK | undefined = await buildNodeInstrumentation(config);
133
126
 
134
127
  assert.ok(sdk);
135
128
 
136
129
  const _configuration = sdk["_configuration"];
137
130
 
138
131
  const logRecordProcessors = _configuration.logRecordProcessors;
139
- assert.equal(logRecordProcessors.length, 1);
140
- assert.ok(logRecordProcessors[0] instanceof SimpleLogRecordProcessor);
132
+ assert.equal(logRecordProcessors.length, 2);
133
+ assert.ok(logRecordProcessors[1] instanceof SimpleLogRecordProcessor);
141
134
  });
142
135
 
143
136
  test("check if clear base endpoint final slash", () => {
@@ -0,0 +1,58 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { LogRecord } from "@opentelemetry/sdk-logs";
3
+ import { EnrichLogProcessor } from "../../lib/processor/enrich-logger-processor.js";
4
+
5
+ const createMockLogRecord = () => {
6
+ return {
7
+ setAttribute: vi.fn(),
8
+ } as unknown as LogRecord;
9
+ };
10
+
11
+ describe("EnrichLogProcessor", () => {
12
+ it("should enrich log record with static attributes", () => {
13
+ const attributes = { key1: "value1", key2: 42 };
14
+ const processor = new EnrichLogProcessor(attributes);
15
+ const mockLogRecord = createMockLogRecord();
16
+
17
+ processor.onEmit(mockLogRecord);
18
+
19
+ expect(mockLogRecord.setAttribute).toHaveBeenCalledWith("key1", "value1");
20
+ expect(mockLogRecord.setAttribute).toHaveBeenCalledWith("key2", 42);
21
+ });
22
+
23
+ it("should enrich log record with dynamic attributes", () => {
24
+ const attributes = {
25
+ key1: () => "dynamicValue",
26
+ key2: () => 100,
27
+ };
28
+ const processor = new EnrichLogProcessor(attributes);
29
+ const mockLogRecord = createMockLogRecord();
30
+
31
+ processor.onEmit(mockLogRecord);
32
+
33
+ expect(mockLogRecord.setAttribute).toHaveBeenCalledWith(
34
+ "key1",
35
+ "dynamicValue",
36
+ );
37
+ expect(mockLogRecord.setAttribute).toHaveBeenCalledWith("key2", 100);
38
+ });
39
+
40
+ it("should not set attributes if no span attributes are provided", () => {
41
+ const processor = new EnrichLogProcessor();
42
+ const mockLogRecord = createMockLogRecord();
43
+
44
+ processor.onEmit(mockLogRecord);
45
+
46
+ expect(mockLogRecord.setAttribute).not.toHaveBeenCalled();
47
+ });
48
+
49
+ it("should reject forceFlush", async () => {
50
+ await expect(
51
+ new EnrichLogProcessor().forceFlush(),
52
+ ).resolves.toBeUndefined();
53
+ });
54
+
55
+ it("should resolve shutdown", async () => {
56
+ await expect(new EnrichLogProcessor().shutdown()).resolves.toBeUndefined();
57
+ });
58
+ });
@@ -5,13 +5,12 @@ import {
5
5
  Link,
6
6
  Span,
7
7
  SpanAttributes,
8
- SpanAttributeValue,
9
8
  SpanContext,
10
9
  SpanStatus,
11
10
  TimeInput,
12
11
  } from "@opentelemetry/api";
13
12
  import { describe, expect, it } from "vitest";
14
- import { EnrichSpanProcessor } from "../lib/processor/enrich-span-processor.js";
13
+ import { EnrichSpanProcessor } from "../../lib/processor/enrich-span-processor.js";
15
14
 
16
15
  class MockSpan implements Span {
17
16
  public attributes: Record<string, AttributeValue> = {};
@@ -0,0 +1,33 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { ObservabilityResourceDetector } from "../lib/resource";
3
+
4
+ describe("ObservabilityResourceDetector", () => {
5
+ test("should return custom resource attribute", () => {
6
+ const detector = new ObservabilityResourceDetector({
7
+ first: "first_value",
8
+ second: "second_value",
9
+ });
10
+ const result = detector.detect();
11
+
12
+ expect(result.attributes).not.toBeNull();
13
+ expect(result.attributes).toHaveProperty("first");
14
+ expect(result.attributes!["first"]).eq("first_value");
15
+ expect(result.attributes).toHaveProperty("second");
16
+ expect(result.attributes!["second"]).eq("second_value");
17
+ // default
18
+ expect(result.attributes).toHaveProperty("o11y.sdk.name");
19
+ expect(result.attributes!["o11y.sdk.name"]).eq("@ogcio/o11y-sdk-node");
20
+ expect(result.attributes).toHaveProperty("o11y.sdk.version");
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
+ });
33
+ });
@@ -10,7 +10,7 @@ describe("validation config: should return without breaking the execution", () =
10
10
  .spyOn(console, "warn")
11
11
  .mockImplementation(vi.fn());
12
12
 
13
- instrumentNode();
13
+ await instrumentNode();
14
14
 
15
15
  expect(consoleWarnSpy).toHaveBeenCalled();
16
16
  expect(consoleWarnSpy).toHaveBeenCalledWith(
@@ -26,11 +26,13 @@ describe("validation config: should return without breaking the execution", () =
26
26
  .mockImplementation(vi.fn());
27
27
  const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(vi.fn());
28
28
 
29
- assert.doesNotThrow(() => {
30
- sdk = buildNodeInstrumentation({
31
- collectorUrl: undefined!,
32
- });
33
- });
29
+ await expect(
30
+ buildNodeInstrumentation({
31
+ collectorUrl: undefined,
32
+ }).then((result) => {
33
+ sdk = result;
34
+ }),
35
+ ).resolves.not.toThrowError();
34
36
 
35
37
  assert.equal(sdk, undefined);
36
38
 
@@ -41,7 +43,7 @@ describe("validation config: should return without breaking the execution", () =
41
43
  expect(consoleLogSpy).not.toHaveBeenCalled();
42
44
  });
43
45
 
44
- test("node instrumentation: invalid url", () => {
46
+ test("node instrumentation: invalid url", async () => {
45
47
  let sdk: NodeSDK | undefined = undefined;
46
48
 
47
49
  const consoleErrorSpy = vi
@@ -49,11 +51,13 @@ describe("validation config: should return without breaking the execution", () =
49
51
  .mockImplementation(vi.fn());
50
52
  const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(vi.fn());
51
53
 
52
- assert.doesNotThrow(() => {
53
- sdk = buildNodeInstrumentation({
54
+ await expect(
55
+ buildNodeInstrumentation({
54
56
  collectorUrl: "notavalidURL",
55
- });
56
- });
57
+ }).then((result) => {
58
+ sdk = result;
59
+ }),
60
+ ).resolves.not.toThrowError();
57
61
 
58
62
  assert.equal(sdk, undefined);
59
63
 
@@ -64,7 +68,7 @@ describe("validation config: should return without breaking the execution", () =
64
68
  expect(consoleLogSpy).not.toHaveBeenCalled();
65
69
  });
66
70
 
67
- test("node instrumentation: verify no instrumentation if exception occurs", () => {
71
+ test("node instrumentation: verify no instrumentation if exception occurs", async () => {
68
72
  let sdk: NodeSDK | undefined = undefined;
69
73
 
70
74
  const consoleErrorSpy = vi
@@ -78,12 +82,14 @@ describe("validation config: should return without breaking the execution", () =
78
82
  }),
79
83
  }));
80
84
 
81
- assert.doesNotThrow(() => {
82
- sdk = buildNodeInstrumentation({
85
+ await expect(
86
+ buildNodeInstrumentation({
83
87
  collectorUrl: "https://testurl.com",
84
88
  serviceName: "test",
85
- });
86
- });
89
+ }).then((result) => {
90
+ sdk = result;
91
+ }),
92
+ ).resolves.not.toThrowError();
87
93
 
88
94
  assert.equal(sdk, undefined);
89
95
 
package/vitest.config.ts CHANGED
@@ -22,10 +22,10 @@ export default defineConfig({
22
22
  reporters: ["default", ["junit", { outputFile: "test-report.xml" }]],
23
23
  environment: "node",
24
24
  pool: "threads",
25
- workspace: [
25
+ projects: [
26
26
  {
27
27
  test: {
28
- include: ["**/test/*.test.ts"],
28
+ include: ["**/test/*.test.ts", "**/test/processor/*.test.ts"],
29
29
  name: "unit",
30
30
  },
31
31
  },
File without changes
File without changes
File without changes