@devopsplaybook.io/otel-utils 1.3.0-beta.28.b481842 → 1.3.0-beta.30.5138047

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 CHANGED
@@ -48,10 +48,8 @@ span.end();
48
48
 
49
49
  **Methods:**
50
50
 
51
- - `startSpan(name, parentSpan?)` — Creates a span. When no `parentSpan` is provided, adds `http.request_method=BACKEND` and `http.route` attributes. Span names are sanitized to `[a-zA-Z0-9-_/]`.
51
+ - `startSpan(name, parentSpan?, options?)` — Creates a span. Span names are sanitized to `[a-zA-Z0-9-_/]`. When neither `parentSpan` nor `options` is given, the span is `SpanKind.INTERNAL` and carries `http.request_method=BACKEND` and a synthetic `http.route` attribute. Passing `options` (e.g. `{ kind: SpanKind.SERVER }`) forwards them to the OpenTelemetry SDK instead and skips those synthetic attributes, because the caller then describes the span itself.
52
52
  - `static updateHttpHeader(context, headers?)` — Injects W3C trace context into an HTTP headers object for propagation to downstream services.
53
- - `forceFlush()` — Flushes pending spans to the exporter.
54
- - `shutdown()` — Shuts down the tracer provider and flushes pending spans (call once, on process exit).
55
53
 
56
54
  ### StandardLogger
57
55
 
@@ -72,8 +70,6 @@ moduleLog.info("Hello world");
72
70
  - `initOTel(config)` — Initializes the logger provider and optional OTLP exporter.
73
71
  - `getLogger()` — Returns the underlying OTel `Logger` or `undefined`.
74
72
  - `createModuleLogger(moduleName)` — Creates a `ModuleLogger` scoped to a module name.
75
- - `forceFlush()` — Flushes pending log records to the exporter (no-op when the provider was never initialized).
76
- - `shutdown()` — Shuts down the logger provider and flushes pending log records; no-op when the provider was never initialized (call once, on process exit).
77
73
 
78
74
  ### ModuleLogger
79
75
 
@@ -126,8 +122,6 @@ const cpuGauge = meter.createObservableGauge(
126
122
  - `createUpDownCounter(key)` — Creates an `UpDownCounter`.
127
123
  - `createHistogram(key)` — Creates a `Histogram`.
128
124
  - `createObservableGauge(key, callback, description?)` — Creates an `ObservableGauge` with a callback. Description is optional.
129
- - `forceFlush()` — Flushes pending metric data points to the exporter.
130
- - `shutdown()` — Shuts down the meter provider and flushes pending metric data points (call once, on process exit).
131
125
 
132
126
  ## Internal Utilities
133
127
 
@@ -4,12 +4,9 @@ import { ModuleLogger } from "./ModuleLogger";
4
4
  import type { StandardLoggerInterface } from "./models/StandardLoggerInterface";
5
5
  export declare class StandardLogger implements StandardLoggerInterface {
6
6
  private logger?;
7
- private loggerProvider?;
8
7
  private serviceVersion?;
9
8
  private serviceName?;
10
9
  initOTel(config: ConfigOTelInterface): void;
11
- forceFlush(): Promise<void>;
12
- shutdown(): Promise<void>;
13
10
  getLogger(): OTelLogger | undefined;
14
11
  createModuleLogger(moduleName: string): ModuleLogger;
15
12
  }
@@ -30,20 +30,9 @@ class StandardLogger {
30
30
  ],
31
31
  resource: (0, createResource_1.createOTelResource)(this.serviceName, this.serviceVersion),
32
32
  });
33
- this.loggerProvider = loggerProvider;
34
33
  this.logger = loggerProvider.getLogger(`${this.serviceName}:${this.serviceVersion}`);
35
34
  }
36
35
  }
37
- forceFlush() {
38
- return this.loggerProvider
39
- ? this.loggerProvider.forceFlush()
40
- : Promise.resolve();
41
- }
42
- shutdown() {
43
- return this.loggerProvider
44
- ? this.loggerProvider.shutdown()
45
- : Promise.resolve();
46
- }
47
36
  getLogger() {
48
37
  return this.logger;
49
38
  }
