@ogcio/o11y-sdk-node 0.1.0-beta.2 → 0.1.0-beta.3

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 (51) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +96 -10
  3. package/index.ts +2 -0
  4. package/lib/console.ts +15 -0
  5. package/lib/grpc.ts +4 -4
  6. package/lib/http.ts +4 -4
  7. package/lib/index.ts +2 -2
  8. package/lib/instrumentation.node.ts +9 -1
  9. package/lib/metrics.ts +70 -0
  10. package/lib/options.ts +2 -3
  11. package/package.json +22 -22
  12. package/test/metrics.test.ts +142 -0
  13. package/test/node-config.test.ts +35 -9
  14. package/test/validation.test.ts +31 -0
  15. package/tsconfig.json +2 -1
  16. package/coverage/cobertura-coverage.xml +0 -199
  17. package/coverage/lcov-report/base.css +0 -224
  18. package/coverage/lcov-report/block-navigation.js +0 -87
  19. package/coverage/lcov-report/favicon.png +0 -0
  20. package/coverage/lcov-report/index.html +0 -131
  21. package/coverage/lcov-report/prettify.css +0 -1
  22. package/coverage/lcov-report/prettify.js +0 -2
  23. package/coverage/lcov-report/sdk-node/index.html +0 -116
  24. package/coverage/lcov-report/sdk-node/index.ts.html +0 -106
  25. package/coverage/lcov-report/sdk-node/lib/grpc.ts.html +0 -178
  26. package/coverage/lcov-report/sdk-node/lib/http.ts.html +0 -190
  27. package/coverage/lcov-report/sdk-node/lib/index.html +0 -191
  28. package/coverage/lcov-report/sdk-node/lib/index.ts.html +0 -265
  29. package/coverage/lcov-report/sdk-node/lib/instrumentation.node.ts.html +0 -310
  30. package/coverage/lcov-report/sdk-node/lib/options.ts.html +0 -109
  31. package/coverage/lcov-report/sdk-node/lib/utils.ts.html +0 -115
  32. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  33. package/coverage/lcov-report/sorter.js +0 -196
  34. package/coverage/lcov.info +0 -206
  35. package/dist/index.d.ts +0 -6
  36. package/dist/index.js +0 -2
  37. package/dist/lib/grpc.d.ts +0 -3
  38. package/dist/lib/grpc.js +0 -26
  39. package/dist/lib/http.d.ts +0 -3
  40. package/dist/lib/http.js +0 -29
  41. package/dist/lib/index.d.ts +0 -46
  42. package/dist/lib/index.js +0 -1
  43. package/dist/lib/instrumentation.node.d.ts +0 -3
  44. package/dist/lib/instrumentation.node.js +0 -53
  45. package/dist/lib/options.d.ts +0 -7
  46. package/dist/lib/options.js +0 -1
  47. package/dist/lib/utils.d.ts +0 -3
  48. package/dist/lib/utils.js +0 -5
  49. package/dist/vitest.config.d.ts +0 -2
  50. package/dist/vitest.config.js +0 -25
  51. package/test-report.xml +0 -39
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-beta.3](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@0.1.0-beta.2...@ogcio/o11y-sdk-node@v0.1.0-beta.3) (2025-01-28)
4
+
5
+
6
+ ### Features
7
+
8
+ * **sdk-node:** custom metrics ([#53](https://github.com/ogcio/o11y/issues/53)) ([3cb40b1](https://github.com/ogcio/o11y/commit/3cb40b1add3d80615c8d123d233724094559c7ff))
9
+
3
10
  ## [0.1.0-beta.2](https://github.com/ogcio/o11y/compare/@ogcio/o11y-sdk-node@0.1.0-beta.1...@ogcio/o11y-sdk-node@0.1.0-beta.2) (2024-11-28)
4
11
 
5
12
 
package/README.md CHANGED
@@ -27,9 +27,6 @@ import("@ogcio/o11y-sdk-node/lib/index").then((sdk) =>
27
27
  sdk.instrumentNode({
28
28
  serviceName: "node-microservice",
29
29
  collectorUrl: "http://localhost:4317",
30
- collectorMode: "single",
31
- enableFS: true,
32
- diagLogLevel: "DEBUG",
33
30
  }),
34
31
  );
35
32
  ```
@@ -42,18 +39,106 @@ Or setup inside your package.json
42
39
 
43
40
  ```json
44
41
  {
45
- "main": "dist/index.js",
46
- "type": "module",
47
- "scripts": {
48
- "start": "node --env-file=.env --import ./dist/instrumentation.js dist/index.js",
49
- },
50
- ....
42
+ "main": "dist/index.js",
43
+ "type": "module",
44
+ "scripts": {
45
+ "start": "node --env-file=.env --import ./dist/instrumentation.js dist/index.js"
46
+ }
51
47
  }
52
48
  ```
53
49
 
50
+ ## Sending Custom Metrics
51
+
52
+ This package gives the possibility to send custom metrics and define them as desired in the code, you can choose between sync metrics and observable async metrics.
53
+
54
+ To use this functionality, you only need to import `getMetric` and enable the application instrumentation.
55
+
56
+ ```typescript
57
+ import { getMetric } from "@ogcio/o11y-sdk-node";
58
+ ```
59
+
60
+ ### Sync
61
+
62
+ Sync metrics are signals sent when the function has been called.
63
+
64
+ Creating a counter, there are 2 types of counter:
65
+
66
+ - **counter** a simple counter that can only add positive numbers
67
+ - **updowncounter** counter that support also negative numbers
68
+
69
+ ```typescript
70
+ const counter = getMetric("counter", {
71
+ attributeName: "counter",
72
+ metricName: "fastify-counter",
73
+ });
74
+
75
+ counter.add(1, {
76
+ my: "my",
77
+ custom: "custom",
78
+ attributes: "attributes",
79
+ });
80
+ ```
81
+
82
+ Creating a Histogram
83
+
84
+ ```typescript
85
+ const histogram = getMetric("histogram", {
86
+ metricName: "response_duration",
87
+ attributeName: "http_response",
88
+ options: {
89
+ advice: {
90
+ explicitBucketBoundaries: [0, 100, 200, 500, 1000],
91
+ },
92
+ description: "Response durations",
93
+ },
94
+ });
95
+
96
+ histogram.record(120, { path: "/home" });
97
+ ```
98
+
99
+ ### Async
100
+
101
+ Async metrics are called by the scraper collector to read current data using the `Observable` pattern.
102
+ Creating an async metric means that the application will subscribe to the observability URL and record data on call (default 60s).
103
+
104
+ _keep in mind, you can't send signals on-demand with this component_
105
+
106
+ Creating an async Gauge
107
+
108
+ ```typescript
109
+ const asyncGauge = getMetric("async-gauge", {
110
+ metricName: "cpu_usage",
111
+ attributeName: "server_load",
112
+ options: { unit: "percentage" },
113
+ }).addCallback((observer) => {
114
+ observer.observe(50, { host: "server1" });
115
+ });
116
+ ```
117
+
118
+ Creating an async Counter
119
+
120
+ ```typescript
121
+ getMetric("async-counter", {
122
+ attributeName: "scraped-memory",
123
+ metricName: "fastify-counter",
124
+ }).addCallback((observer) => {
125
+ observer.observe(freemem(), {
126
+ "application.os.memory": "free-memory",
127
+ });
128
+ });
129
+ ```
130
+
54
131
  ## API Reference
55
132
 
56
- ### Shared Types
133
+ #### Protocol
134
+
135
+ protocol is a string parameter used to define how to send signals to observability infrastructure
136
+
137
+ - **grpc** Use gRPC protocol, usually default port use 4317. Is the most performant option for server side applications.
138
+ - **http** Use HTTP standard protocol, usually default port use 4318. Mainly used on web or UI client applications.
139
+ - **console** Used for debugging sending signals to observability cluster, every information will be printed to your runtime console.
140
+
141
+ #### Shared Types
57
142
 
58
143
  ```ts
59
144
  export type SDKLogLevel =
@@ -75,3 +160,4 @@ export type SDKLogLevel =
75
160
  | `diagLogLevel` | `SDKLogLevel` | Diagnostic log level for the internal runtime instrumentation |
76
161
  | `collectorMode` | `single \| batch` | Signals sending mode, default is batch for performance |
77
162
  | `enableFS` | `boolean` | Flag to enable or disable the tracing for node:fs module |
163
+ | `protocol` | `string` | Type of the protocol used to send signals |
package/index.ts CHANGED
@@ -5,3 +5,5 @@ import buildNodeInstrumentation from "./lib/instrumentation.node.js";
5
5
  export type * from "./lib/index.js";
6
6
  export type { NodeSDKConfig, NodeSDK };
7
7
  export { buildNodeInstrumentation as instrumentNode };
8
+
9
+ export * from "./lib/metrics.js";
package/lib/console.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { NodeSDKConfig } from "./index.js";
2
+ import type { Exporters } from "./options.js";
3
+ import { logs, metrics, tracing } from "@opentelemetry/sdk-node";
4
+
5
+ export default function buildConsoleExporters(_: NodeSDKConfig): Exporters {
6
+ return {
7
+ traces: new tracing.ConsoleSpanExporter(),
8
+ metrics: new metrics.PeriodicExportingMetricReader({
9
+ exporter: new metrics.ConsoleMetricExporter(),
10
+ }),
11
+ logs: [
12
+ new logs.SimpleLogRecordProcessor(new logs.ConsoleLogRecordExporter()),
13
+ ],
14
+ };
15
+ }
package/lib/grpc.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import type { NodeSDKConfig } from "./index.js";
2
2
  import type { Exporters } from "./options.js";
3
3
  import { LogRecordProcessorMap } from "./utils.js";
4
+ import { metrics } from "@opentelemetry/sdk-node";
4
5
  import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base";
5
- import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
6
- import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
7
- import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
8
6
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
7
+ import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
8
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
9
9
 
10
10
  export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
11
11
  return {
@@ -13,7 +13,7 @@ export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
13
13
  url: `${config.collectorUrl}`,
14
14
  compression: CompressionAlgorithm.GZIP,
15
15
  }),
16
- metrics: new PeriodicExportingMetricReader({
16
+ metrics: new metrics.PeriodicExportingMetricReader({
17
17
  exporter: new OTLPMetricExporter({
18
18
  url: `${config.collectorUrl}`,
19
19
  compression: CompressionAlgorithm.GZIP,
package/lib/http.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
2
1
  import type { NodeSDKConfig } from "./index.js";
3
2
  import type { Exporters } from "./options.js";
4
3
  import { LogRecordProcessorMap } from "./utils.js";
4
+ import { metrics } from "@opentelemetry/sdk-node";
5
5
  import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base";
6
- import { PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
7
- import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
6
+ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
8
7
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
8
+ import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
9
9
 
10
10
  export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
11
11
  if (config.collectorUrl.endsWith("/")) {
@@ -17,7 +17,7 @@ export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
17
17
  url: `${config.collectorUrl}/v1/traces`,
18
18
  compression: CompressionAlgorithm.GZIP,
19
19
  }),
20
- metrics: new PeriodicExportingMetricReader({
20
+ metrics: new metrics.PeriodicExportingMetricReader({
21
21
  exporter: new OTLPMetricExporter({
22
22
  url: `${config.collectorUrl}/v1/metrics`,
23
23
  compression: CompressionAlgorithm.GZIP,
package/lib/index.ts CHANGED
@@ -36,7 +36,7 @@ export interface NodeSDKConfig extends SDKConfig {
36
36
  enableFS?: boolean;
37
37
 
38
38
  /**
39
- * http based connection protocol used to send signals.
39
+ * protocol used to send signals.
40
40
  *
41
41
  * @default grpc
42
42
  */
@@ -45,7 +45,7 @@ export interface NodeSDKConfig extends SDKConfig {
45
45
 
46
46
  export type SDKCollectorMode = "single" | "batch";
47
47
 
48
- export type SDKProtocol = "grpc" | "http";
48
+ export type SDKProtocol = "grpc" | "http" | "console";
49
49
 
50
50
  export type SDKLogLevel =
51
51
  | "NONE"
@@ -1,12 +1,14 @@
1
- import { NodeSDK } from "@opentelemetry/sdk-node";
1
+ import { NodeSDK, resources } from "@opentelemetry/sdk-node";
2
2
  import type { NodeSDKConfig } from "./index.js";
3
3
  import type { Exporters } from "./options.js";
4
4
  import isUrl from "is-url";
5
5
  import buildHttpExporters from "./http.js";
6
6
  import buildGrpcExporters from "./grpc.js";
7
+ import buildConsoleExporters from "./console.js";
7
8
  import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
8
9
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
9
10
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
11
+ import packageJson from "../package.json" with { type: "json" };
10
12
 
11
13
  export default function buildNodeInstrumentation(
12
14
  config?: NodeSDKConfig,
@@ -36,6 +38,8 @@ export default function buildNodeInstrumentation(
36
38
 
37
39
  if (config.protocol === "http") {
38
40
  exporter = buildHttpExporters(config);
41
+ } else if (config.protocol === "console") {
42
+ exporter = buildConsoleExporters(config);
39
43
  } else {
40
44
  exporter = buildGrpcExporters(config);
41
45
  }
@@ -49,6 +53,10 @@ export default function buildNodeInstrumentation(
49
53
  );
50
54
 
51
55
  const sdk = new NodeSDK({
56
+ resource: new resources.Resource({
57
+ "o11y.sdk.name": packageJson.name,
58
+ "o11y.sdk.version": packageJson.version,
59
+ }),
52
60
  serviceName: config.serviceName,
53
61
  traceExporter: exporter.traces,
54
62
  metricReader: exporter.metrics,
package/lib/metrics.ts ADDED
@@ -0,0 +1,70 @@
1
+ import {
2
+ Counter,
3
+ createNoopMeter,
4
+ Gauge,
5
+ Histogram,
6
+ Meter,
7
+ MetricOptions,
8
+ metrics,
9
+ ObservableCounter,
10
+ ObservableGauge,
11
+ ObservableUpDownCounter,
12
+ UpDownCounter,
13
+ } from "@opentelemetry/api";
14
+
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;
24
+ };
25
+
26
+ type MetricType = keyof typeof MetricsMap;
27
+
28
+ const MetricsFactoryMap: {
29
+ [K in MetricType]: (
30
+ meter: Meter,
31
+ ) => (name: string, options?: MetricOptions) => (typeof MetricsMap)[K];
32
+ } = {
33
+ gauge: (meter: Meter) => meter.createGauge,
34
+ histogram: (meter: Meter) => meter.createHistogram,
35
+ counter: (meter: Meter) => meter.createCounter,
36
+ updowncounter: (meter: Meter) => meter.createUpDownCounter,
37
+ "async-counter": (meter: Meter) => meter.createObservableCounter,
38
+ "async-updowncounter": (meter: Meter) => meter.createObservableUpDownCounter,
39
+ "async-gauge": (meter: Meter) => meter.createObservableGauge,
40
+ };
41
+
42
+ export interface MetricsParams {
43
+ metricName: string;
44
+ attributeName: string;
45
+ options?: MetricOptions;
46
+ }
47
+
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}`);
55
+ }
56
+ return meter;
57
+ }
58
+
59
+ export function getMetric<T extends MetricType>(
60
+ type: T,
61
+ p: MetricsParams,
62
+ ): (typeof MetricsMap)[T] {
63
+ const meter = getMeter(p);
64
+
65
+ if (!MetricsFactoryMap[type]) {
66
+ throw new Error(`Unsupported metric type: ${type}`);
67
+ }
68
+
69
+ return MetricsFactoryMap[type](meter).bind(meter)(p.attributeName, p.options);
70
+ }
package/lib/options.ts CHANGED
@@ -1,8 +1,7 @@
1
- import type { MetricReader } from "@opentelemetry/sdk-metrics";
2
- import type { logs, tracing } from "@opentelemetry/sdk-node";
1
+ import type { logs, tracing, metrics } from "@opentelemetry/sdk-node";
3
2
 
4
3
  export type Exporters = {
5
4
  traces: tracing.SpanExporter;
6
- metrics: MetricReader;
5
+ metrics: metrics.MetricReader;
7
6
  logs: logs.LogRecordProcessor[];
8
7
  };
package/package.json CHANGED
@@ -1,13 +1,9 @@
1
1
  {
2
2
  "name": "@ogcio/o11y-sdk-node",
3
- "version": "0.1.0-beta.2",
3
+ "version": "0.1.0-beta.3",
4
4
  "description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
- "scripts": {
8
- "build": "rm -rf dist && tsc -p tsconfig.json",
9
- "test": "vitest"
10
- },
11
7
  "exports": {
12
8
  ".": "./dist/index.js",
13
9
  "./*": "./dist/*.js"
@@ -24,26 +20,30 @@
24
20
  "license": "ISC",
25
21
  "dependencies": {
26
22
  "@opentelemetry/api": "^1.9.0",
27
- "@opentelemetry/auto-instrumentations-node": "^0.53.0",
28
- "@opentelemetry/core": "1.28.0",
29
- "@opentelemetry/exporter-logs-otlp-grpc": "^0.55.0",
30
- "@opentelemetry/exporter-logs-otlp-http": "^0.55.0",
31
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0.55.0",
32
- "@opentelemetry/exporter-metrics-otlp-http": "^0.55.0",
33
- "@opentelemetry/exporter-trace-otlp-grpc": "^0.55.0",
34
- "@opentelemetry/exporter-trace-otlp-http": "^0.55.0",
35
- "@opentelemetry/instrumentation": "^0.55.0",
36
- "@opentelemetry/otlp-exporter-base": "^0.55.0",
37
- "@opentelemetry/sdk-metrics": "^1.28.0",
38
- "@opentelemetry/sdk-node": "^0.55.0",
23
+ "@opentelemetry/auto-instrumentations-node": "^0.55.3",
24
+ "@opentelemetry/core": "1.30.1",
25
+ "@opentelemetry/exporter-logs-otlp-grpc": "^0.57.1",
26
+ "@opentelemetry/exporter-logs-otlp-http": "^0.57.1",
27
+ "@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.1",
28
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.57.1",
29
+ "@opentelemetry/exporter-trace-otlp-grpc": "^0.57.1",
30
+ "@opentelemetry/exporter-trace-otlp-http": "^0.57.1",
31
+ "@opentelemetry/instrumentation": "^0.57.1",
32
+ "@opentelemetry/otlp-exporter-base": "^0.57.1",
33
+ "@opentelemetry/sdk-metrics": "^1.30.1",
34
+ "@opentelemetry/sdk-node": "^0.57.1",
39
35
  "is-url": "^1.2.4"
40
36
  },
41
37
  "devDependencies": {
42
38
  "@types/is-url": "^1.2.32",
43
- "@types/node": "^22.10.0",
44
- "@vitest/coverage-v8": "^2.1.6",
39
+ "@types/node": "^22.12.0",
40
+ "@vitest/coverage-v8": "^3.0.4",
45
41
  "tsx": "^4.19.2",
46
- "typescript": "^5.7.2",
47
- "vitest": "^2.1.6"
42
+ "typescript": "^5.7.3",
43
+ "vitest": "^3.0.4"
44
+ },
45
+ "scripts": {
46
+ "build": "rm -rf dist && tsc -p tsconfig.json",
47
+ "test": "vitest"
48
48
  }
49
- }
49
+ }
@@ -0,0 +1,142 @@
1
+ import { describe, test, expect, vi, beforeEach, assert } from "vitest";
2
+ import { getMetric, MetricsParams } from "../lib/metrics";
3
+
4
+ const mockMeter = {
5
+ createGauge: vi.fn(),
6
+ createHistogram: vi.fn(),
7
+ createCounter: vi.fn(),
8
+ createUpDownCounter: vi.fn(),
9
+ createObservableCounter: vi.fn(),
10
+ createObservableUpDownCounter: vi.fn(),
11
+ createObservableGauge: vi.fn(),
12
+ };
13
+
14
+ vi.mock("@opentelemetry/api", async () => {
15
+ const { createNoopMeter } = await import("@opentelemetry/api");
16
+
17
+ return {
18
+ metrics: {
19
+ getMeter: vi.fn(() => mockMeter),
20
+ },
21
+ createNoopMeter: createNoopMeter,
22
+ };
23
+ });
24
+
25
+ describe("MetricsFactoryMap", () => {
26
+ beforeEach(() => {
27
+ vi.clearAllMocks();
28
+ });
29
+
30
+ const validMetricParams: MetricsParams = {
31
+ metricName: "test-metric",
32
+ attributeName: "test-attribute",
33
+ options: { description: "A test metric" },
34
+ };
35
+
36
+ test("should call createGauge when type is 'gauge'", () => {
37
+ mockMeter.createGauge.mockReturnValue("mocked-gauge");
38
+
39
+ const result = getMetric("gauge", validMetricParams);
40
+
41
+ expect(result).toBe("mocked-gauge");
42
+ expect(mockMeter.createGauge).toHaveBeenCalledWith(
43
+ validMetricParams.attributeName,
44
+ validMetricParams.options,
45
+ );
46
+ });
47
+
48
+ test("should call createHistogram when type is 'histogram'", () => {
49
+ mockMeter.createHistogram.mockReturnValue("mocked-histogram");
50
+
51
+ const result = getMetric("histogram", validMetricParams);
52
+
53
+ expect(result).toBe("mocked-histogram");
54
+ expect(mockMeter.createHistogram).toHaveBeenCalledWith(
55
+ validMetricParams.attributeName,
56
+ validMetricParams.options,
57
+ );
58
+ });
59
+
60
+ test("should call createCounter when type is 'counter'", () => {
61
+ mockMeter.createCounter.mockReturnValue("mocked-counter");
62
+
63
+ const result = getMetric("counter", validMetricParams);
64
+
65
+ expect(result).toBe("mocked-counter");
66
+ expect(mockMeter.createCounter).toHaveBeenCalledWith(
67
+ validMetricParams.attributeName,
68
+ validMetricParams.options,
69
+ );
70
+ });
71
+
72
+ test("should call createUpDownCounter when type is 'updowncounter'", () => {
73
+ mockMeter.createUpDownCounter.mockReturnValue("mocked-updowncounter");
74
+
75
+ const result = getMetric("updowncounter", validMetricParams);
76
+
77
+ expect(result).toBe("mocked-updowncounter");
78
+ expect(mockMeter.createUpDownCounter).toHaveBeenCalledWith(
79
+ validMetricParams.attributeName,
80
+ validMetricParams.options,
81
+ );
82
+ });
83
+
84
+ test("should call createObservableCounter when type is 'async-counter'", () => {
85
+ mockMeter.createObservableCounter.mockReturnValue("mocked-async-counter");
86
+
87
+ const result = getMetric("async-counter", validMetricParams);
88
+
89
+ expect(result).toBe("mocked-async-counter");
90
+ expect(mockMeter.createObservableCounter).toHaveBeenCalledWith(
91
+ validMetricParams.attributeName,
92
+ validMetricParams.options,
93
+ );
94
+ });
95
+
96
+ test("should call createObservableUpDownCounter when type is 'async-updowncounter'", () => {
97
+ mockMeter.createObservableUpDownCounter.mockReturnValue(
98
+ "mocked-async-updowncounter",
99
+ );
100
+
101
+ const result = getMetric("async-updowncounter", validMetricParams);
102
+
103
+ expect(result).toBe("mocked-async-updowncounter");
104
+ expect(mockMeter.createObservableUpDownCounter).toHaveBeenCalledWith(
105
+ validMetricParams.attributeName,
106
+ validMetricParams.options,
107
+ );
108
+ });
109
+
110
+ test("should call createObservableGauge when type is 'async-gauge'", () => {
111
+ mockMeter.createObservableGauge.mockReturnValue("mocked-async-gauge");
112
+
113
+ const result = getMetric("async-gauge", validMetricParams);
114
+
115
+ expect(result).toBe("mocked-async-gauge");
116
+ expect(mockMeter.createObservableGauge).toHaveBeenCalledWith(
117
+ validMetricParams.attributeName,
118
+ validMetricParams.options,
119
+ );
120
+ });
121
+
122
+ test("should throw an error for unsupported metric types", () => {
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ const invalidMetricType = "invalid-type" as any;
125
+
126
+ expect(() => getMetric(invalidMetricType, validMetricParams)).toThrow(
127
+ `Unsupported metric type: ${invalidMetricType}`,
128
+ );
129
+ });
130
+
131
+ test("should return noop metric fallback for null config", async () => {
132
+ const nullMetricParams: MetricsParams = {
133
+ metricName: null!,
134
+ attributeName: "",
135
+ };
136
+
137
+ const result = getMetric("async-gauge", nullMetricParams);
138
+
139
+ assert.isNotNull(result);
140
+ assert.equal(result.constructor.name, "NoopObservableGaugeMetric");
141
+ });
142
+ });
@@ -1,6 +1,6 @@
1
1
  import { test, describe, assert, expect } from "vitest";
2
2
  import buildNodeInstrumentation from "../lib/instrumentation.node.js";
3
- import { NodeSDK, logs } from "@opentelemetry/sdk-node";
3
+ import { NodeSDK, logs, metrics, tracing } from "@opentelemetry/sdk-node";
4
4
  import { OTLPTraceExporter as GRPC_OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
5
5
  import { OTLPMetricExporter as GRPC_OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
6
6
  import { OTLPLogExporter as GRPC_OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
@@ -32,10 +32,7 @@ describe("verify config settings", () => {
32
32
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
33
33
 
34
34
  const traceExporter = _configuration.traceExporter;
35
- assert.equal(
36
- traceExporter["_transport"]["_parameters"]["compression"],
37
- "gzip",
38
- );
35
+
39
36
  assert.ok(traceExporter instanceof GRPC_OTLPTraceExporter);
40
37
 
41
38
  const logRecordProcessors = _configuration.logRecordProcessors;
@@ -64,10 +61,6 @@ describe("verify config settings", () => {
64
61
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
65
62
 
66
63
  const traceExporter = _configuration.traceExporter;
67
- assert.equal(
68
- traceExporter._transport._transport._parameters.compression,
69
- "gzip",
70
- );
71
64
  assert.ok(traceExporter instanceof HTTP_OTLPTraceExporter);
72
65
 
73
66
  const logRecordProcessors = _configuration.logRecordProcessors;
@@ -82,6 +75,39 @@ describe("verify config settings", () => {
82
75
  assert.ok(metricReader._exporter instanceof HTTP_OTLPMetricExporter);
83
76
  });
84
77
 
78
+ test("console - console config", () => {
79
+ const config: NodeSDKConfig = {
80
+ ...commonConfig,
81
+ protocol: "console",
82
+ diagLogLevel: "NONE",
83
+ };
84
+
85
+ const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
86
+ assert.ok(sdk);
87
+
88
+ const _configuration = sdk["_configuration"];
89
+ assert.equal(_configuration.serviceName, commonConfig.serviceName);
90
+
91
+ const traceExporter = _configuration.traceExporter;
92
+
93
+ assert.ok(traceExporter instanceof tracing.ConsoleSpanExporter);
94
+ assert.isUndefined(traceExporter._transport);
95
+
96
+ const logRecordProcessors = _configuration.logRecordProcessors;
97
+ assert.equal(logRecordProcessors.length, 1);
98
+
99
+ assert.ok(logRecordProcessors[0] instanceof logs.SimpleLogRecordProcessor);
100
+ assert.ok(
101
+ logRecordProcessors[0]["_exporter"] instanceof
102
+ logs.ConsoleLogRecordExporter,
103
+ );
104
+ assert.isUndefined(logRecordProcessors[0]["_exporter"]._transport);
105
+
106
+ const metricReader = _configuration.metricReader;
107
+ assert.ok(metricReader._exporter instanceof metrics.ConsoleMetricExporter);
108
+ assert.isUndefined(metricReader._exporter._transport);
109
+ });
110
+
85
111
  test("single log sending config", () => {
86
112
  const config: NodeSDKConfig = {
87
113
  ...commonConfig,
@@ -63,4 +63,35 @@ describe("validation config: should return without breaking the execution", () =
63
63
  );
64
64
  expect(consoleLogSpy).not.toHaveBeenCalled();
65
65
  });
66
+
67
+ test("node instrumentation: verify no instrumentation if exception occurs", () => {
68
+ let sdk: NodeSDK | undefined = undefined;
69
+
70
+ const consoleErrorSpy = vi
71
+ .spyOn(console, "error")
72
+ .mockImplementation(vi.fn());
73
+ const consoleLogSpy = vi.spyOn(console, "log").mockImplementation(vi.fn());
74
+
75
+ vi.mock("@opentelemetry/auto-instrumentations-node", () => ({
76
+ getNodeAutoInstrumentations: vi.fn(() => {
77
+ throw new Error("Wanted error");
78
+ }),
79
+ }));
80
+
81
+ assert.doesNotThrow(() => {
82
+ sdk = buildNodeInstrumentation({
83
+ collectorUrl: "https://testurl.com",
84
+ serviceName: "test",
85
+ });
86
+ });
87
+
88
+ assert.equal(sdk, undefined);
89
+
90
+ expect(consoleErrorSpy).toHaveBeenCalled();
91
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
92
+ "Error starting NodeJS OpenTelemetry instrumentation:",
93
+ new Error("Wanted error"),
94
+ );
95
+ expect(consoleLogSpy).not.toHaveBeenCalled();
96
+ });
66
97
  });
package/tsconfig.json CHANGED
@@ -8,7 +8,8 @@
8
8
  "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
9
9
  "strict": true /* Enable all strict type-checking options. */,
10
10
  "skipLibCheck": true /* Skip type checking all .d.ts files. */,
11
- "declaration": true
11
+ "declaration": true,
12
+ "resolveJsonModule": true
12
13
  },
13
14
  "exclude": ["**/test/**"]
14
15
  }