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

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 (34) hide show
  1. package/CHANGELOG.md +20 -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 +1 -1
  5. package/dist/lib/exporter/grpc.js +2 -0
  6. package/dist/lib/exporter/http.d.ts +1 -1
  7. package/dist/lib/exporter/http.js +2 -0
  8. package/dist/lib/instrumentation.node.js +7 -19
  9. package/dist/lib/metrics.d.ts +12 -12
  10. package/dist/lib/metrics.js +6 -10
  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 +24 -20
  16. package/dist/vitest.config.js +1 -1
  17. package/lib/exporter/console.ts +6 -2
  18. package/lib/exporter/grpc.ts +3 -1
  19. package/lib/exporter/http.ts +3 -1
  20. package/lib/instrumentation.node.ts +8 -31
  21. package/lib/metrics.ts +34 -29
  22. package/lib/processor/enrich-logger-processor.ts +34 -0
  23. package/lib/resource.ts +29 -0
  24. package/package.json +24 -20
  25. package/test/integration/run.sh +3 -0
  26. package/test/metrics.test.ts +9 -9
  27. package/test/node-config.test.ts +8 -8
  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 +22 -0
  31. package/vitest.config.ts +1 -1
  32. /package/dist/lib/{options.d.ts → exporter/index.d.ts} +0 -0
  33. /package/dist/lib/{options.js → exporter/index.js} +0 -0
  34. /package/lib/{options.ts → exporter/index.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## [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
+
5
+
6
+ ### Features
7
+
8
+ * add custom log processor for span enrich ([#102](https://github.com/ogcio/o11y/issues/102)) ([bbf8334](https://github.com/ogcio/o11y/commit/bbf83340940ed651dff63bbe7aaa52881d1e8c8c))
9
+ * 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))
10
+
11
+ ## [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)
12
+
13
+
14
+ ### Features
15
+
16
+ * 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))
17
+
18
+
19
+ ### Bug Fixes
20
+
21
+ * improve getMetric attributes types ([#76](https://github.com/ogcio/o11y/issues/76)) ([243649c](https://github.com/ogcio/o11y/commit/243649c4bfe750687a729fbd836772cce16e0cb1))
22
+
3
23
  ## [0.1.0-beta.7](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@v0.1.0-beta.6...@ogcio/o11y-sdk-node@v0.1.0-beta.7) (2025-02-18)
4
24
 
5
25
 
@@ -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";
2
+ import { Exporters } from "./index.js";
3
3
  export default function buildGrpcExporters(config: NodeSDKConfig): Exporters;
@@ -5,6 +5,7 @@ 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
+ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
8
9
  export default function buildGrpcExporters(config) {
9
10
  return {
10
11
  spans: [
@@ -21,6 +22,7 @@ export default function buildGrpcExporters(config) {
21
22
  }),
22
23
  }),
23
24
  logs: [
25
+ new EnrichLogProcessor(config.spanAttributes),
24
26
  new LogRecordProcessorMap[config.collectorMode ?? "batch"](new OTLPLogExporter({
25
27
  url: `${config.collectorUrl}`,
26
28
  compression: CompressionAlgorithm.GZIP,
@@ -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,12 +1,12 @@
1
- import { diag, DiagConsoleLogger, DiagLogLevel, } from "@opentelemetry/api";
1
+ import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
2
2
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
3
3
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
4
- import { NodeSDK, resources } from "@opentelemetry/sdk-node";
4
+ import { NodeSDK } from "@opentelemetry/sdk-node";
5
5
  import { AlwaysOffSampler, ParentBasedSampler, TraceIdRatioBasedSampler, } from "@opentelemetry/sdk-trace-base";
6
- import packageJson from "../package.json" with { type: "json" };
7
6
  import buildConsoleExporters from "./exporter/console.js";
8
7
  import buildGrpcExporters from "./exporter/grpc.js";
9
8
  import buildHttpExporters from "./exporter/http.js";
9
+ import { ObservabilityResourceDetector } from "./resource.js";
10
10
  import { UrlSampler } from "./url-sampler.js";
11
11
  export default function buildNodeInstrumentation(config) {
12
12
  if (!config) {
@@ -44,11 +44,9 @@ export default function buildNodeInstrumentation(config) {
44
44
  ? DiagLogLevel[config.diagLogLevel]
45
45
  : DiagLogLevel.INFO);
46
46
  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
- }),
47
+ resourceDetectors: [
48
+ new ObservabilityResourceDetector(config.resourceAttributes),
49
+ ],
52
50
  spanProcessors: exporter.spans,
53
51
  serviceName: config.serviceName,
54
52
  metricReader: exporter.metrics,
@@ -60,15 +58,6 @@ export default function buildNodeInstrumentation(config) {
60
58
  "@opentelemetry/instrumentation-fs": {
61
59
  enabled: config.enableFS ?? false,
62
60
  },
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
61
  }),
73
62
  ],
74
63
  });
@@ -85,8 +74,7 @@ function isUrl(url) {
85
74
  new URL(url);
86
75
  return true;
87
76
  }
88
- catch (err) {
89
- console.error(err);
77
+ catch (_) {
90
78
  return false;
91
79
  }
92
80
  }
@@ -1,18 +1,18 @@
1
- import { Counter, Gauge, Histogram, MetricOptions, ObservableCounter, ObservableGauge, ObservableUpDownCounter, UpDownCounter } from "@opentelemetry/api";
2
- declare const MetricsMap: {
3
- gauge: Gauge;
4
- histogram: Histogram;
5
- counter: Counter;
6
- updowncounter: UpDownCounter;
7
- "async-counter": ObservableCounter;
8
- "async-updowncounter": ObservableUpDownCounter;
9
- "async-gauge": ObservableGauge;
1
+ import { Counter, Gauge, Histogram, MetricOptions, ObservableCounter, ObservableGauge, ObservableUpDownCounter, UpDownCounter, Attributes } from "@opentelemetry/api";
2
+ type MetricTypeMap<TAttributes extends Attributes> = {
3
+ counter: Counter<TAttributes>;
4
+ histogram: Histogram<TAttributes>;
5
+ gauge: Gauge<TAttributes>;
6
+ updowncounter: UpDownCounter<TAttributes>;
7
+ "async-counter": ObservableCounter<TAttributes>;
8
+ "async-updowncounter": ObservableUpDownCounter<TAttributes>;
9
+ "async-gauge": ObservableGauge<TAttributes>;
10
10
  };
11
- type MetricType = keyof typeof MetricsMap;
11
+ export type MetricType = keyof MetricTypeMap<Attributes>;
12
12
  export interface MetricsParams {
13
+ meterName: string;
13
14
  metricName: string;
14
- attributeName: string;
15
15
  options?: MetricOptions;
16
16
  }
17
- export declare function getMetric<T extends MetricType>(type: T, p: MetricsParams): (typeof MetricsMap)[T];
17
+ export declare function getMetric<T extends MetricType, TAttributes extends Attributes = Attributes>(type: T, p: MetricsParams): MetricTypeMap<TAttributes>[T];
18
18
  export {};
@@ -8,21 +8,17 @@ const MetricsFactoryMap = {
8
8
  "async-updowncounter": (meter) => meter.createObservableUpDownCounter,
9
9
  "async-gauge": (meter) => meter.createObservableGauge,
10
10
  };
11
- function getMeter({ metricName, attributeName }) {
12
- let meter;
13
- if (!metricName || !attributeName) {
14
- console.error("Invaid metric configuration!");
15
- meter = createNoopMeter();
11
+ function getMeter({ meterName }) {
12
+ if (!meterName) {
13
+ console.error("Invalid metric name!");
14
+ return createNoopMeter();
16
15
  }
17
- else {
18
- meter = metrics.getMeter(`custom_metric.${metricName}`);
19
- }
20
- return meter;
16
+ return metrics.getMeter(`custom_metric.${meterName}`);
21
17
  }
22
18
  export function getMetric(type, p) {
23
19
  const meter = getMeter(p);
24
20
  if (!MetricsFactoryMap[type]) {
25
21
  throw new Error(`Unsupported metric type: ${type}`);
26
22
  }
27
- return MetricsFactoryMap[type](meter).bind(meter)(p.attributeName, p.options);
23
+ return MetricsFactoryMap[type](meter).bind(meter)(p.metricName, p.options);
28
24
  }
@@ -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
+ "o11y.sdk.name": packageJson.name,
13
+ "o11y.sdk.version": packageJson.version,
14
+ };
15
+ }
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.7",
3
+ "version": "0.1.0-beta.9",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -28,26 +28,30 @@
28
28
  "license": "ISC",
29
29
  "dependencies": {
30
30
  "@opentelemetry/api": "^1.9.0",
31
- "@opentelemetry/auto-instrumentations-node": "^0.56.0",
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"
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"
45
46
  },
46
47
  "devDependencies": {
47
- "@types/node": "^22.13.4",
48
- "@vitest/coverage-v8": "^3.0.5",
49
- "tsx": "^4.19.2",
50
- "typescript": "^5.7.3",
51
- "vitest": "^3.0.5"
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"
53
+ },
54
+ "engines": {
55
+ "node": ">=20.6.0"
52
56
  }
53
57
  }
@@ -24,7 +24,7 @@ export default defineConfig({
24
24
  workspace: [
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,9 +4,10 @@ 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
12
  export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
12
13
  return {
@@ -26,6 +27,7 @@ export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
26
27
  }),
27
28
  }),
28
29
  logs: [
30
+ new EnrichLogProcessor(config.spanAttributes),
29
31
  new LogRecordProcessorMap[config.collectorMode ?? "batch"](
30
32
  new OTLPLogExporter({
31
33
  url: `${config.collectorUrl}`,
@@ -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`,
@@ -1,23 +1,18 @@
1
- import {
2
- diag,
3
- DiagConsoleLogger,
4
- DiagLogLevel,
5
- Span,
6
- } from "@opentelemetry/api";
1
+ import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
7
2
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
8
3
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
9
- import { NodeSDK, resources } from "@opentelemetry/sdk-node";
4
+ import { NodeSDK } from "@opentelemetry/sdk-node";
10
5
  import {
11
6
  AlwaysOffSampler,
12
7
  ParentBasedSampler,
13
8
  TraceIdRatioBasedSampler,
14
9
  } from "@opentelemetry/sdk-trace-base";
15
- import packageJson from "../package.json" with { type: "json" };
16
10
  import buildConsoleExporters from "./exporter/console.js";
17
11
  import buildGrpcExporters from "./exporter/grpc.js";
18
12
  import buildHttpExporters from "./exporter/http.js";
13
+ import type { Exporters } from "./exporter/index.js";
19
14
  import type { NodeSDKConfig } from "./index.js";
20
- import type { Exporters } from "./options.js";
15
+ import { ObservabilityResourceDetector } from "./resource.js";
21
16
  import { UrlSampler } from "./url-sampler.js";
22
17
 
23
18
  export default function buildNodeInstrumentation(
@@ -76,11 +71,9 @@ export default function buildNodeInstrumentation(
76
71
  );
77
72
 
78
73
  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
- }),
74
+ resourceDetectors: [
75
+ new ObservabilityResourceDetector(config.resourceAttributes),
76
+ ],
84
77
  spanProcessors: exporter.spans,
85
78
  serviceName: config.serviceName,
86
79
  metricReader: exporter.metrics,
@@ -92,21 +85,6 @@ export default function buildNodeInstrumentation(
92
85
  "@opentelemetry/instrumentation-fs": {
93
86
  enabled: config.enableFS ?? false,
94
87
  },
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
88
  }),
111
89
  ],
112
90
  });
@@ -126,8 +104,7 @@ function isUrl(url: string): boolean {
126
104
  try {
127
105
  new URL(url);
128
106
  return true;
129
- } catch (err) {
130
- console.error(err);
107
+ } catch (_) {
131
108
  return false;
132
109
  }
133
110
  }
package/lib/metrics.ts CHANGED
@@ -10,26 +10,30 @@ import {
10
10
  ObservableGauge,
11
11
  ObservableUpDownCounter,
12
12
  UpDownCounter,
13
+ Attributes,
13
14
  } from "@opentelemetry/api";
14
15
 
15
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
16
- declare const MetricsMap: {
17
- gauge: Gauge;
18
- histogram: Histogram;
19
- counter: Counter;
20
- updowncounter: UpDownCounter;
21
- "async-counter": ObservableCounter;
22
- "async-updowncounter": ObservableUpDownCounter;
23
- "async-gauge": ObservableGauge;
16
+ type MetricTypeMap<TAttributes extends Attributes> = {
17
+ counter: Counter<TAttributes>;
18
+ histogram: Histogram<TAttributes>;
19
+ gauge: Gauge<TAttributes>;
20
+ updowncounter: UpDownCounter<TAttributes>;
21
+ "async-counter": ObservableCounter<TAttributes>;
22
+ "async-updowncounter": ObservableUpDownCounter<TAttributes>;
23
+ "async-gauge": ObservableGauge<TAttributes>;
24
24
  };
25
25
 
26
- type MetricType = keyof typeof MetricsMap;
26
+ export type MetricType = keyof MetricTypeMap<Attributes>;
27
27
 
28
- const MetricsFactoryMap: {
29
- [K in MetricType]: (
28
+ const MetricsFactoryMap: Record<
29
+ MetricType,
30
+ (
30
31
  meter: Meter,
31
- ) => (name: string, options?: MetricOptions) => (typeof MetricsMap)[K];
32
- } = {
32
+ ) => (
33
+ name: string,
34
+ options?: MetricOptions,
35
+ ) => MetricTypeMap<Attributes>[MetricType]
36
+ > = {
33
37
  gauge: (meter: Meter) => meter.createGauge,
34
38
  histogram: (meter: Meter) => meter.createHistogram,
35
39
  counter: (meter: Meter) => meter.createCounter,
@@ -37,34 +41,35 @@ const MetricsFactoryMap: {
37
41
  "async-counter": (meter: Meter) => meter.createObservableCounter,
38
42
  "async-updowncounter": (meter: Meter) => meter.createObservableUpDownCounter,
39
43
  "async-gauge": (meter: Meter) => meter.createObservableGauge,
40
- };
44
+ } as const;
41
45
 
42
46
  export interface MetricsParams {
47
+ meterName: string;
43
48
  metricName: string;
44
- attributeName: string;
45
49
  options?: MetricOptions;
46
50
  }
47
51
 
48
- function getMeter({ metricName, attributeName }: MetricsParams) {
49
- let meter: Meter;
50
- if (!metricName || !attributeName) {
51
- console.error("Invaid metric configuration!");
52
- meter = createNoopMeter();
53
- } else {
54
- meter = metrics.getMeter(`custom_metric.${metricName}`);
52
+ function getMeter({ meterName }: MetricsParams) {
53
+ if (!meterName) {
54
+ console.error("Invalid metric name!");
55
+ return createNoopMeter();
55
56
  }
56
- return meter;
57
+
58
+ return metrics.getMeter(`custom_metric.${meterName}`);
57
59
  }
58
60
 
59
- export function getMetric<T extends MetricType>(
60
- type: T,
61
- p: MetricsParams,
62
- ): (typeof MetricsMap)[T] {
61
+ export function getMetric<
62
+ T extends MetricType,
63
+ TAttributes extends Attributes = Attributes,
64
+ >(type: T, p: MetricsParams): MetricTypeMap<TAttributes>[T] {
63
65
  const meter = getMeter(p);
64
66
 
65
67
  if (!MetricsFactoryMap[type]) {
66
68
  throw new Error(`Unsupported metric type: ${type}`);
67
69
  }
68
70
 
69
- return MetricsFactoryMap[type](meter).bind(meter)(p.attributeName, p.options);
71
+ return MetricsFactoryMap[type](meter).bind(meter)(
72
+ p.metricName,
73
+ p.options,
74
+ ) as MetricTypeMap<TAttributes>[T];
70
75
  }
@@ -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,29 @@
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
+ "o11y.sdk.name": packageJson.name,
23
+ "o11y.sdk.version": packageJson.version,
24
+ };
25
+ }
26
+
27
+ return { attributes };
28
+ }
29
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.7",
3
+ "version": "0.1.0-beta.9",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -20,27 +20,31 @@
20
20
  "license": "ISC",
21
21
  "dependencies": {
22
22
  "@opentelemetry/api": "^1.9.0",
23
- "@opentelemetry/auto-instrumentations-node": "^0.56.0",
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"
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"
37
38
  },
38
39
  "devDependencies": {
39
- "@types/node": "^22.13.4",
40
- "@vitest/coverage-v8": "^3.0.5",
41
- "tsx": "^4.19.2",
42
- "typescript": "^5.7.3",
43
- "vitest": "^3.0.5"
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"
45
+ },
46
+ "engines": {
47
+ "node": ">=20.6.0"
44
48
  },
45
49
  "scripts": {
46
50
  "build": "rm -rf dist && tsc -p tsconfig.json",
@@ -37,6 +37,9 @@ if [[ $ERROR_CODE -eq 0 ]]; then
37
37
  docker run --detach \
38
38
  --network $NETWORK_NAME \
39
39
  --name $NODE_CONTAINER_NAME \
40
+ -e DB_DISABLED="true" \
41
+ -e SERVER_HOST="0.0.0.0" \
42
+ -e OTEL_COLLECTOR_URL="http://integrationalloy:4317" \
40
43
  --health-cmd="curl -f http://${NODE_CONTAINER_NAME}:9091/api/health > /dev/null || exit 1" \
41
44
  --health-start-period=1s \
42
45
  --health-retries=10 \
@@ -29,7 +29,7 @@ describe("MetricsFactoryMap", () => {
29
29
 
30
30
  const validMetricParams: MetricsParams = {
31
31
  metricName: "test-metric",
32
- attributeName: "test-attribute",
32
+ meterName: "test-meter",
33
33
  options: { description: "A test metric" },
34
34
  };
35
35
 
@@ -40,7 +40,7 @@ describe("MetricsFactoryMap", () => {
40
40
 
41
41
  expect(result).toBe("mocked-gauge");
42
42
  expect(mockMeter.createGauge).toHaveBeenCalledWith(
43
- validMetricParams.attributeName,
43
+ validMetricParams.metricName,
44
44
  validMetricParams.options,
45
45
  );
46
46
  });
@@ -52,7 +52,7 @@ describe("MetricsFactoryMap", () => {
52
52
 
53
53
  expect(result).toBe("mocked-histogram");
54
54
  expect(mockMeter.createHistogram).toHaveBeenCalledWith(
55
- validMetricParams.attributeName,
55
+ validMetricParams.metricName,
56
56
  validMetricParams.options,
57
57
  );
58
58
  });
@@ -64,7 +64,7 @@ describe("MetricsFactoryMap", () => {
64
64
 
65
65
  expect(result).toBe("mocked-counter");
66
66
  expect(mockMeter.createCounter).toHaveBeenCalledWith(
67
- validMetricParams.attributeName,
67
+ validMetricParams.metricName,
68
68
  validMetricParams.options,
69
69
  );
70
70
  });
@@ -76,7 +76,7 @@ describe("MetricsFactoryMap", () => {
76
76
 
77
77
  expect(result).toBe("mocked-updowncounter");
78
78
  expect(mockMeter.createUpDownCounter).toHaveBeenCalledWith(
79
- validMetricParams.attributeName,
79
+ validMetricParams.metricName,
80
80
  validMetricParams.options,
81
81
  );
82
82
  });
@@ -88,7 +88,7 @@ describe("MetricsFactoryMap", () => {
88
88
 
89
89
  expect(result).toBe("mocked-async-counter");
90
90
  expect(mockMeter.createObservableCounter).toHaveBeenCalledWith(
91
- validMetricParams.attributeName,
91
+ validMetricParams.metricName,
92
92
  validMetricParams.options,
93
93
  );
94
94
  });
@@ -102,7 +102,7 @@ describe("MetricsFactoryMap", () => {
102
102
 
103
103
  expect(result).toBe("mocked-async-updowncounter");
104
104
  expect(mockMeter.createObservableUpDownCounter).toHaveBeenCalledWith(
105
- validMetricParams.attributeName,
105
+ validMetricParams.metricName,
106
106
  validMetricParams.options,
107
107
  );
108
108
  });
@@ -114,7 +114,7 @@ describe("MetricsFactoryMap", () => {
114
114
 
115
115
  expect(result).toBe("mocked-async-gauge");
116
116
  expect(mockMeter.createObservableGauge).toHaveBeenCalledWith(
117
- validMetricParams.attributeName,
117
+ validMetricParams.metricName,
118
118
  validMetricParams.options,
119
119
  );
120
120
  });
@@ -131,7 +131,7 @@ describe("MetricsFactoryMap", () => {
131
131
  test("should return noop metric fallback for null config", async () => {
132
132
  const nullMetricParams: MetricsParams = {
133
133
  metricName: null!,
134
- attributeName: "",
134
+ meterName: "",
135
135
  };
136
136
 
137
137
  const result = getMetric("async-gauge", nullMetricParams);
@@ -44,8 +44,8 @@ describe("verify config settings", () => {
44
44
 
45
45
  const logs = _configuration.logRecordProcessors;
46
46
 
47
- assert.equal(logs.length, 1);
48
- assert.ok(logs[0] instanceof BatchLogRecordProcessor);
47
+ assert.equal(logs.length, 2);
48
+ assert.ok(logs[1] instanceof BatchLogRecordProcessor);
49
49
 
50
50
  const spans = _configuration.spanProcessors;
51
51
 
@@ -74,8 +74,8 @@ describe("verify config settings", () => {
74
74
 
75
75
  const logs = _configuration.logRecordProcessors;
76
76
 
77
- assert.equal(logs.length, 1);
78
- assert.ok(logs[0] instanceof BatchLogRecordProcessor);
77
+ assert.equal(logs.length, 2);
78
+ assert.ok(logs[1] instanceof BatchLogRecordProcessor);
79
79
 
80
80
  const spans = _configuration.spanProcessors;
81
81
 
@@ -105,8 +105,8 @@ describe("verify config settings", () => {
105
105
  const logs = _configuration.logRecordProcessors;
106
106
 
107
107
  // verify simple log processor for instant console logging
108
- assert.equal(logs.length, 1);
109
- assert.ok(logs[0] instanceof SimpleLogRecordProcessor);
108
+ assert.equal(logs.length, 2);
109
+ assert.ok(logs[1] instanceof SimpleLogRecordProcessor);
110
110
 
111
111
  const spans = _configuration.spanProcessors;
112
112
 
@@ -136,8 +136,8 @@ describe("verify config settings", () => {
136
136
  const _configuration = sdk["_configuration"];
137
137
 
138
138
  const logRecordProcessors = _configuration.logRecordProcessors;
139
- assert.equal(logRecordProcessors.length, 1);
140
- assert.ok(logRecordProcessors[0] instanceof SimpleLogRecordProcessor);
139
+ assert.equal(logRecordProcessors.length, 2);
140
+ assert.ok(logRecordProcessors[1] instanceof SimpleLogRecordProcessor);
141
141
  });
142
142
 
143
143
  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,22 @@
1
+ import { describe, test, expect, vi } 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
+ });
package/vitest.config.ts CHANGED
@@ -25,7 +25,7 @@ export default defineConfig({
25
25
  workspace: [
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