@@ -2,12 +2,9 @@ import { Counter, Histogram, ObservableGauge, UpDownCounter } from "@opentelemet
2
2
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
3
3
  export declare class StandardMeter {
4
4
  private meter;
5
- private meterProvider;
6
5
  private serviceVersion;
7
6
  private serviceName;
8
7
  constructor(config: ConfigOTelInterface);
9
- forceFlush(): Promise<void>;
10
- shutdown(): Promise<void>;
11
8
  createCounter(key: string): Counter;
12
9
  createUpDownCounter(key: string): UpDownCounter;
13
10
  createHistogram(key: string): Histogram;
@@ -36,15 +36,8 @@ class StandardMeter {
36
36
  resource: (0, createResource_1.createOTelResource)(this.serviceName, this.serviceVersion),
37
37
  });
38
38
  }
39
- this.meterProvider = meterProvider;
40
39
  this.meter = meterProvider.getMeter(`${this.serviceName}:${this.serviceVersion}`);
41
40
  }
42
- forceFlush() {
43
- return this.meterProvider.forceFlush();
44
- }
45
- shutdown() {
46
- return this.meterProvider.shutdown();
47
- }
48
41
  createCounter(key) {
49
42
  return this.meter.createCounter(`${this.serviceName}.${key}`);
50
43
  }
@@ -1,15 +1,26 @@
1
+ import { SpanKind } from "@opentelemetry/api";
1
2
  import { Span } from "@opentelemetry/sdk-trace-base";
2
3
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
4
+ /**
5
+ * Options accepted by {@link StandardTracer.startSpan}.
6
+ */
7
+ export interface StandardTracerStartSpanOptions {
8
+ /**
9
+ * OpenTelemetry span kind (default `SpanKind.INTERNAL`).
10
+ *
11
+ * Providing options means the caller describes the span itself: the
12
+ * parentless default attributes (`http.request.method=BACKEND` and the
13
+ * synthetic `http.route`) are not added.
14
+ */
15
+ kind?: SpanKind;
16
+ }
3
17
  export declare class StandardTracer {
4
18
  private static readonly SPAN_NAME_SANITIZE_RE;
5
19
  private static readonly PROPAGATOR;
6
20
  private tracer;
7
- private traceProvider;
8
21
  private serviceVersion;
9
22
  private serviceName;
10
23
  constructor(config: ConfigOTelInterface);
11
- forceFlush(): Promise<void>;
12
- shutdown(): Promise<void>;
13
- startSpan(name: string, parentSpan?: Span): Span;
24
+ startSpan(name: string, parentSpan?: Span, options?: StandardTracerStartSpanOptions): Span;
14
25
  static updateHttpHeader(context: Span, headers?: {}): any;
15
26
  }
@@ -66,22 +66,18 @@ class StandardTracer {
66
66
  spanProcessors,
67
67
  });
68
68
  traceProvider.register();
69
- this.traceProvider = traceProvider;
70
69
  const contextManager = new context_async_hooks_1.AsyncHooksContextManager();
71
70
  contextManager.enable();
72
71
  api_1.default.context.setGlobalContextManager(contextManager);
73
72
  this.tracer = api_1.default.trace.getTracer(`${this.serviceName}:${this.serviceVersion}`);
74
73
  }
