@ogcio/o11y-sdk-node 0.1.0-beta.4 → 0.1.0-beta.7
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 +22 -1
- package/README.md +59 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -0
- package/dist/lib/exporter/console.d.ts +3 -0
- package/dist/lib/exporter/console.js +16 -0
- package/dist/lib/exporter/grpc.d.ts +3 -0
- package/dist/lib/{grpc.js → exporter/grpc.js} +9 -5
- package/dist/lib/exporter/http.d.ts +3 -0
- package/dist/lib/{http.js → exporter/http.js} +9 -5
- package/dist/lib/index.d.ts +28 -5
- package/dist/lib/instrumentation.node.js +37 -7
- package/dist/lib/options.d.ts +5 -3
- package/dist/lib/processor/enrich-span-processor.d.ts +11 -0
- package/dist/lib/processor/enrich-span-processor.js +22 -0
- package/dist/lib/traces.d.ts +1 -0
- package/dist/lib/traces.js +4 -0
- package/dist/lib/url-sampler.d.ts +10 -0
- package/dist/lib/url-sampler.js +25 -0
- package/dist/lib/utils.d.ts +4 -2
- package/dist/lib/utils.js +8 -3
- package/dist/package.json +20 -17
- package/dist/vitest.config.js +15 -1
- package/index.ts +2 -2
- package/lib/exporter/console.ts +27 -0
- package/lib/{grpc.ts → exporter/grpc.ts} +13 -7
- package/lib/{http.ts → exporter/http.ts} +13 -7
- package/lib/index.ts +36 -4
- package/lib/instrumentation.node.ts +59 -9
- package/lib/options.ts +5 -3
- package/lib/processor/enrich-span-processor.ts +39 -0
- package/lib/traces.ts +5 -0
- package/lib/url-sampler.ts +52 -0
- package/lib/utils.ts +16 -4
- package/package.json +20 -17
- package/test/enrich-span-processor.test.ts +105 -0
- package/test/index.test.ts +9 -0
- package/test/integration/README.md +26 -0
- package/test/integration/integration.test.ts +58 -0
- package/test/integration/run.sh +85 -0
- package/test/node-config.test.ts +49 -36
- package/test/url-sampler.test.ts +215 -0
- package/test/utils/alloy-log-parser.ts +46 -0
- package/vitest.config.ts +15 -1
- package/coverage/cobertura-coverage.xml +0 -310
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -131
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sdk-node/index.html +0 -116
- package/coverage/lcov-report/sdk-node/index.ts.html +0 -112
- package/coverage/lcov-report/sdk-node/lib/console.ts.html +0 -130
- package/coverage/lcov-report/sdk-node/lib/grpc.ts.html +0 -178
- package/coverage/lcov-report/sdk-node/lib/http.ts.html +0 -190
- package/coverage/lcov-report/sdk-node/lib/index.html +0 -221
- package/coverage/lcov-report/sdk-node/lib/index.ts.html +0 -256
- package/coverage/lcov-report/sdk-node/lib/instrumentation.node.ts.html +0 -334
- package/coverage/lcov-report/sdk-node/lib/metrics.ts.html +0 -295
- package/coverage/lcov-report/sdk-node/lib/options.ts.html +0 -106
- package/coverage/lcov-report/sdk-node/lib/utils.ts.html +0 -115
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -196
- package/coverage/lcov.info +0 -312
- package/dist/lib/console.d.ts +0 -3
- package/dist/lib/console.js +0 -12
- package/dist/lib/grpc.d.ts +0 -3
- package/dist/lib/http.d.ts +0 -3
- package/lib/console.ts +0 -15
- package/test-report.xml +0 -71
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import type { NodeSDKConfig } from "./index.js";
|
|
2
|
-
import type { Exporters } from "./options.js";
|
|
3
|
-
import { LogRecordProcessorMap } from "./utils.js";
|
|
4
1
|
import { metrics } from "@opentelemetry/sdk-node";
|
|
5
2
|
import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base";
|
|
6
3
|
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
7
4
|
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
8
5
|
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
|
|
6
|
+
import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
|
|
7
|
+
import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
|
|
8
|
+
import { Exporters } from "../options.js";
|
|
9
|
+
import { NodeSDKConfig } from "../index.js";
|
|
9
10
|
|
|
10
11
|
export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
|
|
11
12
|
if (config.collectorUrl.endsWith("/")) {
|
|
@@ -13,10 +14,15 @@ export default function buildHttpExporters(config: NodeSDKConfig): Exporters {
|
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
return {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
spans: [
|
|
18
|
+
new SpanProcessorMap[config.collectorMode ?? "batch"](
|
|
19
|
+
new OTLPTraceExporter({
|
|
20
|
+
url: `${config.collectorUrl}/v1/traces`,
|
|
21
|
+
compression: CompressionAlgorithm.GZIP,
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
new EnrichSpanProcessor(config.spanAttributes),
|
|
25
|
+
],
|
|
20
26
|
metrics: new metrics.PeriodicExportingMetricReader({
|
|
21
27
|
exporter: new OTLPMetricExporter({
|
|
22
28
|
url: `${config.collectorUrl}/v1/metrics`,
|
package/lib/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
interface
|
|
1
|
+
export interface NodeSDKConfig {
|
|
2
2
|
/**
|
|
3
3
|
* The opentelemetry collector entrypoint GRPC url.
|
|
4
4
|
* If the collectoUrl is null or undefined, the instrumentation will not be activated.
|
|
@@ -25,9 +25,34 @@ interface SDKConfig {
|
|
|
25
25
|
* @default batch
|
|
26
26
|
*/
|
|
27
27
|
collectorMode?: SDKCollectorMode;
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Array of not traced urls.
|
|
30
|
+
*
|
|
31
|
+
* @type {SamplerCondition}
|
|
32
|
+
* @default []
|
|
33
|
+
*/
|
|
34
|
+
ignoreUrls?: SamplerCondition[];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Object containing static properties or functions used to evaluate custom attributes for every logs and traces.
|
|
38
|
+
*/
|
|
39
|
+
spanAttributes?: Record<
|
|
40
|
+
string,
|
|
41
|
+
SignalAttributeValue | (() => SignalAttributeValue)
|
|
42
|
+
>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Object containing static properties used as resources attributes for the Node SDK initialization.
|
|
46
|
+
*/
|
|
47
|
+
resourceAttributes?: Record<string, SignalAttributeValue>;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Faction value from 0 to 1, used by TraceIdRatioBasedSampler which it deterministically samples a percentage of traces that you pass in as a parameter.
|
|
51
|
+
*
|
|
52
|
+
* @default 1
|
|
53
|
+
*/
|
|
54
|
+
traceRatio?: number;
|
|
29
55
|
|
|
30
|
-
export interface NodeSDKConfig extends SDKConfig {
|
|
31
56
|
/**
|
|
32
57
|
* Flag to enable or disable the tracing for node:fs module
|
|
33
58
|
*
|
|
@@ -36,13 +61,20 @@ export interface NodeSDKConfig extends SDKConfig {
|
|
|
36
61
|
enableFS?: boolean;
|
|
37
62
|
|
|
38
63
|
/**
|
|
39
|
-
*
|
|
64
|
+
* Protocol used to send signals.
|
|
40
65
|
*
|
|
41
66
|
* @default grpc
|
|
42
67
|
*/
|
|
43
68
|
protocol?: SDKProtocol;
|
|
44
69
|
}
|
|
45
70
|
|
|
71
|
+
export interface SamplerCondition {
|
|
72
|
+
type: "endsWith" | "includes" | "equals";
|
|
73
|
+
url: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type SignalAttributeValue = string | number | boolean;
|
|
77
|
+
|
|
46
78
|
export type SDKCollectorMode = "single" | "batch";
|
|
47
79
|
|
|
48
80
|
export type SDKProtocol = "grpc" | "http" | "console";
|
|
@@ -1,14 +1,24 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import buildConsoleExporters from "./console.js";
|
|
8
|
-
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
1
|
+
import {
|
|
2
|
+
diag,
|
|
3
|
+
DiagConsoleLogger,
|
|
4
|
+
DiagLogLevel,
|
|
5
|
+
Span,
|
|
6
|
+
} from "@opentelemetry/api";
|
|
9
7
|
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
10
8
|
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
9
|
+
import { NodeSDK, resources } from "@opentelemetry/sdk-node";
|
|
10
|
+
import {
|
|
11
|
+
AlwaysOffSampler,
|
|
12
|
+
ParentBasedSampler,
|
|
13
|
+
TraceIdRatioBasedSampler,
|
|
14
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
11
15
|
import packageJson from "../package.json" with { type: "json" };
|
|
16
|
+
import buildConsoleExporters from "./exporter/console.js";
|
|
17
|
+
import buildGrpcExporters from "./exporter/grpc.js";
|
|
18
|
+
import buildHttpExporters from "./exporter/http.js";
|
|
19
|
+
import type { NodeSDKConfig } from "./index.js";
|
|
20
|
+
import type { Exporters } from "./options.js";
|
|
21
|
+
import { UrlSampler } from "./url-sampler.js";
|
|
12
22
|
|
|
13
23
|
export default function buildNodeInstrumentation(
|
|
14
24
|
config?: NodeSDKConfig,
|
|
@@ -44,6 +54,19 @@ export default function buildNodeInstrumentation(
|
|
|
44
54
|
exporter = buildGrpcExporters(config);
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
const urlSampler = new UrlSampler(
|
|
58
|
+
config.ignoreUrls,
|
|
59
|
+
new TraceIdRatioBasedSampler(config.traceRatio ?? 1),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const mainSampler = new ParentBasedSampler({
|
|
63
|
+
root: urlSampler,
|
|
64
|
+
remoteParentSampled: urlSampler,
|
|
65
|
+
remoteParentNotSampled: new AlwaysOffSampler(),
|
|
66
|
+
localParentSampled: urlSampler,
|
|
67
|
+
localParentNotSampled: new AlwaysOffSampler(),
|
|
68
|
+
});
|
|
69
|
+
|
|
47
70
|
try {
|
|
48
71
|
diag.setLogger(
|
|
49
72
|
new DiagConsoleLogger(),
|
|
@@ -56,17 +79,34 @@ export default function buildNodeInstrumentation(
|
|
|
56
79
|
resource: new resources.Resource({
|
|
57
80
|
"o11y.sdk.name": packageJson.name,
|
|
58
81
|
"o11y.sdk.version": packageJson.version,
|
|
82
|
+
...config.resourceAttributes,
|
|
59
83
|
}),
|
|
84
|
+
spanProcessors: exporter.spans,
|
|
60
85
|
serviceName: config.serviceName,
|
|
61
|
-
traceExporter: exporter.traces,
|
|
62
86
|
metricReader: exporter.metrics,
|
|
63
87
|
logRecordProcessors: exporter.logs,
|
|
88
|
+
sampler: mainSampler,
|
|
64
89
|
textMapPropagator: new W3CTraceContextPropagator(),
|
|
65
90
|
instrumentations: [
|
|
66
91
|
getNodeAutoInstrumentations({
|
|
67
92
|
"@opentelemetry/instrumentation-fs": {
|
|
68
93
|
enabled: config.enableFS ?? false,
|
|
69
94
|
},
|
|
95
|
+
"@opentelemetry/instrumentation-pino": {
|
|
96
|
+
logHook: (
|
|
97
|
+
_span: Span,
|
|
98
|
+
record: Record<string, unknown>,
|
|
99
|
+
_level?: number,
|
|
100
|
+
) => {
|
|
101
|
+
if (config.spanAttributes != undefined) {
|
|
102
|
+
for (const [key, value] of Object.entries(
|
|
103
|
+
config.spanAttributes,
|
|
104
|
+
)) {
|
|
105
|
+
record[key] = typeof value === "function" ? value() : value;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
},
|
|
70
110
|
}),
|
|
71
111
|
],
|
|
72
112
|
});
|
|
@@ -81,3 +121,13 @@ export default function buildNodeInstrumentation(
|
|
|
81
121
|
);
|
|
82
122
|
}
|
|
83
123
|
}
|
|
124
|
+
|
|
125
|
+
function isUrl(url: string): boolean {
|
|
126
|
+
try {
|
|
127
|
+
new URL(url);
|
|
128
|
+
return true;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error(err);
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
}
|
package/lib/options.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { LogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
2
|
+
import type { metrics } from "@opentelemetry/sdk-node";
|
|
3
|
+
import { SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
2
4
|
|
|
3
5
|
export type Exporters = {
|
|
4
|
-
|
|
6
|
+
spans: SpanProcessor[];
|
|
5
7
|
metrics: metrics.MetricReader;
|
|
6
|
-
logs:
|
|
8
|
+
logs: LogRecordProcessor[];
|
|
7
9
|
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Context } from "@opentelemetry/api";
|
|
2
|
+
import {
|
|
3
|
+
ReadableSpan,
|
|
4
|
+
Span,
|
|
5
|
+
SpanProcessor,
|
|
6
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
7
|
+
import { SignalAttributeValue } from "../index.js";
|
|
8
|
+
|
|
9
|
+
export class EnrichSpanProcessor implements SpanProcessor {
|
|
10
|
+
private _spanAttributes:
|
|
11
|
+
| Record<string, SignalAttributeValue | (() => SignalAttributeValue)>
|
|
12
|
+
| undefined;
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
spanAttributes?: Record<
|
|
16
|
+
string,
|
|
17
|
+
SignalAttributeValue | (() => SignalAttributeValue)
|
|
18
|
+
>,
|
|
19
|
+
) {
|
|
20
|
+
this._spanAttributes = spanAttributes;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
forceFlush(): Promise<void> {
|
|
24
|
+
return Promise.resolve();
|
|
25
|
+
}
|
|
26
|
+
onStart(span: Span, _context: Context): void {
|
|
27
|
+
if (this._spanAttributes != undefined) {
|
|
28
|
+
for (const [key, value] of Object.entries(this._spanAttributes)) {
|
|
29
|
+
span.setAttribute(key, typeof value === "function" ? value() : value);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
onEnd(_span: ReadableSpan): void {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
shutdown(): Promise<void> {
|
|
37
|
+
return Promise.resolve();
|
|
38
|
+
}
|
|
39
|
+
}
|
package/lib/traces.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Attributes, Context, Link, SpanKind } from "@opentelemetry/api";
|
|
2
|
+
import {
|
|
3
|
+
Sampler,
|
|
4
|
+
SamplingDecision,
|
|
5
|
+
SamplingResult,
|
|
6
|
+
} from "@opentelemetry/sdk-trace-base";
|
|
7
|
+
import { SamplerCondition } from "./index.js";
|
|
8
|
+
|
|
9
|
+
export class UrlSampler implements Sampler {
|
|
10
|
+
private _samplerCondition: SamplerCondition[];
|
|
11
|
+
private _nextSampler: Sampler;
|
|
12
|
+
|
|
13
|
+
constructor(samplerCondition: SamplerCondition[] = [], nextSampler: Sampler) {
|
|
14
|
+
this._samplerCondition = samplerCondition;
|
|
15
|
+
this._nextSampler = nextSampler;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
shouldSample(
|
|
19
|
+
_context: Context,
|
|
20
|
+
traceId: string,
|
|
21
|
+
_spanName: string,
|
|
22
|
+
_spanKind: SpanKind,
|
|
23
|
+
attributes: Attributes,
|
|
24
|
+
_links: Link[],
|
|
25
|
+
): SamplingResult {
|
|
26
|
+
const url: string | undefined = attributes["http.target"]?.toString();
|
|
27
|
+
|
|
28
|
+
if (url) {
|
|
29
|
+
for (const condition of this._samplerCondition) {
|
|
30
|
+
if (
|
|
31
|
+
(condition.type === "equals" && url === condition.url) ||
|
|
32
|
+
(condition.type === "endsWith" && url.endsWith(condition.url)) ||
|
|
33
|
+
(condition.type === "includes" && url.includes(condition.url))
|
|
34
|
+
) {
|
|
35
|
+
return { decision: SamplingDecision.NOT_RECORD };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return this._nextSampler.shouldSample(
|
|
41
|
+
_context,
|
|
42
|
+
traceId,
|
|
43
|
+
_spanName,
|
|
44
|
+
_spanKind,
|
|
45
|
+
attributes,
|
|
46
|
+
_links,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
toString(): string {
|
|
50
|
+
return "UrlSampler";
|
|
51
|
+
}
|
|
52
|
+
}
|
package/lib/utils.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BatchLogRecordProcessor,
|
|
3
|
+
SimpleLogRecordProcessor,
|
|
4
|
+
} from "@opentelemetry/sdk-logs";
|
|
1
5
|
import { SDKCollectorMode } from "./index.js";
|
|
2
|
-
import {
|
|
6
|
+
import { tracing } from "@opentelemetry/sdk-node";
|
|
3
7
|
|
|
4
8
|
export const LogRecordProcessorMap: Record<
|
|
5
9
|
SDKCollectorMode,
|
|
6
|
-
typeof
|
|
10
|
+
typeof SimpleLogRecordProcessor | typeof BatchLogRecordProcessor
|
|
7
11
|
> = {
|
|
8
|
-
single:
|
|
9
|
-
batch:
|
|
12
|
+
single: SimpleLogRecordProcessor,
|
|
13
|
+
batch: BatchLogRecordProcessor,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const SpanProcessorMap: Record<
|
|
17
|
+
SDKCollectorMode,
|
|
18
|
+
typeof tracing.SimpleSpanProcessor | typeof tracing.BatchSpanProcessor
|
|
19
|
+
> = {
|
|
20
|
+
single: tracing.SimpleSpanProcessor,
|
|
21
|
+
batch: tracing.BatchSpanProcessor,
|
|
10
22
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogcio/o11y-sdk-node",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.7",
|
|
4
4
|
"description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,30 +20,33 @@
|
|
|
20
20
|
"license": "ISC",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@opentelemetry/api": "^1.9.0",
|
|
23
|
-
"@opentelemetry/auto-instrumentations-node": "^0.
|
|
23
|
+
"@opentelemetry/auto-instrumentations-node": "^0.56.0",
|
|
24
24
|
"@opentelemetry/core": "1.30.1",
|
|
25
|
-
"@opentelemetry/exporter-logs-otlp-grpc": "^0.57.
|
|
26
|
-
"@opentelemetry/exporter-logs-otlp-http": "^0.57.
|
|
27
|
-
"@opentelemetry/exporter-metrics-otlp-grpc": "^0.57.
|
|
28
|
-
"@opentelemetry/exporter-metrics-otlp-http": "^0.57.
|
|
29
|
-
"@opentelemetry/exporter-trace-otlp-grpc": "^0.57.
|
|
30
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.57.
|
|
31
|
-
"@opentelemetry/instrumentation": "^0.57.
|
|
32
|
-
"@opentelemetry/otlp-exporter-base": "^0.57.
|
|
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",
|
|
33
34
|
"@opentelemetry/sdk-metrics": "^1.30.1",
|
|
34
|
-
"@opentelemetry/sdk-node": "^0.57.
|
|
35
|
-
"
|
|
35
|
+
"@opentelemetry/sdk-node": "^0.57.2",
|
|
36
|
+
"@opentelemetry/sdk-trace-base": "^1.30.1"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@types/
|
|
39
|
-
"@
|
|
40
|
-
"@vitest/coverage-v8": "^3.0.4",
|
|
39
|
+
"@types/node": "^22.13.4",
|
|
40
|
+
"@vitest/coverage-v8": "^3.0.5",
|
|
41
41
|
"tsx": "^4.19.2",
|
|
42
42
|
"typescript": "^5.7.3",
|
|
43
|
-
"vitest": "^3.0.
|
|
43
|
+
"vitest": "^3.0.5"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
46
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
47
|
-
"test": "vitest"
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test:unit": "vitest --project unit",
|
|
49
|
+
"test:integration": "pnpm --filter @ogcio/o11y run prepare:integration && vitest --project integration",
|
|
50
|
+
"test:integration:dryrun": "vitest --project integration"
|
|
48
51
|
}
|
|
49
52
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AttributeValue,
|
|
3
|
+
Context,
|
|
4
|
+
Exception,
|
|
5
|
+
Link,
|
|
6
|
+
Span,
|
|
7
|
+
SpanAttributes,
|
|
8
|
+
SpanAttributeValue,
|
|
9
|
+
SpanContext,
|
|
10
|
+
SpanStatus,
|
|
11
|
+
TimeInput,
|
|
12
|
+
} from "@opentelemetry/api";
|
|
13
|
+
import { describe, expect, it } from "vitest";
|
|
14
|
+
import { EnrichSpanProcessor } from "../lib/processor/enrich-span-processor.js";
|
|
15
|
+
|
|
16
|
+
class MockSpan implements Span {
|
|
17
|
+
public attributes: Record<string, AttributeValue> = {};
|
|
18
|
+
|
|
19
|
+
spanContext(): SpanContext {
|
|
20
|
+
throw new Error("Method not implemented.");
|
|
21
|
+
}
|
|
22
|
+
setAttribute(key: string, value: AttributeValue): this {
|
|
23
|
+
this.attributes[key] = value;
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
setAttributes(attributes: SpanAttributes): this {
|
|
27
|
+
throw new Error("Method not implemented.");
|
|
28
|
+
}
|
|
29
|
+
addEvent(
|
|
30
|
+
name: string,
|
|
31
|
+
attributesOrStartTime?: SpanAttributes | TimeInput,
|
|
32
|
+
startTime?: TimeInput,
|
|
33
|
+
): this {
|
|
34
|
+
throw new Error("Method not implemented.");
|
|
35
|
+
}
|
|
36
|
+
addLink(link: Link): this {
|
|
37
|
+
throw new Error("Method not implemented.");
|
|
38
|
+
}
|
|
39
|
+
addLinks(links: Link[]): this {
|
|
40
|
+
throw new Error("Method not implemented.");
|
|
41
|
+
}
|
|
42
|
+
setStatus(status: SpanStatus): this {
|
|
43
|
+
throw new Error("Method not implemented.");
|
|
44
|
+
}
|
|
45
|
+
updateName(name: string): this {
|
|
46
|
+
throw new Error("Method not implemented.");
|
|
47
|
+
}
|
|
48
|
+
end(endTime?: TimeInput): void {
|
|
49
|
+
throw new Error("Method not implemented.");
|
|
50
|
+
}
|
|
51
|
+
isRecording(): boolean {
|
|
52
|
+
throw new Error("Method not implemented.");
|
|
53
|
+
}
|
|
54
|
+
recordException(exception: Exception, time?: TimeInput): void {
|
|
55
|
+
throw new Error("Method not implemented.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("EnrichSpanProcessor", () => {
|
|
60
|
+
it("should set static attributes on span", () => {
|
|
61
|
+
const spanAttributes = {
|
|
62
|
+
key1: "value1",
|
|
63
|
+
key2: 123,
|
|
64
|
+
};
|
|
65
|
+
const processor = new EnrichSpanProcessor(spanAttributes);
|
|
66
|
+
const mockSpan = new MockSpan();
|
|
67
|
+
const mockContext = {} as Context;
|
|
68
|
+
|
|
69
|
+
processor.onStart(mockSpan, mockContext);
|
|
70
|
+
|
|
71
|
+
expect(mockSpan.attributes["key1"]).toBe("value1");
|
|
72
|
+
expect(mockSpan.attributes["key2"]).toBe(123);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should set dynamic attributes on span", () => {
|
|
76
|
+
const spanAttributes = {
|
|
77
|
+
dynamicKey: () => "dynamicValue",
|
|
78
|
+
};
|
|
79
|
+
const processor = new EnrichSpanProcessor(spanAttributes);
|
|
80
|
+
const mockSpan = new MockSpan();
|
|
81
|
+
const mockContext = {} as Context;
|
|
82
|
+
|
|
83
|
+
processor.onStart(mockSpan, mockContext);
|
|
84
|
+
|
|
85
|
+
expect(mockSpan.attributes["dynamicKey"]).toBe("dynamicValue");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should not set attributes if none are provided", () => {
|
|
89
|
+
const processor = new EnrichSpanProcessor();
|
|
90
|
+
const mockSpan = new MockSpan();
|
|
91
|
+
const mockContext = {} as Context;
|
|
92
|
+
|
|
93
|
+
processor.onStart(mockSpan, mockContext);
|
|
94
|
+
|
|
95
|
+
expect(mockSpan.attributes["key1"]).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("default method, should maintain default behaviour", async () => {
|
|
99
|
+
const processor = new EnrichSpanProcessor();
|
|
100
|
+
|
|
101
|
+
expect(processor.onEnd(null!)).toBeUndefined();
|
|
102
|
+
expect(await processor.shutdown()).toBeUndefined();
|
|
103
|
+
expect(await processor.forceFlush()).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
});
|
package/test/index.test.ts
CHANGED
|
@@ -12,6 +12,15 @@ describe("instrumentNode", () => {
|
|
|
12
12
|
serviceName: "custom-service",
|
|
13
13
|
collectorUrl: "http://custom-collector.com",
|
|
14
14
|
protocol: "grpc",
|
|
15
|
+
resourceAttributes: {
|
|
16
|
+
"team.infra.cluster": "dev-01",
|
|
17
|
+
"team.infra.pod": "01",
|
|
18
|
+
"team.service.type": "fastify",
|
|
19
|
+
},
|
|
20
|
+
spanAttributes: {
|
|
21
|
+
"signal.namespace": "example",
|
|
22
|
+
"signal.number": () => "callback",
|
|
23
|
+
},
|
|
15
24
|
};
|
|
16
25
|
|
|
17
26
|
const buildNodeInstrumentation = await import(
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Integration Test
|
|
2
|
+
|
|
3
|
+
This folder contains a setup for integration test with o11y node sdk.
|
|
4
|
+
|
|
5
|
+
## Workflow
|
|
6
|
+
|
|
7
|
+
- Docker must be in running state
|
|
8
|
+
- Run the sh script `sh ./packages/sdk-node/test/integration/run.sh 1 .` from project root with following params
|
|
9
|
+
1. pipeline build number, for local development, any number or string is fine
|
|
10
|
+
2. root folder for docker context
|
|
11
|
+
- Change dir to `packages/sdk-node/`
|
|
12
|
+
- Run full test suite with `pnpm test`
|
|
13
|
+
|
|
14
|
+
## Script
|
|
15
|
+
|
|
16
|
+
The `run.sh` script performs the following steps:
|
|
17
|
+
|
|
18
|
+
- build a docker image of a fastify app `/examples/fastify`
|
|
19
|
+
- setup an temporary test docker network
|
|
20
|
+
- run grafana alloy inside a docker container with a test configuration `/alloy/integration-test.alloy`
|
|
21
|
+
- ensure is running otherwise exit process
|
|
22
|
+
- run fastify app in a docker container
|
|
23
|
+
- ensure is running otherwise exit process
|
|
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/`
|
|
26
|
+
- docker turn down process (containers/network/image)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, test, assert } from "vitest";
|
|
2
|
+
import { parseLog } from "../utils/alloy-log-parser";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
describe("instrumentation integration test", () => {
|
|
7
|
+
test("should exclude health url and process only dummy calls", async () => {
|
|
8
|
+
const data = await readFile(join(__dirname, "logs.txt"), "utf-8");
|
|
9
|
+
|
|
10
|
+
let health_traces_counter = 0;
|
|
11
|
+
let dummy_traces_counter = 0;
|
|
12
|
+
|
|
13
|
+
console.log(data.split(/\nts=/).length);
|
|
14
|
+
|
|
15
|
+
for (const line of data.split(/\nts=/)) {
|
|
16
|
+
const parsedLine: Record<string, object | string | number> =
|
|
17
|
+
parseLog(line);
|
|
18
|
+
|
|
19
|
+
if (
|
|
20
|
+
parsedLine["attributes"] &&
|
|
21
|
+
parsedLine["attributes"]["span_kind"] &&
|
|
22
|
+
parsedLine["attributes"]["span_kind"] === "trace"
|
|
23
|
+
) {
|
|
24
|
+
if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
|
|
25
|
+
dummy_traces_counter++;
|
|
26
|
+
|
|
27
|
+
// verify global sdk span resource
|
|
28
|
+
assert.equal(
|
|
29
|
+
parsedLine["resource_attributes"]["team.infra.pod"],
|
|
30
|
+
"01",
|
|
31
|
+
);
|
|
32
|
+
assert.equal(
|
|
33
|
+
parsedLine["resource_attributes"]["team.service.type"],
|
|
34
|
+
"fastify",
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// verify global sdk span attributes
|
|
38
|
+
assert.equal(parsedLine["attributes"]["signal.namespace"], "example");
|
|
39
|
+
|
|
40
|
+
// verify runtime custom span inside dev application
|
|
41
|
+
assert.equal(parsedLine["attributes"]["business.info"], "dummy");
|
|
42
|
+
assert.equal(
|
|
43
|
+
parsedLine["attributes"]["business.request_type"],
|
|
44
|
+
"application/json",
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (parsedLine["attributes"]["http.target"] === "/api/health") {
|
|
50
|
+
health_traces_counter++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
assert.equal(health_traces_counter, 0);
|
|
56
|
+
assert.equal(dummy_traces_counter, 2);
|
|
57
|
+
});
|
|
58
|
+
});
|