@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.
- package/CHANGELOG.md +19 -0
- 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 +17 -5
- package/dist/lib/instrumentation.node.js +27 -9
- package/dist/lib/metrics.d.ts +12 -12
- package/dist/lib/metrics.js +6 -10
- 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 +3 -2
- package/dist/lib/url-sampler.js +5 -8
- package/dist/lib/utils.d.ts +4 -2
- package/dist/lib/utils.js +8 -3
- package/dist/package.json +21 -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 +24 -4
- package/lib/instrumentation.node.ts +49 -11
- package/lib/metrics.ts +34 -29
- 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 +13 -14
- package/lib/utils.ts +16 -4
- package/package.json +21 -17
- package/test/enrich-span-processor.test.ts +105 -0
- package/test/index.test.ts +9 -0
- package/test/integration/integration.test.ts +21 -0
- package/test/integration/run.sh +5 -2
- package/test/metrics.test.ts +9 -9
- package/test/node-config.test.ts +49 -36
- package/test/url-sampler.test.ts +215 -0
- package/vitest.config.ts +15 -1
- 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
|
@@ -1,18 +1,24 @@
|
|
|
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-grpc";
|
|
7
4
|
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-grpc";
|
|
8
5
|
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
|
|
6
|
+
import { NodeSDKConfig } from "../index.js";
|
|
7
|
+
import { Exporters } from "../options.js";
|
|
8
|
+
import { LogRecordProcessorMap, SpanProcessorMap } from "../utils.js";
|
|
9
|
+
import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
|
|
9
10
|
|
|
10
11
|
export default function buildGrpcExporters(config: NodeSDKConfig): Exporters {
|
|
11
12
|
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
spans: [
|
|
14
|
+
new SpanProcessorMap[config.collectorMode ?? "batch"](
|
|
15
|
+
new OTLPTraceExporter({
|
|
16
|
+
url: `${config.collectorUrl}`,
|
|
17
|
+
compression: CompressionAlgorithm.GZIP,
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
new EnrichSpanProcessor(config.spanAttributes),
|
|
21
|
+
],
|
|
16
22
|
metrics: new metrics.PeriodicExportingMetricReader({
|
|
17
23
|
exporter: new OTLPMetricExporter({
|
|
18
24
|
url: `${config.collectorUrl}`,
|
|
@@ -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.
|
|
@@ -32,9 +32,27 @@ interface SDKConfig {
|
|
|
32
32
|
* @default []
|
|
33
33
|
*/
|
|
34
34
|
ignoreUrls?: SamplerCondition[];
|
|
35
|
-
}
|
|
36
35
|
|
|
37
|
-
|
|
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;
|
|
55
|
+
|
|
38
56
|
/**
|
|
39
57
|
* Flag to enable or disable the tracing for node:fs module
|
|
40
58
|
*
|
|
@@ -43,7 +61,7 @@ export interface NodeSDKConfig extends SDKConfig {
|
|
|
43
61
|
enableFS?: boolean;
|
|
44
62
|
|
|
45
63
|
/**
|
|
46
|
-
*
|
|
64
|
+
* Protocol used to send signals.
|
|
47
65
|
*
|
|
48
66
|
* @default grpc
|
|
49
67
|
*/
|
|
@@ -55,6 +73,8 @@ export interface SamplerCondition {
|
|
|
55
73
|
url: string;
|
|
56
74
|
}
|
|
57
75
|
|
|
76
|
+
export type SignalAttributeValue = string | number | boolean;
|
|
77
|
+
|
|
58
78
|
export type SDKCollectorMode = "single" | "batch";
|
|
59
79
|
|
|
60
80
|
export type SDKProtocol = "grpc" | "http" | "console";
|
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { diag, DiagConsoleLogger, DiagLogLevel } from "@opentelemetry/api";
|
|
1
|
+
import {
|
|
2
|
+
diag,
|
|
3
|
+
DiagConsoleLogger,
|
|
4
|
+
DiagLogLevel,
|
|
5
|
+
Span,
|
|
6
|
+
} from "@opentelemetry/api";
|
|
8
7
|
import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
|
|
9
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";
|
|
10
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";
|
|
11
21
|
import { UrlSampler } from "./url-sampler.js";
|
|
12
22
|
|
|
13
23
|
export default function buildNodeInstrumentation(
|
|
@@ -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,18 +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,
|
|
64
|
-
sampler:
|
|
88
|
+
sampler: mainSampler,
|
|
65
89
|
textMapPropagator: new W3CTraceContextPropagator(),
|
|
66
90
|
instrumentations: [
|
|
67
91
|
getNodeAutoInstrumentations({
|
|
68
92
|
"@opentelemetry/instrumentation-fs": {
|
|
69
93
|
enabled: config.enableFS ?? false,
|
|
70
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
|
+
},
|
|
71
110
|
}),
|
|
72
111
|
],
|
|
73
112
|
});
|
|
@@ -87,8 +126,7 @@ function isUrl(url: string): boolean {
|
|
|
87
126
|
try {
|
|
88
127
|
new URL(url);
|
|
89
128
|
return true;
|
|
90
|
-
} catch (
|
|
91
|
-
console.error(err);
|
|
129
|
+
} catch (_) {
|
|
92
130
|
return false;
|
|
93
131
|
}
|
|
94
132
|
}
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"async-
|
|
22
|
-
"async-
|
|
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
|
|
26
|
+
export type MetricType = keyof MetricTypeMap<Attributes>;
|
|
27
27
|
|
|
28
|
-
const MetricsFactoryMap:
|
|
29
|
-
|
|
28
|
+
const MetricsFactoryMap: Record<
|
|
29
|
+
MetricType,
|
|
30
|
+
(
|
|
30
31
|
meter: Meter,
|
|
31
|
-
) => (
|
|
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({
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
57
|
+
|
|
58
|
+
return metrics.getMeter(`custom_metric.${meterName}`);
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
export function getMetric<
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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)(
|
|
71
|
+
return MetricsFactoryMap[type](meter).bind(meter)(
|
|
72
|
+
p.metricName,
|
|
73
|
+
p.options,
|
|
74
|
+
) as MetricTypeMap<TAttributes>[T];
|
|
70
75
|
}
|
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
package/lib/url-sampler.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Context,
|
|
3
|
-
SpanKind,
|
|
4
|
-
Link,
|
|
5
|
-
Attributes,
|
|
6
|
-
isValidTraceId,
|
|
7
|
-
} from "@opentelemetry/api";
|
|
1
|
+
import { Attributes, Context, Link, SpanKind } from "@opentelemetry/api";
|
|
8
2
|
import {
|
|
9
3
|
Sampler,
|
|
10
4
|
SamplingDecision,
|
|
@@ -14,9 +8,11 @@ import { SamplerCondition } from "./index.js";
|
|
|
14
8
|
|
|
15
9
|
export class UrlSampler implements Sampler {
|
|
16
10
|
private _samplerCondition: SamplerCondition[];
|
|
11
|
+
private _nextSampler: Sampler;
|
|
17
12
|
|
|
18
|
-
constructor(samplerCondition: SamplerCondition[] = []) {
|
|
13
|
+
constructor(samplerCondition: SamplerCondition[] = [], nextSampler: Sampler) {
|
|
19
14
|
this._samplerCondition = samplerCondition;
|
|
15
|
+
this._nextSampler = nextSampler;
|
|
20
16
|
}
|
|
21
17
|
|
|
22
18
|
shouldSample(
|
|
@@ -41,13 +37,16 @@ export class UrlSampler implements Sampler {
|
|
|
41
37
|
}
|
|
42
38
|
}
|
|
43
39
|
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
40
|
+
return this._nextSampler.shouldSample(
|
|
41
|
+
_context,
|
|
42
|
+
traceId,
|
|
43
|
+
_spanName,
|
|
44
|
+
_spanKind,
|
|
45
|
+
attributes,
|
|
46
|
+
_links,
|
|
47
|
+
);
|
|
49
48
|
}
|
|
50
49
|
toString(): string {
|
|
51
|
-
|
|
50
|
+
return "UrlSampler";
|
|
52
51
|
}
|
|
53
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.8",
|
|
4
4
|
"description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -20,29 +20,33 @@
|
|
|
20
20
|
"license": "ISC",
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@opentelemetry/api": "^1.9.0",
|
|
23
|
-
"@opentelemetry/auto-instrumentations-node": "^0.56.
|
|
23
|
+
"@opentelemetry/auto-instrumentations-node": "^0.56.1",
|
|
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
|
+
"@opentelemetry/sdk-node": "^0.57.2",
|
|
35
36
|
"@opentelemetry/sdk-trace-base": "^1.30.1"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
|
-
"@types/node": "^22.
|
|
39
|
-
"@vitest/coverage-v8": "^3.0.
|
|
40
|
-
"tsx": "^4.19.
|
|
41
|
-
"typescript": "^5.
|
|
42
|
-
"vitest": "^3.0.
|
|
39
|
+
"@types/node": "^22.13.9",
|
|
40
|
+
"@vitest/coverage-v8": "^3.0.8",
|
|
41
|
+
"tsx": "^4.19.3",
|
|
42
|
+
"typescript": "^5.8.2",
|
|
43
|
+
"vitest": "^3.0.8"
|
|
43
44
|
},
|
|
44
45
|
"scripts": {
|
|
45
46
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
46
|
-
"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"
|
|
47
51
|
}
|
|
48
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(
|
|
@@ -23,6 +23,27 @@ describe("instrumentation integration test", () => {
|
|
|
23
23
|
) {
|
|
24
24
|
if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
|
|
25
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
|
+
|
|
26
47
|
continue;
|
|
27
48
|
}
|
|
28
49
|
if (parsedLine["attributes"]["http.target"] === "/api/health") {
|