@devopsplaybook.io/otel-utils 1.0.18 → 1.1.0-beta.21.15fb757

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
@@ -1 +1,165 @@
1
- # otel-utils
1
+ # otel-utils
2
+
3
+ Utility library that simplifies OpenTelemetry integration for Node.js services. Provides standardized tracing, structured logging, and metrics export with minimal boilerplate.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @devopsplaybook.io/otel-utils
9
+ ```
10
+
11
+ ## Configuration
12
+
13
+ All classes accept a `ConfigOTelInterface` object:
14
+
15
+ | Field | Type | Default | Description |
16
+ | --------------------------------------------------------- | ---------- | ------- | ------------------------------- |
17
+ | `SERVICE_ID` | `string` | — | Service identifier (required) |
18
+ | `VERSION` | `string` | — | Service version (required) |
19
+ | `OPENTELEMETRY_COLLECTOR_HTTP_TRACES` | `string?` | — | OTLP HTTP endpoint for traces |
20
+ | `OPENTELEMETRY_COLLECTOR_HTTP_METRICS` | `string?` | — | OTLP HTTP endpoint for metrics |
21
+ | `OPENTELEMETRY_COLLECTOR_HTTP_LOGS` | `string?` | — | OTLP HTTP endpoint for logs |
22
+ | `OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS` | `number?` | `60` | Log export interval |
23
+ | `OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS` | `number?` | `60` | Metrics export interval |
24
+ | `OPENTELEMETRY_COLLECTOR_AWS` | `boolean?` | — | AWS-specific feature flag |
25
+ | `OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER` | `string?` | — | Bearer token for collector auth |
26
+
27
+ When a collector endpoint is not provided, the corresponding signal (traces, logs, or metrics) is initialized with no export — the provider is still available but no data is sent.
28
+
29
+ ## Exported Classes
30
+
31
+ ### StandardTracer
32
+
33
+ Sets up a global `NodeTracerProvider` with:
34
+
35
+ - AWS X-Ray ID generator (`AWSXRayIdGenerator`)
36
+ - OTLP HTTP trace exporter (optional, when `OPENTELEMETRY_COLLECTOR_HTTP_TRACES` is set)
37
+ - `AsyncHooksContextManager` for automatic context propagation
38
+ - Service resource attributes (name, version, hostname)
39
+
40
+ ```typescript
41
+ import { StandardTracer } from "@devopsplaybook.io/otel-utils";
42
+
43
+ const tracer = new StandardTracer(config);
44
+ const span = tracer.startSpan("my-operation");
45
+ // ... do work ...
46
+ span.end();
47
+ ```
48
+
49
+ **Methods:**
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-_/]`.
52
+ - `static updateHttpHeader(context, headers?)` — Injects W3C trace context into an HTTP headers object for propagation to downstream services.
53
+
54
+ ### StandardLogger
55
+
56
+ Initializes a `LoggerProvider` with an OTLP log exporter and a `BatchLogRecordProcessor` (`maxQueueSize: 2048`).
57
+
58
+ ```typescript
59
+ import { StandardLogger } from "@devopsplaybook.io/otel-utils";
60
+
61
+ const logger = new StandardLogger();
62
+ logger.initOTel(config);
63
+
64
+ const moduleLog = logger.createModuleLogger("my-module");
65
+ moduleLog.info("Hello world");
66
+ ```
67
+
68
+ **Methods:**
69
+
70
+ - `initOTel(config)` — Initializes the logger provider and optional OTLP exporter.
71
+ - `getLogger()` — Returns the underlying OTel `Logger` or `undefined`.
72
+ - `createModuleLogger(moduleName)` — Creates a `ModuleLogger` scoped to a module name.
73
+
74
+ ### ModuleLogger
75
+
76
+ Scoped logger that prefixes all output with a module name. Logs to both `console.log` and (when available) the OTel Logger.
77
+
78
+ ```typescript
79
+ const log = logger.createModuleLogger("api");
80
+ log.info("request received");
81
+ log.warn("rate limit approaching");
82
+ log.error("connection failed", new Error("timeout"));
83
+ log.info("processing complete", activeSpan); // attach trace context
84
+ ```
85
+
86
+ **Methods:**
87
+
88
+ - `info(message, context?)` — Log at INFO level.
89
+ - `warn(message, context?)` — Log at WARN level.
90
+ - `error(message, error?, context?)` — Log at ERROR level with optional `Error` and span context. Attaches `exception.type`, `exception.message`, and `exception.stacktrace` attributes.
91
+
92
+ When a `Span` is passed as `context`, the log record includes `span.id` and `trace.id` attributes for trace correlation.
93
+
94
+ ### StandardMeter
95
+
96
+ Creates a `MeterProvider` with an optional `PeriodicExportingMetricReader` (`concurrencyLimit: 5`). Provides convenience factories for common metric types.
97
+
98
+ ```typescript
99
+ import { StandardMeter } from "@devopsplaybook.io/otel-utils";
100
+
101
+ const meter = new StandardMeter(config);
102
+
103
+ const requestCount = meter.createCounter("requests.total");
104
+ requestCount.add(1);
105
+
106
+ const latency = meter.createHistogram("requests.latency");
107
+ latency.record(42);
108
+
109
+ const activeUsers = meter.createUpDownCounter("users.active");
110
+ activeUsers.add(1);
111
+
112
+ const cpuGauge = meter.createObservableGauge(
113
+ "cpu.usage",
114
+ (result) => result.observe(cpuPercent),
115
+ "Current CPU usage",
116
+ );
117
+ ```
118
+
119
+ **Methods:**
120
+
121
+ - `createCounter(key)` — Creates a `Counter` with name `${serviceName}.${key}`.
122
+ - `createUpDownCounter(key)` — Creates an `UpDownCounter`.
123
+ - `createHistogram(key)` — Creates a `Histogram`.
124
+ - `createObservableGauge(key, callback, description?)` — Creates an `ObservableGauge` with a callback. Description is optional.
125
+
126
+ ## Internal Utilities
127
+
128
+ ### createOTelResource
129
+
130
+ Shared internal utility that creates an `OTelResource` with `service.name`, `service.version`, and `network.local.address` (hostname). The hostname is cached at module load time to minimize system calls.
131
+
132
+ ```typescript
133
+ // Used internally by StandardTracer, StandardLogger, and StandardMeter
134
+ ```
135
+
136
+ ## Architecture
137
+
138
+ ```
139
+ Application
140
+ ├── StandardTracer ──► OTLP Trace Exporter ──► Collector / otel-light
141
+ ├── StandardLogger ──► OTLP Log Exporter ──► Collector / otel-light
142
+ │ └── ModuleLogger (scoped per module)
143
+ └── StandardMeter ──► OTLP Metric Exporter ──► Collector / otel-light
144
+ └── PeriodicExportingMetricReader
145
+ ```
146
+
147
+ All three signals share the same service identity (name + version) and resource attributes via `createOTelResource`. Export is optional per signal — only configured when the corresponding `OPENTELEMETRY_COLLECTOR_HTTP_*` endpoint is set.
148
+
149
+ ## Dependencies
150
+
151
+ - `@opentelemetry/api` / `@opentelemetry/api-logs` — OpenTelemetry API interfaces
152
+ - `@opentelemetry/sdk-trace-node` / `@opentelemetry/sdk-trace-base` — Tracing SDK
153
+ - `@opentelemetry/sdk-metrics` — Metrics SDK
154
+ - `@opentelemetry/sdk-logs` — Logging SDK
155
+ - `@opentelemetry/exporter-trace-otlp-http` / `@opentelemetry/exporter-metrics-otlp-http` / `@opentelemetry/exporter-logs-otlp-http` — OTLP HTTP exporters
156
+ - `@opentelemetry/id-generator-aws-xray` — AWS X-Ray trace ID format
157
+ - `@opentelemetry/context-async-hooks` — Async context management
158
+ - `@opentelemetry/core` — W3C trace context propagation
159
+ - `@opentelemetry/resources` / `@opentelemetry/semantic-conventions` — Resource attributes
160
+
161
+ ## Build
162
+
163
+ ```bash
164
+ npm run build # tsc → dist/
165
+ ```
@@ -21,7 +21,6 @@ class ModuleLogger {
21
21
  let formattedMessage = message;
22
22
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
23
23
  const attributes = { "log.type": "custom" };
24
- formattedMessage = message;
25
24
  if (error) {
26
25
  attributes["exception.type"] = error.name;
27
26
  attributes["exception.message"] = error.message;
@@ -1,7 +1,8 @@
1
1
  import type { Logger as OTelLogger } from "@opentelemetry/api-logs";
2
2
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
3
3
  import { ModuleLogger } from "./ModuleLogger";
4
- export declare class StandardLogger {
4
+ import type { StandardLoggerInterface } from "./models/StandardLoggerInterface";
5
+ export declare class StandardLogger implements StandardLoggerInterface {
5
6
  private logger?;
6
7
  private serviceVersion?;
7
8
  private serviceName?;
@@ -1,53 +1,20 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.StandardLogger = void 0;
37
4
  const exporter_logs_otlp_http_1 = require("@opentelemetry/exporter-logs-otlp-http");
38
- const resources_1 = require("@opentelemetry/resources");
39
5
  const sdk_logs_1 = require("@opentelemetry/sdk-logs");
40
- const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
41
- const os = __importStar(require("os"));
6
+ const createResource_1 = require("./utils/createResource");
42
7
  const ModuleLogger_1 = require("./ModuleLogger");
43
8
  class StandardLogger {
44
9
  initOTel(config) {
10
+ var _a;
45
11
  this.serviceName = config.SERVICE_ID;
46
12
  this.serviceVersion = config.VERSION;
47
13
  if (config.OPENTELEMETRY_COLLECTOR_HTTP_LOGS) {
48
14
  const exporterHeaders = {};
49
15
  if (config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER) {
50
- exporterHeaders["Authorization"] = `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
16
+ exporterHeaders["Authorization"] =
17
+ `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
51
18
  }
52
19
  const exporter = new exporter_logs_otlp_http_1.OTLPLogExporter({
53
20
  url: config.OPENTELEMETRY_COLLECTOR_HTTP_LOGS,
@@ -56,16 +23,11 @@ class StandardLogger {
56
23
  const loggerProvider = new sdk_logs_1.LoggerProvider({
57
24
  processors: [
58
25
  new sdk_logs_1.BatchLogRecordProcessor(exporter, {
59
- maxQueueSize: 100,
60
- scheduledDelayMillis: config.OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS *
61
- 1000,
26
+ maxQueueSize: 2048,
27
+ scheduledDelayMillis: ((_a = config.OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS) !== null && _a !== void 0 ? _a : 60) * 1000,
62
28
  }),
63
29
  ],
64
- resource: (0, resources_1.resourceFromAttributes)({
65
- [semantic_conventions_1.ATTR_SERVICE_NAME]: `${this.serviceName}`,
66
- [semantic_conventions_1.ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
67
- [semantic_conventions_1.ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
68
- }),
30
+ resource: (0, createResource_1.createOTelResource)(this.serviceName, this.serviceVersion),
69
31
  });
70
32
  this.logger = loggerProvider.getLogger(`${this.serviceName}:${this.serviceVersion}`);
71
33
  }
@@ -1,4 +1,4 @@
1
- import { Counter, Histogram, ObservableGauge } from "@opentelemetry/api";
1
+ import { Counter, Histogram, ObservableGauge, UpDownCounter } from "@opentelemetry/api";
2
2
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
3
3
  export declare class StandardMeter {
4
4
  private meter;
@@ -6,7 +6,7 @@ export declare class StandardMeter {
6
6
  private serviceName;
7
7
  constructor(config: ConfigOTelInterface);
8
8
  createCounter(key: string): Counter;
9
- createUpDownCounter(key: string): Counter;
9
+ createUpDownCounter(key: string): UpDownCounter;
10
10
  createHistogram(key: string): Histogram;
11
- createObservableGauge(key: string, callback: (observableResult: any) => void, description?: any): ObservableGauge;
11
+ createObservableGauge(key: string, callback: (observableResult: any) => void, description?: string): ObservableGauge;
12
12
  }
@@ -1,46 +1,12 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.StandardMeter = void 0;
37
4
  const exporter_metrics_otlp_http_1 = require("@opentelemetry/exporter-metrics-otlp-http");
38
- const resources_1 = require("@opentelemetry/resources");
5
+ const createResource_1 = require("./utils/createResource");
39
6
  const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
40
- const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
41
- const os = __importStar(require("os"));
42
7
  class StandardMeter {
43
8
  constructor(config) {
9
+ var _a;
44
10
  this.serviceName = config.SERVICE_ID;
45
11
  this.serviceVersion = config.VERSION;
46
12
  let meterProvider;
@@ -48,34 +14,26 @@ class StandardMeter {
48
14
  const collectorOptions = {
49
15
  url: config.OPENTELEMETRY_COLLECTOR_HTTP_METRICS,
50
16
  headers: {},
51
- concurrencyLimit: 1,
17
+ concurrencyLimit: 5,
52
18
  };
53
19
  if (config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER) {
54
- collectorOptions.headers["Authorization"] = `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
20
+ collectorOptions.headers["Authorization"] =
21
+ `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
55
22
  }
56
23
  const metricExporter = new exporter_metrics_otlp_http_1.OTLPMetricExporter(collectorOptions);
57
24
  meterProvider = new sdk_metrics_1.MeterProvider({
58
- resource: (0, resources_1.resourceFromAttributes)({
59
- [semantic_conventions_1.ATTR_SERVICE_NAME]: `${this.serviceName}`,
60
- [semantic_conventions_1.ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
61
- [semantic_conventions_1.ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
62
- }),
25
+ resource: (0, createResource_1.createOTelResource)(this.serviceName, this.serviceVersion),
63
26
  readers: [
64
27
  new sdk_metrics_1.PeriodicExportingMetricReader({
65
28
  exporter: metricExporter,
66
- exportIntervalMillis: config.OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS *
67
- 1000,
29
+ exportIntervalMillis: ((_a = config.OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS) !== null && _a !== void 0 ? _a : 60) * 1000,
68
30
  }),
69
31
  ],
70
32
  });
71
33
  }
72
34
  else {
73
35
  meterProvider = new sdk_metrics_1.MeterProvider({
74
- resource: (0, resources_1.resourceFromAttributes)({
75
- [semantic_conventions_1.ATTR_SERVICE_NAME]: `${this.serviceName}`,
76
- [semantic_conventions_1.ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
77
- [semantic_conventions_1.ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
78
- }),
36
+ resource: (0, createResource_1.createOTelResource)(this.serviceName, this.serviceVersion),
79
37
  });
80
38
  }
81
39
  this.meter = meterProvider.getMeter(`${this.serviceName}:${this.serviceVersion}`);
@@ -91,10 +49,8 @@ class StandardMeter {
91
49
  }
92
50
  createObservableGauge(key,
93
51
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
- callback,
95
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
- description = null) {
97
- const observableGauge = this.meter.createObservableGauge(key, description);
52
+ callback, description) {
53
+ const observableGauge = this.meter.createObservableGauge(key, description ? { description } : undefined);
98
54
  observableGauge.addCallback(callback);
99
55
  return observableGauge;
100
56
  }
@@ -1,6 +1,8 @@
1
1
  import { Span } from "@opentelemetry/sdk-trace-base";
2
2
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
3
3
  export declare class StandardTracer {
4
+ private static readonly SPAN_NAME_SANITIZE_RE;
5
+ private static readonly PROPAGATOR;
4
6
  private tracer;
5
7
  private serviceVersion;
6
8
  private serviceName;
@@ -39,11 +39,10 @@ const context_async_hooks_1 = require("@opentelemetry/context-async-hooks");
39
39
  const core_1 = require("@opentelemetry/core");
40
40
  const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
41
41
  const id_generator_aws_xray_1 = require("@opentelemetry/id-generator-aws-xray");
42
- const resources_1 = require("@opentelemetry/resources");
43
42
  const sdk_trace_base_1 = require("@opentelemetry/sdk-trace-base");
44
43
  const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
45
44
  const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
46
- const os = __importStar(require("os"));
45
+ const createResource_1 = require("./utils/createResource");
47
46
  class StandardTracer {
48
47
  constructor(config) {
49
48
  this.serviceName = config.SERVICE_ID;
@@ -52,7 +51,8 @@ class StandardTracer {
52
51
  if (config.OPENTELEMETRY_COLLECTOR_HTTP_TRACES) {
53
52
  const exporterHeaders = {};
54
53
  if (config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER) {
55
- exporterHeaders["Authorization"] = `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
54
+ exporterHeaders["Authorization"] =
55
+ `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
56
56
  }
57
57
  const exporter = new exporter_trace_otlp_http_1.OTLPTraceExporter({
58
58
  url: config.OPENTELEMETRY_COLLECTOR_HTTP_TRACES,
@@ -62,11 +62,7 @@ class StandardTracer {
62
62
  }
63
63
  const traceProvider = new sdk_trace_node_1.NodeTracerProvider({
64
64
  idGenerator: new id_generator_aws_xray_1.AWSXRayIdGenerator(),
65
- resource: (0, resources_1.resourceFromAttributes)({
66
- [semantic_conventions_1.ATTR_SERVICE_NAME]: `${this.serviceName}`,
67
- [semantic_conventions_1.ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
68
- [semantic_conventions_1.ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
69
- }),
65
+ resource: (0, createResource_1.createOTelResource)(this.serviceName, this.serviceVersion),
70
66
  spanProcessors,
71
67
  });
72
68
  traceProvider.register();
@@ -76,7 +72,7 @@ class StandardTracer {
76
72
  this.tracer = api_1.default.trace.getTracer(`${this.serviceName}:${this.serviceVersion}`);
77
73
  }
78
74
  startSpan(name, parentSpan) {
79
- const sanitizedName = String(name).replace(/[^a-zA-Z0-9-_/]/g, "_");
75
+ const sanitizedName = String(name).replace(StandardTracer.SPAN_NAME_SANITIZE_RE, "_");
80
76
  if (parentSpan) {
81
77
  return this.tracer.startSpan(sanitizedName, undefined, api_1.default.trace.setSpan(api_1.default.context.active(), parentSpan));
82
78
  }
@@ -90,11 +86,12 @@ class StandardTracer {
90
86
  if (!headers) {
91
87
  headers = {};
92
88
  }
93
- const propagator = new core_1.W3CTraceContextPropagator();
94
- propagator.inject(api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, context.spanContext()),
89
+ StandardTracer.PROPAGATOR.inject(api_1.trace.setSpanContext(api_1.ROOT_CONTEXT, context.spanContext()),
95
90
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
96
91
  headers, api_1.defaultTextMapSetter);
97
92
  return headers;
98
93
  }
99
94
  }
100
95
  exports.StandardTracer = StandardTracer;
96
+ StandardTracer.SPAN_NAME_SANITIZE_RE = /[^a-zA-Z0-9-_/]/g;
97
+ StandardTracer.PROPAGATOR = new core_1.W3CTraceContextPropagator();
@@ -1,11 +1,11 @@
1
1
  export interface ConfigOTelInterface {
2
2
  SERVICE_ID: string;
3
3
  VERSION: string;
4
- OPENTELEMETRY_COLLECTOR_HTTP_TRACES: string;
5
- OPENTELEMETRY_COLLECTOR_HTTP_METRICS: string;
6
- OPENTELEMETRY_COLLECTOR_HTTP_LOGS: string;
7
- OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS: number;
8
- OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS: number;
9
- OPENTELEMETRY_COLLECTOR_AWS: boolean;
10
- OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER: string;
4
+ OPENTELEMETRY_COLLECTOR_HTTP_TRACES?: string;
5
+ OPENTELEMETRY_COLLECTOR_HTTP_METRICS?: string;
6
+ OPENTELEMETRY_COLLECTOR_HTTP_LOGS?: string;
7
+ OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS?: number;
8
+ OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS?: number;
9
+ OPENTELEMETRY_COLLECTOR_AWS?: boolean;
10
+ OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER?: string;
11
11
  }
@@ -0,0 +1 @@
1
+ export declare function createOTelResource(serviceName: string, serviceVersion: string): import("@opentelemetry/resources").Resource;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.createOTelResource = createOTelResource;
37
+ const resources_1 = require("@opentelemetry/resources");
38
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
39
+ const os = __importStar(require("os"));
40
+ const CACHED_HOSTNAME = os.hostname();
41
+ function createOTelResource(serviceName, serviceVersion) {
42
+ return (0, resources_1.resourceFromAttributes)({
43
+ [semantic_conventions_1.ATTR_SERVICE_NAME]: serviceName,
44
+ [semantic_conventions_1.ATTR_SERVICE_VERSION]: serviceVersion,
45
+ [semantic_conventions_1.ATTR_NETWORK_LOCAL_ADDRESS]: CACHED_HOSTNAME,
46
+ });
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devopsplaybook.io/otel-utils",
3
- "version": "1.0.18",
3
+ "version": "1.1.0-beta.21.15fb757",
4
4
  "description": "Utility to simplify integration with Open Telemetry",
5
5
  "keywords": [
6
6
  "Open",
@@ -20,24 +20,29 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@opentelemetry/api": "^1.9.1",
23
- "@opentelemetry/api-logs": "^0.217.0",
24
- "@opentelemetry/auto-instrumentations-node": "^0.75.0",
25
- "@opentelemetry/exporter-logs-otlp-http": "^0.217.0",
26
- "@opentelemetry/exporter-trace-otlp-http": "^0.217.0",
23
+ "@opentelemetry/api-logs": "^0.218.0",
24
+ "@opentelemetry/auto-instrumentations-node": "^0.76.0",
25
+ "@opentelemetry/context-async-hooks": "^2.7.1",
26
+ "@opentelemetry/core": "^2.7.1",
27
+ "@opentelemetry/exporter-logs-otlp-http": "^0.218.0",
28
+ "@opentelemetry/exporter-metrics-otlp-http": "^0.218.0",
29
+ "@opentelemetry/exporter-trace-otlp-http": "^0.218.0",
27
30
  "@opentelemetry/id-generator-aws-xray": "^2.1.0",
28
31
  "@opentelemetry/resources": "^2.7.1",
29
- "@opentelemetry/sdk-logs": "^0.217.0",
30
- "@opentelemetry/sdk-node": "^0.217.0",
32
+ "@opentelemetry/sdk-logs": "^0.218.0",
33
+ "@opentelemetry/sdk-metrics": "^2.7.1",
34
+ "@opentelemetry/sdk-node": "^0.218.0",
31
35
  "@opentelemetry/sdk-trace-base": "^2.7.1",
36
+ "@opentelemetry/sdk-trace-node": "^2.7.1",
32
37
  "@opentelemetry/sdk-trace-web": "^2.7.1",
33
- "@opentelemetry/semantic-conventions": "^1.40.0"
38
+ "@opentelemetry/semantic-conventions": "^1.41.1"
34
39
  },
35
40
  "devDependencies": {
36
41
  "@eslint/js": "^10.0.1",
37
- "@types/node": "^25.7.0",
42
+ "@types/node": "^25.9.1",
38
43
  "@types/sqlite3": "^5.1.0",
39
44
  "ts-node": "^10.9.2",
40
- "typescript-eslint": "^8.59.3",
45
+ "typescript-eslint": "^8.59.4",
41
46
  "typescript": "^6.0.3"
42
47
  },
43
48
  "publishConfig": {
@@ -28,14 +28,13 @@ export class ModuleLogger {
28
28
  message: string,
29
29
  severityNumber = SeverityNumber.INFO,
30
30
  error?: Error | null,
31
- context?: Span
31
+ context?: Span,
32
32
  ): void {
33
33
  let formattedMessage = message;
34
34
 
35
35
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
36
  const attributes: Record<string, any> = { "log.type": "custom" };
37
37
 
38
- formattedMessage = message;
39
38
  if (error) {
40
39
  attributes["exception.type"] = error.name;
41
40
  attributes["exception.message"] = error.message;
@@ -1,21 +1,17 @@
1
- import type { Logger, Logger as OTelLogger } from "@opentelemetry/api-logs";
1
+ import type { Logger as OTelLogger } from "@opentelemetry/api-logs";
2
2
  import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
3
- import { resourceFromAttributes } from "@opentelemetry/resources";
4
3
  import {
5
4
  BatchLogRecordProcessor,
6
5
  LoggerProvider,
7
6
  } from "@opentelemetry/sdk-logs";
8
- import {
9
- ATTR_NETWORK_LOCAL_ADDRESS,
10
- ATTR_SERVICE_NAME,
11
- ATTR_SERVICE_VERSION,
12
- } from "@opentelemetry/semantic-conventions";
13
- import * as os from "os";
14
7
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
8
+ import { createOTelResource } from "./utils/createResource";
15
9
  import { ModuleLogger } from "./ModuleLogger";
16
10
 
17
- export class StandardLogger {
18
- private logger?: Logger;
11
+ import type { StandardLoggerInterface } from "./models/StandardLoggerInterface";
12
+
13
+ export class StandardLogger implements StandardLoggerInterface {
14
+ private logger?: OTelLogger;
19
15
  private serviceVersion?: string;
20
16
  private serviceName?: string;
21
17
 
@@ -26,9 +22,8 @@ export class StandardLogger {
26
22
  if (config.OPENTELEMETRY_COLLECTOR_HTTP_LOGS) {
27
23
  const exporterHeaders: Record<string, string> = {};
28
24
  if (config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER) {
29
- exporterHeaders[
30
- "Authorization"
31
- ] = `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
25
+ exporterHeaders["Authorization"] =
26
+ `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
32
27
  }
33
28
  const exporter = new OTLPLogExporter({
34
29
  url: config.OPENTELEMETRY_COLLECTOR_HTTP_LOGS,
@@ -38,21 +33,17 @@ export class StandardLogger {
38
33
  const loggerProvider = new LoggerProvider({
39
34
  processors: [
40
35
  new BatchLogRecordProcessor(exporter, {
41
- maxQueueSize: 100,
36
+ maxQueueSize: 2048,
42
37
  scheduledDelayMillis:
43
- config.OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS *
44
- 1000,
38
+ (config.OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS ??
39
+ 60) * 1000,
45
40
  }),
46
41
  ],
47
- resource: resourceFromAttributes({
48
- [ATTR_SERVICE_NAME]: `${this.serviceName}`,
49
- [ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
50
- [ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
51
- }),
42
+ resource: createOTelResource(this.serviceName, this.serviceVersion),
52
43
  });
53
44
 
54
45
  this.logger = loggerProvider.getLogger(
55
- `${this.serviceName}:${this.serviceVersion}`
46
+ `${this.serviceName}:${this.serviceVersion}`,
56
47
  );
57
48
  }
58
49
  }
@@ -1,16 +1,16 @@
1
- import { Counter, Histogram, Meter, ObservableGauge } from "@opentelemetry/api";
1
+ import {
2
+ Counter,
3
+ Histogram,
4
+ Meter,
5
+ ObservableGauge,
6
+ UpDownCounter,
7
+ } from "@opentelemetry/api";
2
8
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
3
- import { resourceFromAttributes } from "@opentelemetry/resources";
9
+ import { createOTelResource } from "./utils/createResource";
4
10
  import {
5
11
  MeterProvider,
6
12
  PeriodicExportingMetricReader,
7
13
  } from "@opentelemetry/sdk-metrics";
8
- import {
9
- ATTR_NETWORK_LOCAL_ADDRESS,
10
- ATTR_SERVICE_NAME,
11
- ATTR_SERVICE_VERSION,
12
- } from "@opentelemetry/semantic-conventions";
13
- import * as os from "os";
14
14
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
15
15
 
16
16
  export class StandardMeter {
@@ -26,40 +26,31 @@ export class StandardMeter {
26
26
  const collectorOptions = {
27
27
  url: config.OPENTELEMETRY_COLLECTOR_HTTP_METRICS,
28
28
  headers: {} as Record<string, string>,
29
- concurrencyLimit: 1,
29
+ concurrencyLimit: 5,
30
30
  };
31
31
  if (config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER) {
32
- collectorOptions.headers[
33
- "Authorization"
34
- ] = `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
32
+ collectorOptions.headers["Authorization"] =
33
+ `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
35
34
  }
36
35
  const metricExporter = new OTLPMetricExporter(collectorOptions);
37
36
  meterProvider = new MeterProvider({
38
- resource: resourceFromAttributes({
39
- [ATTR_SERVICE_NAME]: `${this.serviceName}`,
40
- [ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
41
- [ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
42
- }),
37
+ resource: createOTelResource(this.serviceName, this.serviceVersion),
43
38
  readers: [
44
39
  new PeriodicExportingMetricReader({
45
40
  exporter: metricExporter,
46
41
  exportIntervalMillis:
47
- config.OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS *
48
- 1000,
42
+ (config.OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS ??
43
+ 60) * 1000,
49
44
  }),
50
45
  ],
51
46
  });
52
47
  } else {
53
48
  meterProvider = new MeterProvider({
54
- resource: resourceFromAttributes({
55
- [ATTR_SERVICE_NAME]: `${this.serviceName}`,
56
- [ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
57
- [ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
58
- }),
49
+ resource: createOTelResource(this.serviceName, this.serviceVersion),
59
50
  });
60
51
  }
61
52
  this.meter = meterProvider.getMeter(
62
- `${this.serviceName}:${this.serviceVersion}`
53
+ `${this.serviceName}:${this.serviceVersion}`,
63
54
  );
64
55
  }
65
56
 
@@ -67,7 +58,7 @@ export class StandardMeter {
67
58
  return this.meter.createCounter(`${this.serviceName}.${key}`);
68
59
  }
69
60
 
70
- public createUpDownCounter(key: string): Counter {
61
+ public createUpDownCounter(key: string): UpDownCounter {
71
62
  return this.meter.createUpDownCounter(`${this.serviceName}.${key}`);
72
63
  }
73
64
 
@@ -79,10 +70,12 @@ export class StandardMeter {
79
70
  key: string,
80
71
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
72
  callback: (observableResult: any) => void,
82
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
- description: any = null
73
+ description?: string,
84
74
  ): ObservableGauge {
85
- const observableGauge = this.meter.createObservableGauge(key, description);
75
+ const observableGauge = this.meter.createObservableGauge(
76
+ key,
77
+ description ? { description } : undefined,
78
+ );
86
79
  observableGauge.addCallback(callback);
87
80
  return observableGauge;
88
81
  }
@@ -8,20 +8,19 @@ import { AsyncHooksContextManager } from "@opentelemetry/context-async-hooks";
8
8
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
9
9
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
10
10
  import { AWSXRayIdGenerator } from "@opentelemetry/id-generator-aws-xray";
11
- import { resourceFromAttributes } from "@opentelemetry/resources";
12
11
  import { BatchSpanProcessor, Span } from "@opentelemetry/sdk-trace-base";
13
12
  import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node";
14
13
  import {
15
14
  ATTR_HTTP_REQUEST_METHOD,
16
15
  ATTR_HTTP_ROUTE,
17
- ATTR_NETWORK_LOCAL_ADDRESS,
18
- ATTR_SERVICE_NAME,
19
- ATTR_SERVICE_VERSION,
20
16
  } from "@opentelemetry/semantic-conventions";
21
- import * as os from "os";
22
17
  import { ConfigOTelInterface } from "./models/ConfigOTelInterface";
18
+ import { createOTelResource } from "./utils/createResource";
23
19
 
24
20
  export class StandardTracer {
21
+ private static readonly SPAN_NAME_SANITIZE_RE = /[^a-zA-Z0-9-_/]/g;
22
+ private static readonly PROPAGATOR = new W3CTraceContextPropagator();
23
+
25
24
  private tracer: Tracer;
26
25
  private serviceVersion: string;
27
26
  private serviceName: string;
@@ -34,9 +33,8 @@ export class StandardTracer {
34
33
  if (config.OPENTELEMETRY_COLLECTOR_HTTP_TRACES) {
35
34
  const exporterHeaders: Record<string, string> = {};
36
35
  if (config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER) {
37
- exporterHeaders[
38
- "Authorization"
39
- ] = `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
36
+ exporterHeaders["Authorization"] =
37
+ `Bearer ${config.OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER}`;
40
38
  }
41
39
  const exporter = new OTLPTraceExporter({
42
40
  url: config.OPENTELEMETRY_COLLECTOR_HTTP_TRACES,
@@ -46,11 +44,7 @@ export class StandardTracer {
46
44
  }
47
45
  const traceProvider = new NodeTracerProvider({
48
46
  idGenerator: new AWSXRayIdGenerator(),
49
- resource: resourceFromAttributes({
50
- [ATTR_SERVICE_NAME]: `${this.serviceName}`,
51
- [ATTR_SERVICE_VERSION]: `${this.serviceVersion}`,
52
- [ATTR_NETWORK_LOCAL_ADDRESS]: os.hostname(),
53
- }),
47
+ resource: createOTelResource(this.serviceName, this.serviceVersion),
54
48
  spanProcessors,
55
49
  });
56
50
  traceProvider.register();
@@ -58,24 +52,27 @@ export class StandardTracer {
58
52
  contextManager.enable();
59
53
  opentelemetry.context.setGlobalContextManager(contextManager);
60
54
  this.tracer = opentelemetry.trace.getTracer(
61
- `${this.serviceName}:${this.serviceVersion}`
55
+ `${this.serviceName}:${this.serviceVersion}`,
62
56
  );
63
57
  }
64
58
 
65
59
  public startSpan(name: string, parentSpan?: Span): Span {
66
- const sanitizedName = String(name).replace(/[^a-zA-Z0-9-_/]/g, "_");
60
+ const sanitizedName = String(name).replace(
61
+ StandardTracer.SPAN_NAME_SANITIZE_RE,
62
+ "_",
63
+ );
67
64
  if (parentSpan) {
68
65
  return this.tracer.startSpan(
69
66
  sanitizedName,
70
67
  undefined,
71
- opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan)
68
+ opentelemetry.trace.setSpan(opentelemetry.context.active(), parentSpan),
72
69
  ) as Span;
73
70
  }
74
71
  const span = this.tracer.startSpan(sanitizedName) as Span;
75
72
  span.setAttribute(ATTR_HTTP_REQUEST_METHOD, `BACKEND`);
76
73
  span.setAttribute(
77
74
  ATTR_HTTP_ROUTE,
78
- `${this.serviceName}-${this.serviceVersion}-${sanitizedName}`
75
+ `${this.serviceName}-${this.serviceVersion}-${sanitizedName}`,
79
76
  );
80
77
  return span;
81
78
  }
@@ -85,12 +82,11 @@ export class StandardTracer {
85
82
  if (!headers) {
86
83
  headers = {};
87
84
  }
88
- const propagator = new W3CTraceContextPropagator();
89
- propagator.inject(
85
+ StandardTracer.PROPAGATOR.inject(
90
86
  trace.setSpanContext(ROOT_CONTEXT, context.spanContext()),
91
87
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
88
  headers as any,
93
- defaultTextMapSetter
89
+ defaultTextMapSetter,
94
90
  );
95
91
  return headers;
96
92
  }
@@ -1,11 +1,11 @@
1
1
  export interface ConfigOTelInterface {
2
2
  SERVICE_ID: string;
3
3
  VERSION: string;
4
- OPENTELEMETRY_COLLECTOR_HTTP_TRACES: string;
5
- OPENTELEMETRY_COLLECTOR_HTTP_METRICS: string;
6
- OPENTELEMETRY_COLLECTOR_HTTP_LOGS: string;
7
- OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS: number;
8
- OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS: number;
9
- OPENTELEMETRY_COLLECTOR_AWS: boolean;
10
- OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER: string;
4
+ OPENTELEMETRY_COLLECTOR_HTTP_TRACES?: string;
5
+ OPENTELEMETRY_COLLECTOR_HTTP_METRICS?: string;
6
+ OPENTELEMETRY_COLLECTOR_HTTP_LOGS?: string;
7
+ OPENTELEMETRY_COLLECTOR_EXPORT_LOGS_INTERVAL_SECONDS?: number;
8
+ OPENTELEMETRY_COLLECTOR_EXPORT_METRICS_INTERVAL_SECONDS?: number;
9
+ OPENTELEMETRY_COLLECTOR_AWS?: boolean;
10
+ OPENTELEMETRY_COLLECT_AUTHORIZATION_HEADER?: string;
11
11
  }
@@ -0,0 +1,20 @@
1
+ import { resourceFromAttributes } from "@opentelemetry/resources";
2
+ import {
3
+ ATTR_NETWORK_LOCAL_ADDRESS,
4
+ ATTR_SERVICE_NAME,
5
+ ATTR_SERVICE_VERSION,
6
+ } from "@opentelemetry/semantic-conventions";
7
+ import * as os from "os";
8
+
9
+ const CACHED_HOSTNAME = os.hostname();
10
+
11
+ export function createOTelResource(
12
+ serviceName: string,
13
+ serviceVersion: string,
14
+ ) {
15
+ return resourceFromAttributes({
16
+ [ATTR_SERVICE_NAME]: serviceName,
17
+ [ATTR_SERVICE_VERSION]: serviceVersion,
18
+ [ATTR_NETWORK_LOCAL_ADDRESS]: CACHED_HOSTNAME,
19
+ });
20
+ }