75
- forceFlush() {
76
- return this.traceProvider.forceFlush();
77
- }
78
- shutdown() {
79
- return this.traceProvider.shutdown();
80
- }
81
- startSpan(name, parentSpan) {
74
+ startSpan(name, parentSpan, options) {
82
75
  const sanitizedName = String(name).replace(StandardTracer.SPAN_NAME_SANITIZE_RE, "_");
83
76
  if (parentSpan) {
84
- return this.tracer.startSpan(sanitizedName, undefined, api_1.default.trace.setSpan(api_1.default.context.active(), parentSpan));
77
+ return this.tracer.startSpan(sanitizedName, options, api_1.default.trace.setSpan(api_1.default.context.active(), parentSpan));
78
+ }
79
+ if (options) {
80
+ return this.tracer.startSpan(sanitizedName, options);
85
81
  }
86
82
  const span = this.tracer.startSpan(sanitizedName);
87
83
  span.setAttribute(semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD, `BACKEND`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devopsplaybook.io/otel-utils",
3
- "version": "1.3.0-beta.28.b481842",
3
+ "version": "1.3.0-beta.30.5138047",
4
4
  "description": "Utility to simplify integration with Open Telemetry",
5
5
  "keywords": [
6
6
  "Open",
@@ -46,7 +46,7 @@
46
46
  "@types/node": "^26.6.2",
47
47
  "@types/sqlite3": "^5.1.0",
48
48
  "jest": "^30.5.2",
49
- "oxlint": "^1.83.0",
49
+ "oxlint": "^1.85.0",
50
50
  "typescript": "^7.0.2"
51
51
  },
52
52
  "publishConfig": {
@@ -12,7 +12,6 @@ import type { StandardLoggerInterface } from "./models/StandardLoggerInterface";
12
12
 
13
13
  export class StandardLogger implements StandardLoggerInterface {
14
14
  private logger?: OTelLogger;
15
- private loggerProvider?: LoggerProvider;
16
15
  private serviceVersion?: string;
17
16
  private serviceName?: string;
18
17
 
@@ -44,25 +43,12 @@ export class StandardLogger implements StandardLoggerInterface {
44
43
  resource: createOTelResource(this.serviceName, this.serviceVersion),
45
44
  });
46
45
 
47
- this.loggerProvider = loggerProvider;
48
46
  this.logger = loggerProvider.getLogger(
49
47
  `${this.serviceName}:${this.serviceVersion}`,
50
48
  );
51
49
  }
52
50
  }
53
51
 
54
- public forceFlush(): Promise<void> {
55
- return this.loggerProvider
56
- ? this.loggerProvider.forceFlush()
57
- : Promise.resolve();
58
- }
59
-
60
- public shutdown(): Promise<void> {
61
- return this.loggerProvider
62
- ? this.loggerProvider.shutdown()
63
- : Promise.resolve();
64
- }
65
-
66
52
  public getLogger(): OTelLogger | undefined {
67
53
  return this.logger;
68
54
  }
@@ -15,7 +15,6 @@ import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
15
15
 
16
16
  export class StandardMeter {
17
17
  private meter: Meter;
18
- private meterProvider: MeterProvider;
19
18
  private serviceVersion: string;
20
19
  private serviceName: string;
21
20
 
@@ -50,20 +49,11 @@ export class StandardMeter {
50
49
  resource: createOTelResource(this.serviceName, this.serviceVersion),
51
50
  });
52
51
  }
53
- this.meterProvider = meterProvider;
54
52
  this.meter = meterProvider.getMeter(
55
53
  `${this.serviceName}:${this.serviceVersion}`,
56
54
  );
57
55
  }
58
56
 
59
- public forceFlush(): Promise<void> {
60
- return this.meterProvider.forceFlush();
61
- }
62
-
63
- public shutdown(): Promise<void> {
64
- return this.meterProvider.shutdown();
65
- }
66
-
67
57
  public createCounter(key: string): Counter {
68
58
  return this.meter.createCounter(`${this.serviceName}.${key}`);
69
59
  }
