@ogcio/o11y-sdk-node 0.1.0-beta.6 → 0.1.0-beta.8

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 +19 -0
  2. package/README.md +59 -0
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +1 -0
  5. package/dist/lib/exporter/console.d.ts +3 -0
  6. package/dist/lib/exporter/console.js +16 -0
  7. package/dist/lib/exporter/grpc.d.ts +3 -0
  8. package/dist/lib/{grpc.js → exporter/grpc.js} +9 -5
  9. package/dist/lib/exporter/http.d.ts +3 -0
  10. package/dist/lib/{http.js → exporter/http.js} +9 -5
  11. package/dist/lib/index.d.ts +17 -5
  12. package/dist/lib/instrumentation.node.js +27 -9
  13. package/dist/lib/metrics.d.ts +12 -12
  14. package/dist/lib/metrics.js +6 -10
  15. package/dist/lib/options.d.ts +5 -3
  16. package/dist/lib/processor/enrich-span-processor.d.ts +11 -0
  17. package/dist/lib/processor/enrich-span-processor.js +22 -0
  18. package/dist/lib/traces.d.ts +1 -0
  19. package/dist/lib/traces.js +4 -0
  20. package/dist/lib/url-sampler.d.ts +3 -2
  21. package/dist/lib/url-sampler.js +5 -8
  22. package/dist/lib/utils.d.ts +4 -2
  23. package/dist/lib/utils.js +8 -3
  24. package/dist/package.json +21 -17
  25. package/dist/vitest.config.js +15 -1
  26. package/index.ts +2 -2
  27. package/lib/exporter/console.ts +27 -0
  28. package/lib/{grpc.ts → exporter/grpc.ts} +13 -7
  29. package/lib/{http.ts → exporter/http.ts} +13 -7
  30. package/lib/index.ts +24 -4
  31. package/lib/instrumentation.node.ts +49 -11
  32. package/lib/metrics.ts +34 -29
  33. package/lib/options.ts +5 -3
  34. package/lib/processor/enrich-span-processor.ts +39 -0
  35. package/lib/traces.ts +5 -0
  36. package/lib/url-sampler.ts +13 -14
  37. package/lib/utils.ts +16 -4
  38. package/package.json +21 -17
  39. package/test/enrich-span-processor.test.ts +105 -0
  40. package/test/index.test.ts +9 -0
  41. package/test/integration/integration.test.ts +21 -0
  42. package/test/integration/run.sh +5 -2
  43. package/test/metrics.test.ts +9 -9
  44. package/test/node-config.test.ts +49 -36
  45. package/test/url-sampler.test.ts +215 -0
  46. package/vitest.config.ts +15 -1
  47. package/dist/lib/console.d.ts +0 -3
  48. package/dist/lib/console.js +0 -12
  49. package/dist/lib/grpc.d.ts +0 -3
  50. package/dist/lib/http.d.ts +0 -3
  51. package/lib/console.ts +0 -15
@@ -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 \
@@ -60,9 +63,9 @@ fi
60
63
 
61
64
  if [[ $ERROR_CODE -eq 0 ]]; then
62
65
  sleep 2
63
- curl -X GET -f http://localhost:9091/api/dummy
66
+ curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
64
67
  sleep 2
65
- curl -X GET -f http://localhost:9091/api/dummy
68
+ curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
66
69
  fi
67
70
 
68
71
  # sleep N seconds to await instrumentation flow send and receiving signals
