@devopsplaybook.io/otel-utils 1.2.1 → 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,7 +48,7 @@ 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
53
 
54
54
  ### StandardLogger
@@ -1,5 +1,19 @@
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;
@@ -7,6 +21,6 @@ export declare class StandardTracer {
7
21
  private serviceVersion;
8
22
  private serviceName;
9
23
  constructor(config: ConfigOTelInterface);
10
- startSpan(name: string, parentSpan?: Span): Span;
24
+ startSpan(name: string, parentSpan?: Span, options?: StandardTracerStartSpanOptions): Span;
11
25
  static updateHttpHeader(context: Span, headers?: {}): any;
12
26
  }
@@ -71,10 +71,13 @@ class StandardTracer {
71
71
  api_1.default.context.setGlobalContextManager(contextManager);
72
72
  this.tracer = api_1.default.trace.getTracer(`${this.serviceName}:${this.serviceVersion}`);
73
73
  }
74
- startSpan(name, parentSpan) {
74
+ startSpan(name, parentSpan, options) {
75
75
  const sanitizedName = String(name).replace(StandardTracer.SPAN_NAME_SANITIZE_RE, "_");
76
76
  if (parentSpan) {
77
- 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);
78
81
  }
79
82
  const span = this.tracer.startSpan(sanitizedName);
80
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.2.1",
3
+ "version": "1.3.0-beta.30.5138047",
4
4
  "description": "Utility to simplify integration with Open Telemetry",
5
5
  "keywords": [
6
6
  "Open",
@@ -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,6 +18,20 @@ 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();
@@ -56,7 +71,11 @@ export class StandardTracer {
56
71
  );
57
72
  }
58
73
 
59
- public startSpan(name: string, parentSpan?: Span): Span {
74
+ public startSpan(
75
+ name: string,
76
+ parentSpan?: Span,
77
+ options?: StandardTracerStartSpanOptions,
78
+ ): Span {
60
79
  const sanitizedName = String(name).replace(
61
80
  StandardTracer.SPAN_NAME_SANITIZE_RE,
62
81
  "_",
@@ -64,10 +83,13 @@ export class StandardTracer {
64
83
  if (parentSpan) {
65
84
  return this.tracer.startSpan(
66
85
  sanitizedName,
67
- undefined,
86
+ options,
68
87
  opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan),
69
88
  ) as Span;
70
89
  }
90
+ if (options) {
91
+ return this.tracer.startSpan(sanitizedName, options) as Span;
92
+ }
71
93
  const span = this.tracer.startSpan(sanitizedName) as Span;
72
94
  span.setAttribute(ATTR_HTTP_REQUEST_METHOD, `BACKEND`);
73
95
  span.setAttribute(