@@ -0,0 +1,68 @@
1
+ import { SpanKind } from "@opentelemetry/api";
2
+ import {
3
+ ATTR_HTTP_REQUEST_METHOD,
4
+ ATTR_HTTP_ROUTE,
5
+ } from "@opentelemetry/semantic-conventions";
6
+ import { StandardTracer } from "./StandardTracer";
7
+
8
+ describe("StandardTracer", () => {
9
+ let tracer: StandardTracer;
10
+
11
+ beforeAll(() => {
12
+ tracer = new StandardTracer({
13
+ SERVICE_ID: "test-service",
14
+ VERSION: "1.0.0",
15
+ });
16
+ });
17
+
18
+ describe("startSpan", () => {
19
+ it("sanitizes span names", () => {
20
+ const span = tracer.startSpan("GET-/api/files/:id");
21
+ expect(span.name).toBe("GET-/api/files/_id");
22
+ span.end();
23
+ });
24
+
25
+ it("keeps the default parentless behavior when no options are given", () => {
26
+ const span = tracer.startSpan("my-operation");
27
+ expect(span.kind).toBe(SpanKind.INTERNAL);
28
+ expect(span.attributes[ATTR_HTTP_REQUEST_METHOD]).toBe("BACKEND");
29
+ expect(span.attributes[ATTR_HTTP_ROUTE]).toBe(
30
+ "test-service-1.0.0-my-operation",
31
+ );
32
+ span.end();
33
+ });
34
+
35
+ it("creates a child of the parent span without synthetic attributes", () => {
36
+ const parent = tracer.startSpan("parent-operation");
37
+ const child = tracer.startSpan("child-operation", parent);
38
+ expect(child.spanContext().traceId).toBe(parent.spanContext().traceId);
39
+ expect(child.parentSpanContext?.spanId).toBe(parent.spanContext().spanId);
40
+ expect(child.attributes[ATTR_HTTP_REQUEST_METHOD]).toBeUndefined();
41
+ expect(child.attributes[ATTR_HTTP_ROUTE]).toBeUndefined();
42
+ child.end();
43
+ parent.end();
44
+ });
45
+
46
+ it("applies the options instead of the synthetic attributes", () => {
47
+ const span = tracer.startSpan("GET-/api/files/:id", undefined, {
48
+ kind: SpanKind.SERVER,
49
+ });
50
+ expect(span.kind).toBe(SpanKind.SERVER);
51
+ expect(span.attributes[ATTR_HTTP_REQUEST_METHOD]).toBeUndefined();
52
+ expect(span.attributes[ATTR_HTTP_ROUTE]).toBeUndefined();
53
+ span.end();
54
+ });
55
+
56
+ it("combines the options with a parent span", () => {
57
+ const parent = tracer.startSpan("parent-operation");
58
+ const child = tracer.startSpan("child-operation", parent, {
59
+ kind: SpanKind.CLIENT,
60
+ });
61
+ expect(child.kind).toBe(SpanKind.CLIENT);
62
+ expect(child.spanContext().traceId).toBe(parent.spanContext().traceId);
63
+ expect(child.parentSpanContext?.spanId).toBe(parent.spanContext().spanId);
64
+ child.end();
65
+ parent.end();
66
+ });
67
+ });
68
+ });
@@ -1,6 +1,7 @@
1
1
  import opentelemetry, {
2
2
  defaultTextMapSetter,
3
3
  ROOT_CONTEXT,
4
+ SpanKind,
4
5
  trace,
5
6
  Tracer,
6
7
  } from "@opentelemetry/api";
@@ -17,12 +18,25 @@ import {
17
18
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
18
19
  import { createOTelResource } from "./utils/createResource";
19
20
 
21
+ /**
22
+ * Options accepted by {@link StandardTracer.startSpan}.
23
+ */
24
+ export interface StandardTracerStartSpanOptions {
25
+ /**
26
+ * OpenTelemetry span kind (default `SpanKind.INTERNAL`).
27
+ *
28
+ * Providing options means the caller describes the span itself: the
29
+ * parentless default attributes (`http.request.method=BACKEND` and the
30
+ * synthetic `http.route`) are not added.
31
+ */
32
+ kind?: SpanKind;
33
+ }
34
+
20
35
  export class StandardTracer {
21
36
  private static readonly SPAN_NAME_SANITIZE_RE = /[^a-zA-Z0-9-_/]/g;
22
37
  private static readonly PROPAGATOR = new W3CTraceContextPropagator();
23
38
 
24
39
  private tracer: Tracer;
25
- private traceProvider: NodeTracerProvider;
26
40
  private serviceVersion: string;
27
41
  private serviceName: string;
28
42
 
@@ -49,7 +63,6 @@ export class StandardTracer {
49
63
  spanProcessors,
50
64
  });
51
65
  traceProvider.register();
52
- this.traceProvider = traceProvider;
53
66
  const contextManager = new AsyncHooksContextManager();
54
67
  contextManager.enable();
55
68
  opentelemetry.context.setGlobalContextManager(contextManager);
@@ -58,15 +71,11 @@ export class StandardTracer {
58
71
  );
59
72
  }
