@ogcio/o11y-sdk-node 0.1.0-beta.9 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,71 @@
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
+
3
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)
4
70
 
5
71
 
@@ -1,3 +1,3 @@
1
1
  import { NodeSDKConfig } from "../index.js";
2
2
  import { Exporters } from "./index.js";
3
- export default function buildGrpcExporters(config: NodeSDKConfig): Exporters;
3
+ export default function buildGrpcExporters(config: NodeSDKConfig): Promise<Exporters>;
@@ -6,12 +6,19 @@ 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
8
  import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
9
- export default function buildGrpcExporters(config) {
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) {
10
16
  return {
11
17
  spans: [
12
18
  new SpanProcessorMap[config.collectorMode ?? "batch"](new OTLPTraceExporter({
13
19
  url: `${config.collectorUrl}`,
14
20
  compression: CompressionAlgorithm.GZIP,
21
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
15
22
  })),
16
23
  new EnrichSpanProcessor(config.spanAttributes),
17
24
  ],
@@ -19,6 +26,7 @@ export default function buildGrpcExporters(config) {
19
26
  exporter: new OTLPMetricExporter({
20
27
  url: `${config.collectorUrl}`,
21
28
  compression: CompressionAlgorithm.GZIP,
29
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
22
30
  }),
23
31
  }),
24
32
  logs: [
@@ -26,6 +34,7 @@ export default function buildGrpcExporters(config) {
26
34
  new LogRecordProcessorMap[config.collectorMode ?? "batch"](new OTLPLogExporter({
27
35
  url: `${config.collectorUrl}`,
28
36
  compression: CompressionAlgorithm.GZIP,
37
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
29
38
  })),
30
39
  ],
31
40
  };
@@ -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,3 +1,4 @@
1
+ import process from "process";
1
2
  import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
2
3
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
3
4
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -8,7 +9,7 @@ import buildGrpcExporters from "./exporter/grpc.js";
8
9
  import buildHttpExporters from "./exporter/http.js";
9
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,10 +30,23 @@ 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
51
  resourceDetectors: [
48
52
  new ObservabilityResourceDetector(config.resourceAttributes),
@@ -53,16 +57,20 @@ export default function buildNodeInstrumentation(config) {
53
57
  logRecordProcessors: exporter.logs,
54
58
  sampler: mainSampler,
55
59
  textMapPropagator: new W3CTraceContextPropagator(),
56
- instrumentations: [
57
- getNodeAutoInstrumentations({
58
- "@opentelemetry/instrumentation-fs": {
59
- enabled: config.enableFS ?? false,
60
- },
61
- }),
62
- ],
60
+ instrumentations: [nodeSdkInstrumentation],
63
61
  });
64
62
  sdk.start();
65
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
+ });
66
74
  return sdk;
67
75
  }
68
76
  catch (error) {
@@ -9,10 +9,10 @@ export class ObservabilityResourceDetector {
9
9
  if (this._resourceAttributes) {
10
10
  attributes = {
11
11
  ...this._resourceAttributes,
12
- "o11y.sdk.name": packageJson.name,
13
- "o11y.sdk.version": packageJson.version,
14
12
  };
15
13
  }
14
+ attributes["o11y.sdk.name"] = packageJson.name;
15
+ attributes["o11y.sdk.version"] = packageJson.version;
16
16
  return { attributes };
17
17
  }
18
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.9",
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,29 +27,30 @@
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.57.0",
32
- "@opentelemetry/core": "^2.0.0",
33
- "@opentelemetry/exporter-logs-otlp-grpc": "^0.200.0",
34
- "@opentelemetry/exporter-logs-otlp-http": "^0.200.0",
35
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0.200.0",
36
- "@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
37
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.200.0",
38
- "@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
39
- "@opentelemetry/instrumentation": "^0.200.0",
40
- "@opentelemetry/otlp-exporter-base": "^0.200.0",
41
- "@opentelemetry/resources": "^2.0.0",
42
- "@opentelemetry/sdk-logs": "^0.200.0",
43
- "@opentelemetry/sdk-metrics": "^2.0.0",
44
- "@opentelemetry/sdk-node": "^0.200.0",
45
- "@opentelemetry/sdk-trace-base": "^2.0.0"
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"
46
47
  },
