@bsb/observable-opentelemetry 9.0.0

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 ADDED
@@ -0,0 +1,58 @@
1
+ # @bsb/observable-opentelemetry
2
+
3
+ OpenTelemetry observable plugin for BSB with full OTLP export support for traces, metrics, and logs.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @bsb/observable-opentelemetry
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Full OpenTelemetry integration** - Traces, metrics, and logs
14
+ - **OTLP export** - HTTP and gRPC protocol support
15
+ - **Resource attributes** - Customizable service metadata
16
+ - **Configurable sampling** - Control trace collection rates
17
+ - **Batch processing** - Efficient data export with batching
18
+
19
+ ## Configuration
20
+
21
+ ```yaml
22
+ plugins:
23
+ observables:
24
+ - plugin: "@bsb/observable-opentelemetry"
25
+ enabled: true
26
+ config:
27
+ serviceName: "my-service"
28
+ serviceVersion: "1.0.0"
29
+ endpoint: "http://localhost:4318"
30
+ export:
31
+ protocol: "http"
32
+ interval: 5000
33
+ maxBatchSize: 512
34
+ enabled:
35
+ traces: true
36
+ metrics: true
37
+ logs: true
38
+ resourceAttributes:
39
+ environment: "production"
40
+ region: "us-east-1"
41
+ samplingRate: 1.0
42
+ ```
43
+
44
+ ### Configuration Options
45
+
46
+ - **serviceName**: Name of your service
47
+ - **serviceVersion**: Version of your service
48
+ - **endpoint**: OTLP collector endpoint URL
49
+ - **export.protocol**: "http" or "grpc"
50
+ - **export.interval**: Export interval in milliseconds
51
+ - **export.maxBatchSize**: Maximum batch size for exports
52
+ - **enabled**: Toggle traces, metrics, and logs individually
53
+ - **resourceAttributes**: Custom resource attributes
54
+ - **samplingRate**: Trace sampling rate (0.0 to 1.0)
55
+
56
+ ## License
57
+
58
+ Dual-licensed under AGPL-3.0-only OR Commercial License.
package/lib/index.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * BSB (Better-Service-Base) is an event-bus based microservice framework.
3
+ * Copyright (C) 2016 - 2025 BetterCorp (PTY) Ltd
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published
7
+ * by the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * Alternatively, you may obtain a commercial license for this program.
11
+ * The commercial license allows you to use the Program in a closed-source manner,
12
+ * including the right to create derivative works that are not subject to the terms
13
+ * of the AGPL.
14
+ *
15
+ * To obtain a commercial license, please contact the copyright holders at
16
+ * https://www.bettercorp.dev. The terms and conditions of the commercial license
17
+ * will be provided upon request.
18
+ *
19
+ * This program is distributed in the hope that it will be useful,
20
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ * GNU Affero General Public License for more details.
23
+ *
24
+ * You should have received a copy of the GNU Affero General Public License
25
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
26
+ */
27
+ export * from "./plugins/observable-opentelemetry";
package/lib/index.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ /**
3
+ * BSB (Better-Service-Base) is an event-bus based microservice framework.
4
+ * Copyright (C) 2016 - 2025 BetterCorp (PTY) Ltd
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU Affero General Public License as published
8
+ * by the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Alternatively, you may obtain a commercial license for this program.
12
+ * The commercial license allows you to use the Program in a closed-source manner,
13
+ * including the right to create derivative works that are not subject to the terms
14
+ * of the AGPL.
15
+ *
16
+ * To obtain a commercial license, please contact the copyright holders at
17
+ * https://www.bettercorp.dev. The terms and conditions of the commercial license
18
+ * will be provided upon request.
19
+ *
20
+ * This program is distributed in the hope that it will be useful,
21
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
+ * GNU Affero General Public License for more details.
24
+ *
25
+ * You should have received a copy of the GNU Affero General Public License
26
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
27
+ */
28
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ var desc = Object.getOwnPropertyDescriptor(m, k);
31
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
32
+ desc = { enumerable: true, get: function() { return m[k]; } };
33
+ }
34
+ Object.defineProperty(o, k2, desc);
35
+ }) : (function(o, m, k, k2) {
36
+ if (k2 === undefined) k2 = k;
37
+ o[k2] = m[k];
38
+ }));
39
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
40
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ __exportStar(require("./plugins/observable-opentelemetry"), exports);
@@ -0,0 +1,134 @@
1
+ /**
2
+ * BSB (Better-Service-Base) is an event-bus based microservice framework.
3
+ * Copyright (C) 2016 - 2025 BetterCorp (PTY) Ltd
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published
7
+ * by the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * Alternatively, you may obtain a commercial license for this program.
11
+ * The commercial license allows you to use the Program in a closed-source manner,
12
+ * including the right to create derivative works that are not subject to the terms
13
+ * of the AGPL.
14
+ *
15
+ * To obtain a commercial license, please contact the copyright holders at
16
+ * https://www.bettercorp.dev. The terms and conditions of the commercial license
17
+ * will be provided upon request.
18
+ *
19
+ * This program is distributed in the hope that it will be useful,
20
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ * GNU Affero General Public License for more details.
23
+ *
24
+ * You should have received a copy of the GNU Affero General Public License
25
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
26
+ */
27
+ import { BSBObservable, BSBObservableConstructor, BSBError } from "@bsb/base";
28
+ import { DTrace, LogMeta } from "@bsb/base";
29
+ import { z } from "zod";
30
+ /**
31
+ * Configuration schema for OpenTelemetry plugin
32
+ */
33
+ export declare const OpenTelemetryConfigSchema: z.ZodObject<{
34
+ serviceName: z.ZodDefault<z.ZodString>;
35
+ serviceVersion: z.ZodOptional<z.ZodString>;
36
+ endpoint: z.ZodDefault<z.ZodString>;
37
+ export: z.ZodObject<{
38
+ protocol: z.ZodDefault<z.ZodEnum<{
39
+ http: "http";
40
+ grpc: "grpc";
41
+ }>>;
42
+ interval: z.ZodDefault<z.ZodNumber>;
43
+ maxBatchSize: z.ZodDefault<z.ZodNumber>;
44
+ }, z.core.$strip>;
45
+ enabled: z.ZodObject<{
46
+ traces: z.ZodDefault<z.ZodBoolean>;
47
+ metrics: z.ZodDefault<z.ZodBoolean>;
48
+ logs: z.ZodDefault<z.ZodBoolean>;
49
+ }, z.core.$strip>;
50
+ resourceAttributes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
51
+ samplingRate: z.ZodDefault<z.ZodNumber>;
52
+ }, z.core.$strip>;
53
+ export type OpenTelemetryConfig = z.infer<typeof OpenTelemetryConfigSchema>;
54
+ export declare const Config: import("@bsb/base").BSBPluginConfigClass<z.ZodObject<{
55
+ serviceName: z.ZodDefault<z.ZodString>;
56
+ serviceVersion: z.ZodOptional<z.ZodString>;
57
+ endpoint: z.ZodDefault<z.ZodString>;
58
+ export: z.ZodObject<{
59
+ protocol: z.ZodDefault<z.ZodEnum<{
60
+ http: "http";
61
+ grpc: "grpc";
62
+ }>>;
63
+ interval: z.ZodDefault<z.ZodNumber>;
64
+ maxBatchSize: z.ZodDefault<z.ZodNumber>;
65
+ }, z.core.$strip>;
66
+ enabled: z.ZodObject<{
67
+ traces: z.ZodDefault<z.ZodBoolean>;
68
+ metrics: z.ZodDefault<z.ZodBoolean>;
69
+ logs: z.ZodDefault<z.ZodBoolean>;
70
+ }, z.core.$strip>;
71
+ resourceAttributes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
72
+ samplingRate: z.ZodDefault<z.ZodNumber>;
73
+ }, z.core.$strip>>;
74
+ /**
75
+ * OpenTelemetry observable plugin with OTLP export
76
+ */
77
+ export declare class Plugin extends BSBObservable<InstanceType<typeof Config>> {
78
+ static Config: import("@bsb/base").BSBPluginConfigClass<z.ZodObject<{
79
+ serviceName: z.ZodDefault<z.ZodString>;
80
+ serviceVersion: z.ZodOptional<z.ZodString>;
81
+ endpoint: z.ZodDefault<z.ZodString>;
82
+ export: z.ZodObject<{
83
+ protocol: z.ZodDefault<z.ZodEnum<{
84
+ http: "http";
85
+ grpc: "grpc";
86
+ }>>;
87
+ interval: z.ZodDefault<z.ZodNumber>;
88
+ maxBatchSize: z.ZodDefault<z.ZodNumber>;
89
+ }, z.core.$strip>;
90
+ enabled: z.ZodObject<{
91
+ traces: z.ZodDefault<z.ZodBoolean>;
92
+ metrics: z.ZodDefault<z.ZodBoolean>;
93
+ logs: z.ZodDefault<z.ZodBoolean>;
94
+ }, z.core.$strip>;
95
+ resourceAttributes: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
96
+ samplingRate: z.ZodDefault<z.ZodNumber>;
97
+ }, z.core.$strip>>;
98
+ private logFormatter;
99
+ private sdk;
100
+ private tracer;
101
+ private meter;
102
+ private logger;
103
+ private loggerProvider;
104
+ private isDisposed;
105
+ private counters;
106
+ private gauges;
107
+ private histograms;
108
+ private spans;
109
+ constructor(config: BSBObservableConstructor<InstanceType<typeof Config>>);
110
+ init(): Promise<void>;
111
+ run(): Promise<void>;
112
+ /**
113
+ * Map DTrace to OpenTelemetry trace context
114
+ */
115
+ private getTraceContext;
116
+ /**
117
+ * Write log entry via OpenTelemetry
118
+ */
119
+ private writeLog;
120
+ debug(trace: DTrace, pluginName: string, message: string, meta: LogMeta<any>): void;
121
+ info(trace: DTrace, pluginName: string, message: string, meta: LogMeta<any>): void;
122
+ warn(trace: DTrace, pluginName: string, message: string, meta: LogMeta<any>): void;
123
+ error(trace: DTrace, pluginName: string, message: string | BSBError<any>, meta?: LogMeta<any>): void;
124
+ createCounter(timestamp: number, pluginName: string, name: string, description: string, help: string, labels?: string[]): void;
125
+ incrementCounter(timestamp: number, pluginName: string, name: string, value: number, labels?: Record<string, string>): void;
126
+ createGauge(timestamp: number, pluginName: string, name: string, description: string, help: string, labels?: string[]): void;
127
+ setGauge(timestamp: number, pluginName: string, name: string, value: number, labels?: Record<string, string>): void;
128
+ createHistogram(timestamp: number, pluginName: string, name: string, description: string, help: string, boundaries?: number[], labels?: string[]): void;
129
+ observeHistogram(timestamp: number, pluginName: string, name: string, value: number, labels?: Record<string, string>): void;
130
+ spanStart(trace: DTrace, pluginName: string, spanName: string, parentSpanId: string | null, attributes?: Record<string, string | number | boolean>): void;
131
+ spanEnd(trace: DTrace, pluginName: string, attributes?: Record<string, string | number | boolean>): void;
132
+ spanError(trace: DTrace, pluginName: string, error: Error, attributes?: Record<string, string | number | boolean>): void;
133
+ dispose(): Promise<void>;
134
+ }
@@ -0,0 +1,401 @@
1
+ "use strict";
2
+ /**
3
+ * BSB (Better-Service-Base) is an event-bus based microservice framework.
4
+ * Copyright (C) 2016 - 2025 BetterCorp (PTY) Ltd
5
+ *
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU Affero General Public License as published
8
+ * by the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Alternatively, you may obtain a commercial license for this program.
12
+ * The commercial license allows you to use the Program in a closed-source manner,
13
+ * including the right to create derivative works that are not subject to the terms
14
+ * of the AGPL.
15
+ *
16
+ * To obtain a commercial license, please contact the copyright holders at
17
+ * https://www.bettercorp.dev. The terms and conditions of the commercial license
18
+ * will be provided upon request.
19
+ *
20
+ * This program is distributed in the hope that it will be useful,
21
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
+ * GNU Affero General Public License for more details.
24
+ *
25
+ * You should have received a copy of the GNU Affero General Public License
26
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
27
+ */
28
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ var desc = Object.getOwnPropertyDescriptor(m, k);
31
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
32
+ desc = { enumerable: true, get: function() { return m[k]; } };
33
+ }
34
+ Object.defineProperty(o, k2, desc);
35
+ }) : (function(o, m, k, k2) {
36
+ if (k2 === undefined) k2 = k;
37
+ o[k2] = m[k];
38
+ }));
39
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
40
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
41
+ }) : function(o, v) {
42
+ o["default"] = v;
43
+ });
44
+ var __importStar = (this && this.__importStar) || (function () {
45
+ var ownKeys = function(o) {
46
+ ownKeys = Object.getOwnPropertyNames || function (o) {
47
+ var ar = [];
48
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
49
+ return ar;
50
+ };
51
+ return ownKeys(o);
52
+ };
53
+ return function (mod) {
54
+ if (mod && mod.__esModule) return mod;
55
+ var result = {};
56
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
57
+ __setModuleDefault(result, mod);
58
+ return result;
59
+ };
60
+ })();
61
+ Object.defineProperty(exports, "__esModule", { value: true });
62
+ exports.Plugin = exports.Config = exports.OpenTelemetryConfigSchema = void 0;
63
+ const base_1 = require("@bsb/base");
64
+ const zod_1 = require("zod");
65
+ const api = __importStar(require("@opentelemetry/api"));
66
+ const resources_1 = require("@opentelemetry/resources");
67
+ const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
68
+ const sdk_node_1 = require("@opentelemetry/sdk-node");
69
+ const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
70
+ const exporter_metrics_otlp_http_1 = require("@opentelemetry/exporter-metrics-otlp-http");
71
+ const exporter_logs_otlp_http_1 = require("@opentelemetry/exporter-logs-otlp-http");
72
+ const sdk_metrics_1 = require("@opentelemetry/sdk-metrics");
73
+ const sdk_logs_1 = require("@opentelemetry/sdk-logs");
74
+ /**
75
+ * Configuration schema for OpenTelemetry plugin
76
+ */
77
+ exports.OpenTelemetryConfigSchema = zod_1.z.object({
78
+ serviceName: zod_1.z.string().default("bsb-service"),
79
+ serviceVersion: zod_1.z.string().optional(),
80
+ endpoint: zod_1.z.string().url().default("http://localhost:4318"),
81
+ export: zod_1.z.object({
82
+ protocol: zod_1.z.enum(["http", "grpc"]).default("http"),
83
+ interval: zod_1.z.number().int().min(100).default(5000),
84
+ maxBatchSize: zod_1.z.number().int().min(1).default(512),
85
+ }),
86
+ enabled: zod_1.z.object({
87
+ traces: zod_1.z.boolean().default(true),
88
+ metrics: zod_1.z.boolean().default(true),
89
+ logs: zod_1.z.boolean().default(true),
90
+ }),
91
+ resourceAttributes: zod_1.z.record(zod_1.z.string(), zod_1.z.string()).default({}),
92
+ samplingRate: zod_1.z.number().min(0).max(1).default(1.0),
93
+ });
94
+ exports.Config = (0, base_1.createConfigSchema)({
95
+ name: 'observable-opentelemetry',
96
+ description: 'OpenTelemetry integration for logs, metrics, and traces via OTLP',
97
+ version: '9.0.0',
98
+ image: './observable-opentelemetry.png',
99
+ tags: ['opentelemetry', 'otlp', 'observability', 'logs', 'metrics', 'traces'],
100
+ documentation: ['./docs/plugin.md'],
101
+ }, exports.OpenTelemetryConfigSchema);
102
+ /**
103
+ * Convert BSB log level to OpenTelemetry severity number
104
+ */
105
+ function bsbLevelToOtelSeverity(level) {
106
+ const normalized = level.toLowerCase();
107
+ switch (normalized) {
108
+ case "debug":
109
+ return 5; // DEBUG
110
+ case "info":
111
+ return 9; // INFO
112
+ case "warn":
113
+ case "warning":
114
+ return 13; // WARN
115
+ case "error":
116
+ return 17; // ERROR
117
+ default:
118
+ return 9; // Default to INFO
119
+ }
120
+ }
121
+ /**
122
+ * Convert BSB log level to OpenTelemetry severity text
123
+ */
124
+ function bsbLevelToOtelSeverityText(level) {
125
+ const normalized = level.toLowerCase();
126
+ switch (normalized) {
127
+ case "debug":
128
+ return "DEBUG";
129
+ case "info":
130
+ return "INFO";
131
+ case "warn":
132
+ case "warning":
133
+ return "WARN";
134
+ case "error":
135
+ return "ERROR";
136
+ default:
137
+ return "INFO";
138
+ }
139
+ }
140
+ /**
141
+ * OpenTelemetry observable plugin with OTLP export
142
+ */
143
+ class Plugin extends base_1.BSBObservable {
144
+ static Config = exports.Config;
145
+ logFormatter = new base_1.LogFormatter();
146
+ sdk = null;
147
+ tracer = null;
148
+ meter = null;
149
+ logger = null;
150
+ loggerProvider = null;
151
+ isDisposed = false;
152
+ // Cache for metrics
153
+ counters = new Map();
154
+ gauges = new Map();
155
+ histograms = new Map();
156
+ // Cache for active spans
157
+ spans = new Map();
158
+ constructor(config) {
159
+ super(config);
160
+ }
161
+ async init() {
162
+ // Create resource with service information
163
+ const resource = new resources_1.Resource({
164
+ [semantic_conventions_1.ATTR_SERVICE_NAME]: this.config.serviceName,
165
+ ...(this.config.serviceVersion && { [semantic_conventions_1.ATTR_SERVICE_VERSION]: this.config.serviceVersion }),
166
+ ...this.config.resourceAttributes,
167
+ });
168
+ // Configure exporters
169
+ const traceExporter = new exporter_trace_otlp_http_1.OTLPTraceExporter({
170
+ url: `${this.config.endpoint}/v1/traces`,
171
+ });
172
+ const metricExporter = new exporter_metrics_otlp_http_1.OTLPMetricExporter({
173
+ url: `${this.config.endpoint}/v1/metrics`,
174
+ });
175
+ const logExporter = new exporter_logs_otlp_http_1.OTLPLogExporter({
176
+ url: `${this.config.endpoint}/v1/logs`,
177
+ });
178
+ // Initialize SDK
179
+ this.sdk = new sdk_node_1.NodeSDK({
180
+ resource,
181
+ traceExporter: this.config.enabled.traces ? traceExporter : undefined,
182
+ });
183
+ await this.sdk.start();
184
+ // Get tracer
185
+ if (this.config.enabled.traces) {
186
+ this.tracer = api.trace.getTracer(this.config.serviceName, this.config.serviceVersion);
187
+ }
188
+ // Setup metrics separately
189
+ if (this.config.enabled.metrics) {
190
+ const meterProvider = new sdk_metrics_1.MeterProvider({ resource });
191
+ meterProvider.addMetricReader(new sdk_metrics_1.PeriodicExportingMetricReader({
192
+ exporter: metricExporter,
193
+ exportIntervalMillis: this.config.export.interval,
194
+ }));
195
+ this.meter = meterProvider.getMeter(this.config.serviceName, this.config.serviceVersion);
196
+ }
197
+ // Setup logs separately
198
+ if (this.config.enabled.logs) {
199
+ this.loggerProvider = new sdk_logs_1.LoggerProvider({ resource });
200
+ this.loggerProvider.addLogRecordProcessor(new sdk_logs_1.BatchLogRecordProcessor(logExporter));
201
+ this.logger = this.loggerProvider.getLogger(this.config.serviceName, this.config.serviceVersion);
202
+ }
203
+ }
204
+ async run() {
205
+ // No runtime setup needed
206
+ }
207
+ /**
208
+ * Map DTrace to OpenTelemetry trace context
209
+ */
210
+ getTraceContext(trace) {
211
+ // Convert DTrace.t (trace ID) and DTrace.s (span ID) to OpenTelemetry format
212
+ // BSB uses string IDs, OpenTelemetry uses hex format
213
+ const traceId = trace.t.padStart(32, "0").substring(0, 32);
214
+ const spanId = trace.s.padStart(16, "0").substring(0, 16);
215
+ const spanContext = {
216
+ traceId,
217
+ spanId,
218
+ traceFlags: api.TraceFlags.SAMPLED,
219
+ };
220
+ return api.trace.setSpanContext(api.context.active(), spanContext);
221
+ }
222
+ /**
223
+ * Write log entry via OpenTelemetry
224
+ */
225
+ writeLog(level, trace, pluginName, message, meta) {
226
+ if (!this.logger || this.isDisposed) {
227
+ return;
228
+ }
229
+ const formattedMessage = this.logFormatter.formatLog(trace, message, meta);
230
+ const context = this.getTraceContext(trace);
231
+ this.logger.emit({
232
+ severityNumber: bsbLevelToOtelSeverity(level),
233
+ severityText: bsbLevelToOtelSeverityText(level),
234
+ body: formattedMessage,
235
+ attributes: {
236
+ "bsb.plugin": pluginName,
237
+ "bsb.trace.t": trace.t,
238
+ "bsb.trace.s": trace.s,
239
+ ...(meta && Object.keys(meta).length > 0 ? { "bsb.meta": JSON.stringify(meta) } : {}),
240
+ },
241
+ context,
242
+ });
243
+ }
244
+ // Logging methods
245
+ debug(trace, pluginName, message, meta) {
246
+ if (this.mode === "production") {
247
+ return;
248
+ }
249
+ this.writeLog("debug", trace, pluginName, message, meta);
250
+ }
251
+ info(trace, pluginName, message, meta) {
252
+ this.writeLog("info", trace, pluginName, message, meta);
253
+ }
254
+ warn(trace, pluginName, message, meta) {
255
+ this.writeLog("warn", trace, pluginName, message, meta);
256
+ }
257
+ error(trace, pluginName, message, meta) {
258
+ if (message instanceof base_1.BSBError) {
259
+ if (message.raw !== null) {
260
+ this.writeLog("error", message.raw.trace, pluginName, message.raw.message, message.raw.meta);
261
+ }
262
+ else {
263
+ this.writeLog("error", trace, pluginName, message.message);
264
+ }
265
+ }
266
+ else {
267
+ this.writeLog("error", trace, pluginName, message, meta);
268
+ }
269
+ }
270
+ // Metrics methods
271
+ createCounter(timestamp, pluginName, name, description, help, labels) {
272
+ if (!this.meter || this.isDisposed) {
273
+ return;
274
+ }
275
+ const fullName = `${pluginName}.${name}`;
276
+ if (!this.counters.has(fullName)) {
277
+ const counter = this.meter.createCounter(fullName, {
278
+ description: description || help,
279
+ });
280
+ this.counters.set(fullName, counter);
281
+ }
282
+ }
283
+ incrementCounter(timestamp, pluginName, name, value, labels) {
284
+ if (!this.meter || this.isDisposed) {
285
+ return;
286
+ }
287
+ const fullName = `${pluginName}.${name}`;
288
+ const counter = this.counters.get(fullName);
289
+ if (counter) {
290
+ counter.add(value, labels);
291
+ }
292
+ }
293
+ createGauge(timestamp, pluginName, name, description, help, labels) {
294
+ if (!this.meter || this.isDisposed) {
295
+ return;
296
+ }
297
+ const fullName = `${pluginName}.${name}`;
298
+ if (!this.gauges.has(fullName)) {
299
+ let currentValue = 0;
300
+ const gauge = this.meter.createObservableGauge(fullName, {
301
+ description: description || help,
302
+ });
303
+ gauge.addCallback((observableResult) => {
304
+ observableResult.observe(currentValue);
305
+ });
306
+ this.gauges.set(fullName, gauge);
307
+ }
308
+ }
309
+ setGauge(timestamp, pluginName, name, value, labels) {
310
+ // OpenTelemetry ObservableGauge doesn't support direct setting
311
+ // Would need to use UpDownCounter instead or maintain state
312
+ // For now, log a warning
313
+ console.warn(`[observable-opentelemetry] setGauge not fully supported in OpenTelemetry, use UpDownCounter`);
314
+ }
315
+ createHistogram(timestamp, pluginName, name, description, help, boundaries, labels) {
316
+ if (!this.meter || this.isDisposed) {
317
+ return;
318
+ }
319
+ const fullName = `${pluginName}.${name}`;
320
+ if (!this.histograms.has(fullName)) {
321
+ const histogram = this.meter.createHistogram(fullName, {
322
+ description: description || help,
323
+ });
324
+ this.histograms.set(fullName, histogram);
325
+ }
326
+ }
327
+ observeHistogram(timestamp, pluginName, name, value, labels) {
328
+ if (!this.meter || this.isDisposed) {
329
+ return;
330
+ }
331
+ const fullName = `${pluginName}.${name}`;
332
+ const histogram = this.histograms.get(fullName);
333
+ if (histogram) {
334
+ histogram.record(value, labels);
335
+ }
336
+ }
337
+ // Tracing methods
338
+ spanStart(trace, pluginName, spanName, parentSpanId, attributes) {
339
+ if (!this.tracer || this.isDisposed) {
340
+ return;
341
+ }
342
+ const context = this.getTraceContext(trace);
343
+ const span = this.tracer.startSpan(spanName, {
344
+ attributes: {
345
+ "bsb.plugin": pluginName,
346
+ ...(parentSpanId ? { "bsb.parent_span_id": parentSpanId } : {}),
347
+ ...attributes,
348
+ },
349
+ }, context);
350
+ const spanKey = `${trace.t}:${trace.s}`;
351
+ this.spans.set(spanKey, span);
352
+ }
353
+ spanEnd(trace, pluginName, attributes) {
354
+ if (this.isDisposed) {
355
+ return;
356
+ }
357
+ const spanKey = `${trace.t}:${trace.s}`;
358
+ const span = this.spans.get(spanKey);
359
+ if (span) {
360
+ if (attributes) {
361
+ span.setAttributes(attributes);
362
+ }
363
+ span.end();
364
+ this.spans.delete(spanKey);
365
+ }
366
+ }
367
+ spanError(trace, pluginName, error, attributes) {
368
+ if (this.isDisposed) {
369
+ return;
370
+ }
371
+ const spanKey = `${trace.t}:${trace.s}`;
372
+ const span = this.spans.get(spanKey);
373
+ if (span) {
374
+ span.recordException(error);
375
+ span.setStatus({ code: api.SpanStatusCode.ERROR, message: error.message });
376
+ if (attributes) {
377
+ span.setAttributes(attributes);
378
+ }
379
+ }
380
+ }
381
+ async dispose() {
382
+ this.isDisposed = true;
383
+ // End all active spans
384
+ for (const span of this.spans.values()) {
385
+ span.end();
386
+ }
387
+ this.spans.clear();
388
+ // Shutdown SDK
389
+ if (this.sdk) {
390
+ await this.sdk.shutdown();
391
+ this.sdk = null;
392
+ }
393
+ this.tracer = null;
394
+ this.meter = null;
395
+ this.logger = null;
396
+ this.counters.clear();
397
+ this.gauges.clear();
398
+ this.histograms.clear();
399
+ }
400
+ }
401
+ exports.Plugin = Plugin;