60
73
 
61
- public forceFlush(): Promise<void> {
62
- return this.traceProvider.forceFlush();
63
- }
64
-
65
- public shutdown(): Promise<void> {
66
- return this.traceProvider.shutdown();
67
- }
68
-
69
- public startSpan(name: string, parentSpan?: Span): Span {
74
+ public startSpan(
75
+ name: string,
76
+ parentSpan?: Span,
77
+ options?: StandardTracerStartSpanOptions,
78
+ ): Span {
70
79
  const sanitizedName = String(name).replace(
71
80
  StandardTracer.SPAN_NAME_SANITIZE_RE,
72
81
  "_",
@@ -74,10 +83,13 @@ export class StandardTracer {
74
83
  if (parentSpan) {
75
84
  return this.tracer.startSpan(
76
85
  sanitizedName,
77
- undefined,
86
+ options,
78
87
  opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan),
79
88
  ) as Span;
80
89
  }
90
+ if (options) {
91
+ return this.tracer.startSpan(sanitizedName, options) as Span;
92
+ }
81
93
  const span = this.tracer.startSpan(sanitizedName) as Span;
82
94
  span.setAttribute(ATTR_HTTP_REQUEST_METHOD, `BACKEND`);
83
95
  span.setAttribute(
@@ -1,43 +0,0 @@
1
- import { StandardLogger } from "./StandardLogger";
2
- import { StandardMeter } from "./StandardMeter";
3
- import { StandardTracer } from "./StandardTracer";
4
- import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
5
-
6
- const config: ConfigOTelInterface = {
7
- SERVICE_ID: "test-service",
8
- VERSION: "1.0.0",
9
- OPENTELEMETRY_COLLECTOR_HTTP_TRACES: "",
10
- OPENTELEMETRY_COLLECTOR_HTTP_METRICS: "",
11
- OPENTELEMETRY_COLLECTOR_HTTP_LOGS: "",
12
- OPENTELEMETRY_COLLECTOR_AWS: false,
13
- OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS: 60,
14
- OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS: 60,
15
- OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER: "",
16
- };
17
-
18
- describe("OTel shutdown and forceFlush", () => {
19
- it("StandardMeter exposes forceFlush and shutdown that resolve", async () => {
20
- const meter = new StandardMeter(config);
21
- await expect(meter.forceFlush()).resolves.toBeUndefined();
22
- await expect(meter.shutdown()).resolves.toBeUndefined();
23
- });
24
-
25
- it("StandardLogger exposes forceFlush and shutdown that resolve without OTel endpoint", async () => {
26
- const logger = new StandardLogger();
27
- await expect(logger.forceFlush()).resolves.toBeUndefined();
28
- await expect(logger.shutdown()).resolves.toBeUndefined();
29
- });
30
-
31
- it("StandardLogger flushes and shuts down the provider when initialized", async () => {
32
- const logger = new StandardLogger();
33
- logger.initOTel(config);
34
- await expect(logger.forceFlush()).resolves.toBeUndefined();
35
- await expect(logger.shutdown()).resolves.toBeUndefined();
36
- });
37
-
38
- it("StandardTracer exposes forceFlush and shutdown that resolve", async () => {
39
- const tracer = new StandardTracer(config);
40
- await expect(tracer.forceFlush()).resolves.toBeUndefined();
41
- await expect(tracer.shutdown()).resolves.toBeUndefined();
42
- });
43
- });