47
48
  "devDependencies": {
48
- "@types/node": "^22.13.9",
49
- "@vitest/coverage-v8": "^3.0.8",
50
- "tsx": "^4.19.3",
51
- "typescript": "^5.8.2",
52
- "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"
53
54
  },
54
55
  "engines": {
55
56
  "node": ">=20.6.0"
@@ -21,7 +21,7 @@ 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
27
  include: ["**/test/*.test.ts", "**/test/processor/*.test.ts"],
@@ -9,13 +9,24 @@ import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
9
9
  import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
10
10
  import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
11
11
 
12
- 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> {
13
23
  return {
14
24
  spans: [
15
25
  new SpanProcessorMap[config.collectorMode ?? "batch"](
16
26
  new OTLPTraceExporter({
17
27
  url: `${config.collectorUrl}`,
18
28
  compression: CompressionAlgorithm.GZIP,
29
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
19
30
  }),
20
31
  ),
21
32
  new EnrichSpanProcessor(config.spanAttributes),
@@ -24,6 +35,7 @@ export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
24
35
  exporter: new OTLPMetricExporter({
25
36
  url: `${config.collectorUrl}`,
26
37
  compression: CompressionAlgorithm.GZIP,
38
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
27
39
  }),
28
40
  }),
29
41
  logs: [
@@ -32,6 +44,7 @@ export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
32
44
  new OTLPLogExporter({
33
45
  url: `${config.collectorUrl}`,
34
46
  compression: CompressionAlgorithm.GZIP,
47
+ metadata: config.grpcMetadata ?? (await defaultMetadata()),
35
48
  }),
36
49
  ),
37
50
  ],
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,3 +1,4 @@
1
+ import process from "process";
1
2
  import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
2
3
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
3
4
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -15,9 +16,9 @@ import type { NodeSDKConfig } from "./index.js";
15
16
  import { ObservabilityResourceDetector } from "./resource.js";
16
17
  import { UrlSampler } from "./url-sampler.js";
17
18
 
18
- export default function buildNodeInstrumentation(
19
+ export default async function buildNodeInstrumentation(
19
20
  config?: NodeSDKConfig,
20
- ): NodeSDK | undefined {
21
+ ): Promise<NodeSDK | undefined> {
21
22
  if (!config) {
22
23
  console.warn(
23
24
  "observability config not set. Skipping NodeJS OpenTelemetry instrumentation.",
@@ -39,16 +40,6 @@ export default function buildNodeInstrumentation(
39
40
  return;
40
41
  }
41
42
 
42
- let exporter: Exporters;
43
-
44
- if (config.protocol === "http") {
45
- exporter = buildHttpExporters(config);
46
- } else if (config.protocol === "console") {
47
- exporter = buildConsoleExporters(config);
48
- } else {
49
- exporter = buildGrpcExporters(config);
50
- }
51
-
52
43
  const urlSampler = new UrlSampler(
53
44
  config.ignoreUrls,
54
45
  new TraceIdRatioBasedSampler(config.traceRatio ?? 1),
@@ -62,13 +53,27 @@ export default function buildNodeInstrumentation(
62
53
  localParentNotSampled: new AlwaysOffSampler(),
63
54
  });
64
55
 
56
+ diag.setLogger(
57
+ new DiagConsoleLogger(),
58
+ config.diagLogLevel ? DiagLogLevel[config.diagLogLevel] : DiagLogLevel.INFO,
59
+ );
60
+
65
61
  try {
66
- diag.setLogger(
67
- new DiagConsoleLogger(),
68
- config.diagLogLevel
69
- ? DiagLogLevel[config.diagLogLevel]
70
- : DiagLogLevel.INFO,
71
- );
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
+ }
72
77
 
73
78
  const sdk = new NodeSDK({
74
79
  resourceDetectors: [
@@ -80,17 +85,27 @@ export default function buildNodeInstrumentation(
80
85
  logRecordProcessors: exporter.logs,
81
86
  sampler: mainSampler,
82
87
  textMapPropagator: new W3CTraceContextPropagator(),
83
- instrumentations: [
84
- getNodeAutoInstrumentations({
85
- "@opentelemetry/instrumentation-fs": {
86
- enabled: config.enableFS ?? false,
87
- },
88
- }),
89
- ],
88
+ instrumentations: [nodeSdkInstrumentation],
90
89
  });
91
90
 
92
91
  sdk.start();
93
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
+
94
109
  return sdk;
95
110
  } catch (error) {
96
111
  console.error(
package/lib/resource.ts CHANGED
@@ -19,11 +19,12 @@ export class ObservabilityResourceDetector implements ResourceDetector {
19
19
  if (this._resourceAttributes) {
20
20
  attributes = {
21
21
  ...this._resourceAttributes,
22
- "o11y.sdk.name": packageJson.name,
23
- "o11y.sdk.version": packageJson.version,
24
22
  };
25
23
  }
26
24
 
25
+ attributes["o11y.sdk.name"] = packageJson.name;
26
+ attributes["o11y.sdk.version"] = packageJson.version;
27
+
27
28
  return { attributes };
28
29
  }
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.9",
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,29 +19,30 @@
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.57.0",
24
- "@opentelemetry/core": "^2.0.0",
25
- "@opentelemetry/exporter-logs-otlp-grpc": "^0.200.0",
26
- "@opentelemetry/exporter-logs-otlp-http": "^0.200.0",
27
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0.200.0",
28
- "@opentelemetry/exporter-metrics-otlp-http": "^0.200.0",
29
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.200.0",
30
- "@opentelemetry/exporter-trace-otlp-http": "^0.200.0",
31
- "@opentelemetry/instrumentation": "^0.200.0",
32
- "@opentelemetry/otlp-exporter-base": "^0.200.0",
33
- "@opentelemetry/resources": "^2.0.0",
34
- "@opentelemetry/sdk-logs": "^0.200.0",
35
- "@opentelemetry/sdk-metrics": "^2.0.0",
36
- "@opentelemetry/sdk-node": "^0.200.0",
37
- "@opentelemetry/sdk-trace-base": "^2.0.0"
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"
38
39
  },
39
40
  "devDependencies": {
40
- "@types/node": "^22.13.9",
41
- "@vitest/coverage-v8": "^3.0.8",
42
- "tsx": "^4.19.3",
43
- "typescript": "^5.8.2",
44
- "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"
45
46
  },
46
47
  "engines": {
47
48
  "node": ">=20.6.0"
@@ -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
 
@@ -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"];
@@ -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"];
@@ -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,7 +122,7 @@ 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
 
@@ -1,4 +1,4 @@
1
- import { describe, test, expect, vi } from "vitest";
1
+ import { describe, test, expect } from "vitest";
2
2
  import { ObservabilityResourceDetector } from "../lib/resource";
3
3
 
4
4
  describe("ObservabilityResourceDetector", () => {
@@ -19,4 +19,15 @@ describe("ObservabilityResourceDetector", () => {
19
19
  expect(result.attributes!["o11y.sdk.name"]).eq("@ogcio/o11y-sdk-node");
20
20
  expect(result.attributes).toHaveProperty("o11y.sdk.version");
21
21
  });
22
+
23
+ test("should return default resource attribute", () => {
24
+ const detector = new ObservabilityResourceDetector();
25
+ const result = detector.detect();
26
+
27
+ expect(result.attributes).not.toBeNull();
28
+ // default
29
+ expect(result.attributes).toHaveProperty("o11y.sdk.name");
30
+ expect(result.attributes!["o11y.sdk.name"]).eq("@ogcio/o11y-sdk-node");
31
+ expect(result.attributes).toHaveProperty("o11y.sdk.version");
32
+ });
22
33
  });
@@ -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,7 +22,7 @@ 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
28
  include: ["**/test/*.test.ts", "**/test/processor/*.test.ts"],