@ogcio/o11y-sdk-node 0.6.2 → 0.7.1
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/README.md +12 -10
- package/dist/sdk-core/lib/utils/data-structures.js +1 -1
- package/dist/sdk-node/index.d.ts +1 -0
- package/dist/sdk-node/index.js +1 -0
- package/dist/sdk-node/lib/config-manager.js +27 -2
- package/dist/sdk-node/lib/exporter/console.js +1 -0
- package/dist/sdk-node/lib/exporter/grpc.js +1 -0
- package/dist/sdk-node/lib/exporter/http.js +1 -0
- package/dist/sdk-node/lib/exporter/processor-config.d.ts +2 -1
- package/dist/sdk-node/lib/exporter/processor-config.js +5 -0
- package/dist/sdk-node/lib/index.d.ts +32 -0
- package/dist/sdk-node/lib/instrumentation.node.js +8 -6
- package/dist/sdk-node/lib/processor/nextjs-span-processor.d.ts +4 -6
- package/dist/sdk-node/lib/processor/nextjs-span-processor.js +35 -19
- package/dist/sdk-node/lib/processor/on-ending-hook-span-processor.d.ts +37 -0
- package/dist/sdk-node/lib/processor/on-ending-hook-span-processor.js +41 -0
- package/dist/sdk-node/lib/processor/pseudo-profiling-span-processor.d.ts +20 -0
- package/dist/sdk-node/lib/processor/pseudo-profiling-span-processor.js +60 -0
- package/dist/sdk-node/lib/url-sampler.js +3 -1
- package/dist/sdk-node/package.json +12 -9
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -7,13 +7,13 @@ The NodeJS observability sdk is a npm package used to setup and implement opente
|
|
|
7
7
|
pnpm
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
|
|
10
|
+
pnpm i --save @ogcio/o11y-sdk-node
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
npm
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
16
|
+
npm i @ogcio/o11y-sdk-node
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## Usage
|
|
@@ -357,11 +357,13 @@ export type SDKLogLevel =
|
|
|
357
357
|
|
|
358
358
|
### Config
|
|
359
359
|
|
|
360
|
-
| Parameter
|
|
361
|
-
|
|
|
362
|
-
| `collectorUrl`
|
|
363
|
-
| `serviceName`
|
|
364
|
-
| `diagLogLevel`
|
|
365
|
-
| `collectorMode`
|
|
366
|
-
| `enableFS`
|
|
367
|
-
| `
|
|
360
|
+
| Parameter | Type | Description |
|
|
361
|
+
| :--------------------------- | :------------------------- | :---------------------------------------------------------------------------------------------------------------- |
|
|
362
|
+
| `collectorUrl` | `string` | **Required**. The opentelemetry collector entrypoint url, if null, instrumentation will not be activated |
|
|
363
|
+
| `serviceName` | `string` | Name of your application used for the collector to group logs |
|
|
364
|
+
| `diagLogLevel` | `SDKLogLevel` | Diagnostic log level for the internal runtime instrumentation |
|
|
365
|
+
| `collectorMode` | `single \| batch` | Signals sending mode, default is batch for performance |
|
|
366
|
+
| `enableFS` | `boolean` | **Deprecated**. Use `autoInstrumentationConfig` instead. Flag to enable or disable the tracing for node:fs module |
|
|
367
|
+
| `autoInstrumentationConfig` | `InstrumentationConfigMap` | Configuration object for auto instrumentations. Default: `{"@opentelemetry/instrumentation-fs":{enabled:false}}` |
|
|
368
|
+
| `additionalInstrumentations` | `Instrumentation[]` | Additional custom instrumentations to be added to the NodeSDK. Default: `[]` |
|
|
369
|
+
| `protocol` | `string` | Type of the protocol used to send signals |
|
|
@@ -33,7 +33,7 @@ export function _recursiveObjectClean(value, source, redactors) {
|
|
|
33
33
|
value == null) {
|
|
34
34
|
return value;
|
|
35
35
|
}
|
|
36
|
-
if (value
|
|
36
|
+
if (ArrayBuffer.isView(value)) {
|
|
37
37
|
try {
|
|
38
38
|
const decoded = decoder.decode(value);
|
|
39
39
|
const sanitized = _cleanStringPII(decoded, source, redactors);
|
package/dist/sdk-node/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import buildNodeInstrumentation from "./lib/instrumentation.node.js";
|
|
|
3
3
|
export type * from "./lib/index.js";
|
|
4
4
|
export type { NodeSDK };
|
|
5
5
|
export { buildNodeInstrumentation as instrumentNode };
|
|
6
|
+
export * from "./lib/processor/on-ending-hook-span-processor.js";
|
|
6
7
|
export * from "./lib/metrics.js";
|
|
7
8
|
export * from "./lib/traces.js";
|
|
8
9
|
export * from "../sdk-core/index.js";
|
package/dist/sdk-node/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import buildNodeInstrumentation from "./lib/instrumentation.node.js";
|
|
2
2
|
export { buildNodeInstrumentation as instrumentNode };
|
|
3
|
+
export * from "./lib/processor/on-ending-hook-span-processor.js";
|
|
3
4
|
export * from "./lib/metrics.js";
|
|
4
5
|
export * from "./lib/traces.js";
|
|
5
6
|
export * from "../sdk-core/index.js";
|
|
@@ -3,6 +3,31 @@ export const setNodeSdkConfig = (config) => {
|
|
|
3
3
|
nodeSDKConfig = config;
|
|
4
4
|
};
|
|
5
5
|
export const getNodeSdkConfig = () => {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
if (!nodeSDKConfig) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
// Create a shallow copy and handle non-serializable properties separately.
|
|
10
|
+
// Properties like additionalInstrumentations, grpcMetadata, and detection.custom
|
|
11
|
+
// contain class instances with circular references that cannot be JSON serialized.
|
|
12
|
+
const { additionalInstrumentations, grpcMetadata, autoInstrumentationConfig, detection, ...serializableConfig } = nodeSDKConfig;
|
|
13
|
+
// Deep clone the serializable portion of the config
|
|
14
|
+
const clonedConfig = JSON.parse(JSON.stringify(serializableConfig));
|
|
15
|
+
// Re-attach non-serializable properties by reference (they should be read-only)
|
|
16
|
+
if (additionalInstrumentations) {
|
|
17
|
+
clonedConfig.additionalInstrumentations = additionalInstrumentations;
|
|
18
|
+
}
|
|
19
|
+
if (grpcMetadata) {
|
|
20
|
+
clonedConfig.grpcMetadata = grpcMetadata;
|
|
21
|
+
}
|
|
22
|
+
if (autoInstrumentationConfig) {
|
|
23
|
+
clonedConfig.autoInstrumentationConfig = autoInstrumentationConfig;
|
|
24
|
+
}
|
|
25
|
+
if (detection) {
|
|
26
|
+
// Clone detection but keep custom redactors by reference
|
|
27
|
+
clonedConfig.detection = {
|
|
28
|
+
...detection,
|
|
29
|
+
custom: detection.custom,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return clonedConfig;
|
|
8
33
|
};
|
|
@@ -9,6 +9,7 @@ export default function buildConsoleExporters(config) {
|
|
|
9
9
|
..._spansProcessorConfig(config),
|
|
10
10
|
],
|
|
11
11
|
metrics: new metrics.PeriodicExportingMetricReader({
|
|
12
|
+
exportIntervalMillis: config.metrics?.exportIntervalMs,
|
|
12
13
|
exporter: new metrics.ConsoleMetricExporter(),
|
|
13
14
|
}),
|
|
14
15
|
logs: [
|
|
@@ -24,6 +24,7 @@ export default async function buildGrpcExporters(config) {
|
|
|
24
24
|
..._spansProcessorConfig(config),
|
|
25
25
|
],
|
|
26
26
|
metrics: new metrics.PeriodicExportingMetricReader({
|
|
27
|
+
exportIntervalMillis: config.metrics?.exportIntervalMs,
|
|
27
28
|
exporter: new PIIExporterDecorator(new OTLPMetricExporter({
|
|
28
29
|
url: `${config.collectorUrl}`,
|
|
29
30
|
compression: CompressionAlgorithm.GZIP,
|
|
@@ -20,6 +20,7 @@ export default function buildHttpExporters(config) {
|
|
|
20
20
|
..._spansProcessorConfig(config),
|
|
21
21
|
],
|
|
22
22
|
metrics: new metrics.PeriodicExportingMetricReader({
|
|
23
|
+
exportIntervalMillis: config.metrics?.exportIntervalMs,
|
|
23
24
|
exporter: new PIIExporterDecorator(new OTLPMetricExporter({
|
|
24
25
|
url: `${config.collectorUrl}/v1/metrics`,
|
|
25
26
|
compression: CompressionAlgorithm.GZIP,
|
|
@@ -3,5 +3,6 @@ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
|
|
|
3
3
|
import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
|
|
4
4
|
import { NextJsLogProcessor } from "../processor/nextjs-logger-processor.js";
|
|
5
5
|
import { NextJsSpanProcessor } from "../processor/nextjs-span-processor.js";
|
|
6
|
-
|
|
6
|
+
import { PseudoProfilingSpanProcessor } from "../processor/pseudo-profiling-span-processor.js";
|
|
7
|
+
export declare function _spansProcessorConfig(config: NodeSDKConfig): (EnrichSpanProcessor | NextJsSpanProcessor | PseudoProfilingSpanProcessor)[];
|
|
7
8
|
export declare function _logsProcessorConfig(config: NodeSDKConfig): (EnrichLogProcessor | NextJsLogProcessor)[];
|
|
@@ -2,12 +2,17 @@ import { EnrichLogProcessor } from "../processor/enrich-logger-processor.js";
|
|
|
2
2
|
import { EnrichSpanProcessor } from "../processor/enrich-span-processor.js";
|
|
3
3
|
import { NextJsLogProcessor } from "../processor/nextjs-logger-processor.js";
|
|
4
4
|
import { NextJsSpanProcessor } from "../processor/nextjs-span-processor.js";
|
|
5
|
+
import { PseudoProfilingSpanProcessor } from "../processor/pseudo-profiling-span-processor.js";
|
|
5
6
|
export function _spansProcessorConfig(config) {
|
|
6
7
|
const _processor = [];
|
|
7
8
|
_processor.push(new NextJsSpanProcessor());
|
|
8
9
|
if (config.spanAttributes) {
|
|
9
10
|
_processor.push(new EnrichSpanProcessor(config.spanAttributes));
|
|
10
11
|
}
|
|
12
|
+
if (config.pseudoProfiling?.enabled) {
|
|
13
|
+
// Pseudo-profiling span processor should always be the last one in the chain
|
|
14
|
+
_processor.push(new PseudoProfilingSpanProcessor());
|
|
15
|
+
}
|
|
11
16
|
return _processor;
|
|
12
17
|
}
|
|
13
18
|
export function _logsProcessorConfig(config) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Metadata } from "@grpc/grpc-js";
|
|
2
2
|
import { BasicRedactor } from "../../sdk-core/lib/redaction/basic-redactor.js";
|
|
3
|
+
import { InstrumentationConfigMap } from "@opentelemetry/auto-instrumentations-node";
|
|
4
|
+
import { Instrumentation } from "@opentelemetry/instrumentation";
|
|
3
5
|
export interface NodeSDKConfig {
|
|
4
6
|
/**
|
|
5
7
|
* The opentelemetry collector entrypoint GRPC url.
|
|
@@ -52,12 +54,31 @@ export interface NodeSDKConfig {
|
|
|
52
54
|
* @default 1
|
|
53
55
|
*/
|
|
54
56
|
traceRatio?: number;
|
|
57
|
+
metrics?: {
|
|
58
|
+
/**
|
|
59
|
+
* Delay in milliseconds for the metric reader to initiate metric collection.
|
|
60
|
+
*
|
|
61
|
+
* @default 60_000
|
|
62
|
+
*/
|
|
63
|
+
exportIntervalMs?: number;
|
|
64
|
+
};
|
|
55
65
|
/**
|
|
56
66
|
* Flag to enable or disable the tracing for node:fs module
|
|
57
67
|
*
|
|
58
68
|
* @default false disabling `instrumentation-fs` because it bloating the traces
|
|
69
|
+
* @deprecated This option will be removed in a future version and is currently and replaced by the autoInstrumentationConfig option.
|
|
59
70
|
*/
|
|
60
71
|
enableFS?: boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Configuration object for auto instrumentations.
|
|
74
|
+
* @default {"@opentelemetry/instrumentation-fs":{enabled:false}}
|
|
75
|
+
*/
|
|
76
|
+
autoInstrumentationConfig?: InstrumentationConfigMap;
|
|
77
|
+
/**
|
|
78
|
+
* Additional custom instrumentations to be added to the NodeSDK
|
|
79
|
+
* @default []
|
|
80
|
+
*/
|
|
81
|
+
additionalInstrumentations?: Instrumentation[];
|
|
61
82
|
/**
|
|
62
83
|
* Protocol used to send signals.
|
|
63
84
|
*
|
|
@@ -95,6 +116,17 @@ export interface NodeSDKConfig {
|
|
|
95
116
|
*/
|
|
96
117
|
custom?: BasicRedactor[];
|
|
97
118
|
};
|
|
119
|
+
/**
|
|
120
|
+
* Configuration for the pseudo-profiling span processor
|
|
121
|
+
* @default {enabled:false}
|
|
122
|
+
*/
|
|
123
|
+
pseudoProfiling?: {
|
|
124
|
+
/**
|
|
125
|
+
* Enable/Disable pseudo-profiling span processor
|
|
126
|
+
* @default false
|
|
127
|
+
*/
|
|
128
|
+
enabled: boolean;
|
|
129
|
+
};
|
|
98
130
|
}
|
|
99
131
|
export interface SamplerCondition {
|
|
100
132
|
type: "endsWith" | "includes" | "equals";
|
|
@@ -35,11 +35,6 @@ export default async function buildNodeInstrumentation(config) {
|
|
|
35
35
|
});
|
|
36
36
|
diag.setLogger(new DiagConsoleLogger(), config.diagLogLevel ? DiagLogLevel[config.diagLogLevel] : DiagLogLevel.INFO);
|
|
37
37
|
try {
|
|
38
|
-
const nodeSdkInstrumentation = getNodeAutoInstrumentations({
|
|
39
|
-
"@opentelemetry/instrumentation-fs": {
|
|
40
|
-
enabled: config.enableFS ?? false,
|
|
41
|
-
},
|
|
42
|
-
});
|
|
43
38
|
let exporter;
|
|
44
39
|
if (config.protocol === "http") {
|
|
45
40
|
exporter = buildHttpExporters(config);
|
|
@@ -60,7 +55,14 @@ export default async function buildNodeInstrumentation(config) {
|
|
|
60
55
|
logRecordProcessors: exporter.logs,
|
|
61
56
|
sampler: mainSampler,
|
|
62
57
|
textMapPropagator: new W3CTraceContextPropagator(),
|
|
63
|
-
instrumentations: [
|
|
58
|
+
instrumentations: [
|
|
59
|
+
getNodeAutoInstrumentations(config.autoInstrumentationConfig ?? {
|
|
60
|
+
"@opentelemetry/instrumentation-fs": {
|
|
61
|
+
enabled: config.enableFS ?? false, // TODO: remove enableFS in future versions
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
...(config.additionalInstrumentations ?? []),
|
|
65
|
+
],
|
|
64
66
|
});
|
|
65
67
|
sdk.start();
|
|
66
68
|
console.log("NodeJS OpenTelemetry instrumentation started successfully.");
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { Context } from "@opentelemetry/api";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
onEnd(_span: ReadableSpan): void;
|
|
7
|
-
shutdown(): Promise<void>;
|
|
2
|
+
import { Span } from "@opentelemetry/sdk-trace-base";
|
|
3
|
+
import { OnEndingHookSpanProcessor } from "./on-ending-hook-span-processor.js";
|
|
4
|
+
export declare class NextJsSpanProcessor extends OnEndingHookSpanProcessor {
|
|
5
|
+
onEnding(span: Span, _context: Context): void;
|
|
8
6
|
}
|
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
import { SpanStatusCode } from "@opentelemetry/api";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import { OnEndingHookSpanProcessor } from "./on-ending-hook-span-processor.js";
|
|
3
|
+
export class NextJsSpanProcessor extends OnEndingHookSpanProcessor {
|
|
4
|
+
onEnding(span, _context) {
|
|
5
|
+
// ref. https://grafana.com/docs/grafana-cloud/monitor-applications/frontend-observability/quickstart/nextjs/#instrument-the-nextjs-backend
|
|
6
|
+
if (span.name.startsWith("GET /_next/static")) {
|
|
7
|
+
span.updateName("GET /_next/static");
|
|
8
|
+
}
|
|
9
|
+
else if (span.name.startsWith("GET /_next/data")) {
|
|
10
|
+
span.updateName("GET /_next/data");
|
|
11
|
+
}
|
|
7
12
|
if (span.status.code !== SpanStatusCode.ERROR)
|
|
8
13
|
return;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
const events = span.events;
|
|
15
|
+
let cleaned = 0;
|
|
16
|
+
let exceptions = 0;
|
|
17
|
+
for (let i = 0; i < events.length; i++) {
|
|
18
|
+
// Required only for NexJS v14
|
|
19
|
+
if (events[i].name == "exception") {
|
|
20
|
+
exceptions++;
|
|
21
|
+
if (typeof events[i].attributes?.["exception.message"] === "string" &&
|
|
22
|
+
events[i].attributes?.["exception.message"] === "NEXT_REDIRECT") {
|
|
23
|
+
events.splice(i, 1);
|
|
24
|
+
cleaned++;
|
|
25
|
+
i--;
|
|
26
|
+
}
|
|
16
27
|
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
}
|
|
29
|
+
const status = {
|
|
30
|
+
...span.status,
|
|
31
|
+
code: cleaned === exceptions ? SpanStatusCode.UNSET : span.status.code,
|
|
32
|
+
};
|
|
33
|
+
if (cleaned) {
|
|
34
|
+
span.setAttribute("o11y.sdk.nextjs.lifecycle.redirect.cleaned", true);
|
|
35
|
+
}
|
|
36
|
+
span.setStatus(status);
|
|
37
|
+
Object.assign(span, {
|
|
38
|
+
events,
|
|
39
|
+
});
|
|
24
40
|
}
|
|
25
41
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Context } from "@opentelemetry/api";
|
|
2
|
+
import { ReadableSpan, Span, SpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
3
|
+
/**
|
|
4
|
+
* A SpanProcessor that provides a hook to execute custom logic
|
|
5
|
+
* just before a span is ended.
|
|
6
|
+
*
|
|
7
|
+
* This allows for capturing or modifying span data at the precise moment
|
|
8
|
+
* the span is about to be finished, while it is still writable.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* Extend this class and implement the onEnding method with custom logic.
|
|
12
|
+
* Override the onStart method if needed to set up any required state.
|
|
13
|
+
* The onEnding method will be called just before the span's end() method
|
|
14
|
+
* is invoked.
|
|
15
|
+
*
|
|
16
|
+
* Example:
|
|
17
|
+
* ```typescript
|
|
18
|
+
* class CustomSpanProcessor extends OnEndingHookSpanProcessor {
|
|
19
|
+
* onEnding(span: Span, context: Context): void {
|
|
20
|
+
* // Custom logic to execute before the span ends
|
|
21
|
+
* span.setAttribute("custom.attribute", "value");
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class OnEndingHookSpanProcessor implements SpanProcessor {
|
|
27
|
+
/**
|
|
28
|
+
* Hook method called just before a span is ended.
|
|
29
|
+
* @param span The span that is about to end.
|
|
30
|
+
* @param context The context in which the span is ending.
|
|
31
|
+
*/
|
|
32
|
+
abstract onEnding(span: Span, context: Context): void;
|
|
33
|
+
onStart(span: Span, _context: Context): void;
|
|
34
|
+
onEnd(_span: ReadableSpan): void;
|
|
35
|
+
forceFlush(): Promise<void>;
|
|
36
|
+
shutdown(): Promise<void>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A SpanProcessor that provides a hook to execute custom logic
|
|
3
|
+
* just before a span is ended.
|
|
4
|
+
*
|
|
5
|
+
* This allows for capturing or modifying span data at the precise moment
|
|
6
|
+
* the span is about to be finished, while it is still writable.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* Extend this class and implement the onEnding method with custom logic.
|
|
10
|
+
* Override the onStart method if needed to set up any required state.
|
|
11
|
+
* The onEnding method will be called just before the span's end() method
|
|
12
|
+
* is invoked.
|
|
13
|
+
*
|
|
14
|
+
* Example:
|
|
15
|
+
* ```typescript
|
|
16
|
+
* class CustomSpanProcessor extends OnEndingHookSpanProcessor {
|
|
17
|
+
* onEnding(span: Span, context: Context): void {
|
|
18
|
+
* // Custom logic to execute before the span ends
|
|
19
|
+
* span.setAttribute("custom.attribute", "value");
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class OnEndingHookSpanProcessor {
|
|
25
|
+
onStart(span, _context) {
|
|
26
|
+
// Store the original end method
|
|
27
|
+
const originalEnd = span.end.bind(span);
|
|
28
|
+
// Wrap the end method to inject custom logic before ending the span
|
|
29
|
+
span.end = (endTime) => {
|
|
30
|
+
this.onEnding(span, _context);
|
|
31
|
+
originalEnd(endTime);
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
onEnd(_span) { }
|
|
35
|
+
forceFlush() {
|
|
36
|
+
return Promise.resolve();
|
|
37
|
+
}
|
|
38
|
+
shutdown() {
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Context } from "@opentelemetry/api";
|
|
2
|
+
import type { Span } from "@opentelemetry/sdk-trace-base";
|
|
3
|
+
import { OnEndingHookSpanProcessor } from "./on-ending-hook-span-processor.js";
|
|
4
|
+
/**
|
|
5
|
+
* A SpanProcessor that captures pseudo-profiling data (CPU time and memory usage)
|
|
6
|
+
* for each span and adds them as span attributes.
|
|
7
|
+
*
|
|
8
|
+
* This processor wraps the span's end() method to capture profiling metrics
|
|
9
|
+
* just before the span is marked as ended, while the span is still writable.
|
|
10
|
+
*/
|
|
11
|
+
export declare class PseudoProfilingSpanProcessor extends OnEndingHookSpanProcessor {
|
|
12
|
+
private _spanProfilingData;
|
|
13
|
+
onStart(span: Span, _context: Context): void;
|
|
14
|
+
onEnding(span: Span, _context: Context): void;
|
|
15
|
+
/**
|
|
16
|
+
* Captures profiling data and sets attributes on the span.
|
|
17
|
+
* Called just before the span is ended, while it's still writable.
|
|
18
|
+
*/
|
|
19
|
+
private _captureProfilingData;
|
|
20
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { OnEndingHookSpanProcessor } from "./on-ending-hook-span-processor.js";
|
|
2
|
+
const PROFILING_ATTRIBUTE_PREFIX = "profiling";
|
|
3
|
+
/**
|
|
4
|
+
* A SpanProcessor that captures pseudo-profiling data (CPU time and memory usage)
|
|
5
|
+
* for each span and adds them as span attributes.
|
|
6
|
+
*
|
|
7
|
+
* This processor wraps the span's end() method to capture profiling metrics
|
|
8
|
+
* just before the span is marked as ended, while the span is still writable.
|
|
9
|
+
*/
|
|
10
|
+
export class PseudoProfilingSpanProcessor extends OnEndingHookSpanProcessor {
|
|
11
|
+
_spanProfilingData = new WeakMap();
|
|
12
|
+
onStart(span, _context) {
|
|
13
|
+
super.onStart(span, _context);
|
|
14
|
+
const cpuUsageStart = process.cpuUsage();
|
|
15
|
+
const memoryUsageStart = process.memoryUsage();
|
|
16
|
+
// Store profiling data
|
|
17
|
+
this._spanProfilingData.set(span, {
|
|
18
|
+
cpuUsageStart,
|
|
19
|
+
memoryUsageStart,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
onEnding(span, _context) {
|
|
23
|
+
this._captureProfilingData(span);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Captures profiling data and sets attributes on the span.
|
|
27
|
+
* Called just before the span is ended, while it's still writable.
|
|
28
|
+
*/
|
|
29
|
+
_captureProfilingData(span) {
|
|
30
|
+
const profilingData = this._spanProfilingData.get(span);
|
|
31
|
+
if (!profilingData) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const { cpuUsageStart, memoryUsageStart } = profilingData;
|
|
35
|
+
const cpuUsageEnd = process.cpuUsage(cpuUsageStart);
|
|
36
|
+
const memoryUsageEnd = process.memoryUsage();
|
|
37
|
+
const memoryDelta = {
|
|
38
|
+
heapUsed: memoryUsageEnd.heapUsed - memoryUsageStart.heapUsed,
|
|
39
|
+
heapTotal: memoryUsageEnd.heapTotal - memoryUsageStart.heapTotal,
|
|
40
|
+
external: memoryUsageEnd.external - memoryUsageStart.external,
|
|
41
|
+
rss: memoryUsageEnd.rss - memoryUsageStart.rss,
|
|
42
|
+
arrayBuffers: memoryUsageEnd.arrayBuffers - memoryUsageStart.arrayBuffers,
|
|
43
|
+
};
|
|
44
|
+
// CPU time in microseconds
|
|
45
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.cpu.user_time_us`, cpuUsageEnd.user);
|
|
46
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.cpu.system_time_us`, cpuUsageEnd.system);
|
|
47
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.cpu.total_time_us`, cpuUsageEnd.user + cpuUsageEnd.system);
|
|
48
|
+
// Memory delta in bytes
|
|
49
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.memory.heap_used_delta_bytes`, memoryDelta.heapUsed);
|
|
50
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.memory.heap_total_delta_bytes`, memoryDelta.heapTotal);
|
|
51
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.memory.external_delta_bytes`, memoryDelta.external);
|
|
52
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.memory.rss_delta_bytes`, memoryDelta.rss);
|
|
53
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.memory.array_buffers_delta_bytes`, memoryDelta.arrayBuffers);
|
|
54
|
+
// Absolute memory values at span end (useful for overall monitoring)
|
|
55
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.memory.heap_used_bytes`, memoryUsageEnd.heapUsed);
|
|
56
|
+
span.setAttribute(`${PROFILING_ATTRIBUTE_PREFIX}.memory.heap_total_bytes`, memoryUsageEnd.heapTotal);
|
|
57
|
+
// Clean up the WeakMap entry
|
|
58
|
+
this._spanProfilingData.delete(span);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -7,7 +7,9 @@ export class UrlSampler {
|
|
|
7
7
|
this._nextSampler = nextSampler;
|
|
8
8
|
}
|
|
9
9
|
shouldSample(_context, traceId, _spanName, _spanKind, attributes, _links) {
|
|
10
|
-
|
|
10
|
+
// Check both http.target and url.path (@fastify/otel) attributes
|
|
11
|
+
const url = attributes["http.target"]?.toString() ||
|
|
12
|
+
attributes["url.path"]?.toString();
|
|
11
13
|
if (url) {
|
|
12
14
|
for (const condition of this._samplerCondition) {
|
|
13
15
|
if ((condition.type === "equals" && url === condition.url) ||
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogcio/o11y-sdk-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
"test": "vitest",
|
|
10
10
|
"prepublishOnly": "pnpm i && rm -rf dist && tsc -p tsconfig.json",
|
|
11
11
|
"test:unit": "vitest --project unit",
|
|
12
|
-
"test:integration": "pnpm
|
|
13
|
-
"test:integration:
|
|
12
|
+
"test:integration": "pnpm test:integration:setup && pnpm test:integration:run && pnpm test:integration:assert && pnpm test:integration:teardown",
|
|
13
|
+
"test:integration:setup": "sh ./test/integration/setup.sh integration",
|
|
14
|
+
"test:integration:run": "pnpm --filter @ogcio/o11y run prepare:integration",
|
|
15
|
+
"test:integration:assert": "vitest --project integration",
|
|
16
|
+
"test:integration:teardown": "sh ./test/integration/teardown.sh integration"
|
|
14
17
|
},
|
|
15
18
|
"exports": {
|
|
16
19
|
".": "./dist/sdk-node/index.js",
|
|
@@ -30,10 +33,10 @@
|
|
|
30
33
|
"author": "team:ogcio/observability",
|
|
31
34
|
"license": "ISC",
|
|
32
35
|
"dependencies": {
|
|
33
|
-
"@grpc/grpc-js": "1.14.
|
|
36
|
+
"@grpc/grpc-js": "1.14.3",
|
|
34
37
|
"@opentelemetry/api": "1.9.0",
|
|
35
38
|
"@opentelemetry/api-logs": "0.208.0",
|
|
36
|
-
"@opentelemetry/auto-instrumentations-node": "0.67.
|
|
39
|
+
"@opentelemetry/auto-instrumentations-node": "0.67.2",
|
|
37
40
|
"@opentelemetry/core": "2.2.0",
|
|
38
41
|
"@opentelemetry/exporter-logs-otlp-grpc": "0.208.0",
|
|
39
42
|
"@opentelemetry/exporter-logs-otlp-http": "0.208.0",
|
|
@@ -50,11 +53,11 @@
|
|
|
50
53
|
"@opentelemetry/sdk-trace-base": "2.2.0"
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
53
|
-
"@types/node": "
|
|
54
|
-
"@vitest/coverage-v8": "4.0.
|
|
55
|
-
"tsx": "4.
|
|
56
|
+
"@types/node": "25.0.2",
|
|
57
|
+
"@vitest/coverage-v8": "4.0.15",
|
|
58
|
+
"tsx": "4.21.0",
|
|
56
59
|
"typescript": "5.9.3",
|
|
57
|
-
"vitest": "4.0.
|
|
60
|
+
"vitest": "4.0.15"
|
|
58
61
|
},
|
|
59
62
|
"engines": {
|
|
60
63
|
"node": ">=20.6.0"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogcio/o11y-sdk-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Opentelemetry standard instrumentation SDK for NodeJS based project",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"author": "team:ogcio/observability",
|
|
23
23
|
"license": "ISC",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@grpc/grpc-js": "1.14.
|
|
25
|
+
"@grpc/grpc-js": "1.14.3",
|
|
26
26
|
"@opentelemetry/api": "1.9.0",
|
|
27
27
|
"@opentelemetry/api-logs": "0.208.0",
|
|
28
|
-
"@opentelemetry/auto-instrumentations-node": "0.67.
|
|
28
|
+
"@opentelemetry/auto-instrumentations-node": "0.67.2",
|
|
29
29
|
"@opentelemetry/core": "2.2.0",
|
|
30
30
|
"@opentelemetry/exporter-logs-otlp-grpc": "0.208.0",
|
|
31
31
|
"@opentelemetry/exporter-logs-otlp-http": "0.208.0",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"@opentelemetry/sdk-trace-base": "2.2.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@types/node": "
|
|
46
|
-
"@vitest/coverage-v8": "4.0.
|
|
47
|
-
"tsx": "4.
|
|
45
|
+
"@types/node": "25.0.2",
|
|
46
|
+
"@vitest/coverage-v8": "4.0.15",
|
|
47
|
+
"tsx": "4.21.0",
|
|
48
48
|
"typescript": "5.9.3",
|
|
49
|
-
"vitest": "4.0.
|
|
49
|
+
"vitest": "4.0.15"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
52
52
|
"node": ">=20.6.0"
|
|
@@ -55,7 +55,10 @@
|
|
|
55
55
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
56
56
|
"test": "vitest",
|
|
57
57
|
"test:unit": "vitest --project unit",
|
|
58
|
-
"test:integration": "pnpm
|
|
59
|
-
"test:integration:
|
|
58
|
+
"test:integration": "pnpm test:integration:setup && pnpm test:integration:run && pnpm test:integration:assert && pnpm test:integration:teardown",
|
|
59
|
+
"test:integration:setup": "sh ./test/integration/setup.sh integration",
|
|
60
|
+
"test:integration:run": "pnpm --filter @ogcio/o11y run prepare:integration",
|
|
61
|
+
"test:integration:assert": "vitest --project integration",
|
|
62
|
+
"test:integration:teardown": "sh ./test/integration/teardown.sh integration"
|
|
60
63
|
}
|
|
61
64
|
}
|