@@ -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);
@@ -9,7 +9,17 @@ import { OTLPTraceExporter as HTTP_OTLPTraceExporter } from "@opentelemetry/expo
9
9
  import { OTLPMetricExporter as HTTP_OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
10
10
  import { OTLPLogExporter as HTTP_OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
11
11
  import { NodeSDKConfig } from "../lib/index.js";
12
- import buildHttpExporters from "../lib/http.js";
12
+ import buildHttpExporters from "../lib/exporter/http.js";
13
+ import {
14
+ BatchSpanProcessor,
15
+ ConsoleSpanExporter,
16
+ SimpleSpanProcessor,
17
+ } from "@opentelemetry/sdk-trace-base";
18
+ import {
19
+ BatchLogRecordProcessor,
20
+ SimpleLogRecordProcessor,
21
+ } from "@opentelemetry/sdk-logs";
22
+ import { EnrichSpanProcessor } from "../lib/processor/enrich-span-processor.js";
13
23
 
14
24
  describe("verify config settings", () => {
15
25
  const commonConfig = {
@@ -21,6 +31,7 @@ describe("verify config settings", () => {
21
31
  const config: NodeSDKConfig = {
22
32
  ...commonConfig,
23
33
  protocol: "grpc",
34
+ collectorMode: "batch",
24
35
  diagLogLevel: "NONE",
25
36
  };
26
37
 
@@ -31,20 +42,21 @@ describe("verify config settings", () => {
31
42
  const _configuration = sdk["_configuration"];
32
43
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
33
44
 
34
- const traceExporter = _configuration.traceExporter;
45
+ const logs = _configuration.logRecordProcessors;
35
46
 
36
- assert.ok(traceExporter instanceof GRPC_OTLPTraceExporter);
47
+ assert.equal(logs.length, 1);
48
+ assert.ok(logs[0] instanceof BatchLogRecordProcessor);
49
+
50
+ const spans = _configuration.spanProcessors;
51
+
52
+ assert.equal(spans.length, 2);
53
+ assert.ok(spans[0] instanceof BatchSpanProcessor);
54
+ assert.ok(spans[1] instanceof EnrichSpanProcessor);
37
55
 
38
- const logRecordProcessors = _configuration.logRecordProcessors;
39
- assert.equal(logRecordProcessors.length, 1);
40
- // assert default signals sending mode
41
- assert.ok(logRecordProcessors[0] instanceof logs.BatchLogRecordProcessor);
42
56
  assert.ok(
43
- logRecordProcessors[0]["_exporter"] instanceof GRPC_OTLPLogExporter,
57
+ _configuration.metricReader instanceof
58
+ metrics.PeriodicExportingMetricReader,
44
59
  );
45
-
46
- const metricReader = _configuration.metricReader;
47
- assert.ok(metricReader._exporter instanceof GRPC_OTLPMetricExporter);
48
60
  });
49
61
 
50
62
  test("http config", () => {
@@ -60,19 +72,21 @@ describe("verify config settings", () => {
60
72
  const _configuration = sdk["_configuration"];
61
73
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
62
74
 
63
- const traceExporter = _configuration.traceExporter;
64
- assert.ok(traceExporter instanceof HTTP_OTLPTraceExporter);
75
+ const logs = _configuration.logRecordProcessors;
76
+
77
+ assert.equal(logs.length, 1);
78
+ assert.ok(logs[0] instanceof BatchLogRecordProcessor);
79
+
80
+ const spans = _configuration.spanProcessors;
81
+
82
+ assert.equal(spans.length, 2);
83
+ assert.ok(spans[0] instanceof BatchSpanProcessor);
84
+ assert.ok(spans[1] instanceof EnrichSpanProcessor);
65
85
 
66
- const logRecordProcessors = _configuration.logRecordProcessors;
67
- assert.equal(logRecordProcessors.length, 1);
68
- // assert default signals sending mode
69
- assert.ok(logRecordProcessors[0] instanceof logs.BatchLogRecordProcessor);
70
86
  assert.ok(
71
- logRecordProcessors[0]["_exporter"] instanceof HTTP_OTLPLogExporter,
87
+ _configuration.metricReader instanceof
88
+ metrics.PeriodicExportingMetricReader,
72
89
  );
73
-
74
- const metricReader = _configuration.metricReader;
75
- assert.ok(metricReader._exporter instanceof HTTP_OTLPMetricExporter);
76
90
  });
77
91
 
78
92
  test("console - console config", () => {
@@ -82,30 +96,29 @@ describe("verify config settings", () => {
82
96
  diagLogLevel: "NONE",
83
97
  };
84
98
 
85
- const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
99
+ const sdk: NodeSDK = buildNodeInstrumentation(config)!;
86
100
  assert.ok(sdk);
87
101
 
88
102
  const _configuration = sdk["_configuration"];
89
103
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
90
104
 
91
- const traceExporter = _configuration.traceExporter;
105
+ const logs = _configuration.logRecordProcessors;
92
106
 
93
- assert.ok(traceExporter instanceof tracing.ConsoleSpanExporter);
94
- assert.isUndefined(traceExporter._transport);
107
+ // verify simple log processor for instant console logging
108
+ assert.equal(logs.length, 1);
109
+ assert.ok(logs[0] instanceof SimpleLogRecordProcessor);
95
110
 
96
- const logRecordProcessors = _configuration.logRecordProcessors;
97
- assert.equal(logRecordProcessors.length, 1);
111
+ const spans = _configuration.spanProcessors;
112
+
113
+ assert.equal(spans.length, 2);
114
+ // verify simple span for instant console logging
115
+ assert.ok(spans[0] instanceof SimpleSpanProcessor);
116
+ assert.ok(spans[1] instanceof EnrichSpanProcessor);
98
117
 
99
- assert.ok(logRecordProcessors[0] instanceof logs.SimpleLogRecordProcessor);
100
118
  assert.ok(
101
- logRecordProcessors[0]["_exporter"] instanceof
102
- logs.ConsoleLogRecordExporter,
119
+ _configuration.metricReader instanceof
120
+ metrics.PeriodicExportingMetricReader,
103
121
  );
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
122
  });
110
123
 
111
124
  test("single log sending config", () => {
@@ -124,7 +137,7 @@ describe("verify config settings", () => {
124
137
 
125
138
  const logRecordProcessors = _configuration.logRecordProcessors;
126
139
  assert.equal(logRecordProcessors.length, 1);
127
- assert.ok(logRecordProcessors[0] instanceof logs.SimpleLogRecordProcessor);
140
+ assert.ok(logRecordProcessors[0] instanceof SimpleLogRecordProcessor);
128
141
  });
129
142
 
130
143
  test("check if clear base endpoint final slash", () => {
@@ -0,0 +1,215 @@
1
+ import { Context } from "@opentelemetry/api";
2
+ import {
3
+ SamplingDecision,
4
+ SamplingResult,
5
+ } from "@opentelemetry/sdk-trace-base";
6
+ import { describe, expect, test, vi } from "vitest";
7
+ import { UrlSampler } from "../lib/url-sampler";
8
+
9
+ describe("url sampler", () => {
10
+ // mock sampler to be sure every trace after UrlSamper has RECORD status
11
+ const mockSampler = {
12
+ shouldSample: vi
13
+ .fn()
14
+ .mockImplementation(
15
+ (_context, _traceId, _spanName, _spanKind, attributes, _links) => {
16
+ return {
17
+ decision: SamplingDecision.RECORD,
18
+ attributes: attributes,
19
+ } as SamplingResult;
20
+ },
21
+ ),
22
+ };
23
+
24
+ test("should add custom span attributes to trace", async () => {
25
+ const sampler: UrlSampler = new UrlSampler(
26
+ [
27
+ {
28
+ type: "endsWith",
29
+ url: "/health",
30
+ },
31
+ ],
32
+ mockSampler,
33
+ {
34
+ "signal.namespace": "unittest",
35
+ "signal.callback.result": () => "test",
36
+ },
37
+ );
38
+
39
+ expect(sampler).not.toBeNull();
40
+
41
+ const result = sampler.shouldSample(
42
+ {} as Context,
43
+ "traceId",
44
+ "span",
45
+ 0,
46
+ { "http.target": "/track" },
47
+ [],
48
+ );
49
+
50
+ expect(sampler.toString()).toBe("UrlSampler");
51
+ expect(result.decision).toBe(SamplingDecision.RECORD);
52
+ expect(result.attributes).not.toBeNull();
53
+ });
54
+
55
+ test("should not record trace about /health api", async () => {
56
+ const sampler: UrlSampler = new UrlSampler(
57
+ [
58
+ {
59
+ type: "endsWith",
60
+ url: "/health",
61
+ },
62
+ ],
63
+ mockSampler,
64
+ );
65
+
66
+ const result = sampler.shouldSample(
67
+ {} as Context,
68
+ "traceId",
69
+ "span",
70
+ 0,
71
+ { "http.target": "/health" },
72
+ [],
73
+ );
74
+
75
+ expect(sampler.toString()).toBe("UrlSampler");
76
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
77
+ });
78
+
79
+ test("should record every other trace which is not /health api", async () => {
80
+ const sampler: UrlSampler = new UrlSampler(
81
+ [
82
+ {
83
+ type: "endsWith",
84
+ url: "/health",
85
+ },
86
+ ],
87
+ mockSampler,
88
+ );
89
+
90
+ let result = sampler.shouldSample(
91
+ {} as Context,
92
+ "traceId",
93
+ "span",
94
+ 0,
95
+ { "http.target": "/test" },
96
+ [],
97
+ );
98
+
99
+ expect(result.decision).toBe(SamplingDecision.RECORD);
100
+
101
+ result = sampler.shouldSample(
102
+ {} as Context,
103
+ "traceId",
104
+ "span",
105
+ 0,
106
+ { "http.target": "/another/url" },
107
+ [],
108
+ );
109
+
110
+ expect(result.decision).toBe(SamplingDecision.RECORD);
111
+ });
112
+
113
+ test("operator 'includes', should not record every trace which include /block in url", async () => {
114
+ const sampler: UrlSampler = new UrlSampler(
115
+ [
116
+ {
117
+ type: "includes",
118
+ url: "/block",
119
+ },
120
+ ],
121
+ mockSampler,
122
+ );
123
+
124
+ expect(sampler).not.toBeNull();
125
+
126
+ const result = sampler.shouldSample(
127
+ {} as Context,
128
+ "traceId",
129
+ "span",
130
+ 0,
131
+ { "http.target": "/namespace/block/example/12" },
132
+ [],
133
+ );
134
+
135
+ expect(sampler.toString()).toBe("UrlSampler");
136
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
137
+ });
138
+
139
+ test("operator 'endsWith', should not record only trace which ends with /block in url", async () => {
140
+ const sampler: UrlSampler = new UrlSampler(
141
+ [
142
+ {
143
+ type: "endsWith",
144
+ url: "/block",
145
+ },
146
+ ],
147
+ mockSampler,
148
+ );
149
+
150
+ expect(sampler).not.toBeNull();
151
+
152
+ // expect traced with block in the URL middle
153
+ let result = sampler.shouldSample(
154
+ {} as Context,
155
+ "traceId",
156
+ "span",
157
+ 0,
158
+ { "http.target": "/namespace/block/example/12" },
159
+ [],
160
+ );
161
+
162
+ expect(sampler.toString()).toBe("UrlSampler");
163
+ expect(result.decision).toBe(SamplingDecision.RECORD);
164
+
165
+ // should stop trace with block at the end
166
+ result = sampler.shouldSample(
167
+ {} as Context,
168
+ "traceId",
169
+ "span",
170
+ 0,
171
+ { "http.target": "/namespace/example/block" },
172
+ [],
173
+ );
174
+
175
+ expect(sampler.toString()).toBe("UrlSampler");
176
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
177
+ });
178
+
179
+ test("operator 'equals', should not record trace which is equal to /block in url", async () => {
180
+ const sampler: UrlSampler = new UrlSampler(
181
+ [
182
+ {
183
+ type: "equals",
184
+ url: "/block",
185
+ },
186
+ ],
187
+ mockSampler,
188
+ );
189
+
190
+ expect(sampler).not.toBeNull();
191
+
192
+ let result = sampler.shouldSample(
193
+ {} as Context,
194
+ "traceId",
195
+ "span",
196
+ 0,
197
+ { "http.target": "/namespace/block/example/12" },
198
+ [],
199
+ );
200
+
201
+ expect(sampler.toString()).toBe("UrlSampler");
202
+ expect(result.decision).toBe(SamplingDecision.RECORD);
203
+
204
+ result = sampler.shouldSample(
205
+ {} as Context,
206
+ "traceId",
207
+ "span",
208
+ 0,
209
+ { "http.target": "/block" },
210
+ [],
211
+ );
212
+
213
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
214
+ });
215
+ });
package/vitest.config.ts CHANGED
@@ -4,7 +4,6 @@ export default defineConfig({
4
4
  test: {
5
5
  globals: true,
6
6
  watch: false,
7
- include: ["**/test/*.test.ts", "**/test/integration/*.test.ts"],
8
7
  exclude: ["**/fixtures/**", "**/dist/**"],
9
8
  poolOptions: {
10
9
  threads: {
@@ -22,5 +21,20 @@ export default defineConfig({
22
21
  },
23
22
  reporters: ["default", ["junit", { outputFile: "test-report.xml" }]],
24
23
  environment: "node",
24
+ pool: "threads",
25
+ workspace: [
26
+ {
27
+ test: {
28
+ include: ["**/test/*.test.ts"],
29
+ name: "unit",
30
+ },
31
+ },
32
+ {
33
+ test: {
34
+ include: ["**/test/integration/*.test.ts"],
35
+ name: "integration",
36
+ },
37
+ },
38
+ ],
25
39
  },
26
40
  });
@@ -1,3 +0,0 @@
1
- import type { NodeSDKConfig } from "./index.js";
2
- import type { Exporters } from "./options.js";
3
- export default function buildConsoleExporters(_: NodeSDKConfig): Exporters;
@@ -1,12 +0,0 @@
1
- import { logs, metrics, tracing } from "@opentelemetry/sdk-node";
2
- export default function buildConsoleExporters(_) {
3
- return {
4
- traces: new tracing.ConsoleSpanExporter(),
5
- metrics: new metrics.PeriodicExportingMetricReader({
6
- exporter: new metrics.ConsoleMetricExporter(),
7
- }),
8
- logs: [
9
- new logs.SimpleLogRecordProcessor(new logs.ConsoleLogRecordExporter()),
10
- ],
11
- };
12
- }
@@ -1,3 +0,0 @@
1
- import type { NodeSDKConfig } from "./index.js";
2
- import type { Exporters } from "./options.js";
3
- export default function buildGrpcExporters(config: NodeSDKConfig): Exporters;
@@ -1,3 +0,0 @@
1
- import type { NodeSDKConfig } from "./index.js";
2
- import type { Exporters } from "./options.js";
3
- export default function buildHttpExporters(config: NodeSDKConfig): Exporters;
package/lib/console.ts DELETED
@@ -1,15 +0,0 @@
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
- }