@logtape/otel 1.2.1 → 1.2.3

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/dist/deno.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  //#region deno.json
3
3
  var name = "@logtape/otel";
4
- var version = "1.2.1";
4
+ var version = "1.2.3";
5
5
  var license = "MIT";
6
6
  var exports$1 = { ".": "./src/mod.ts" };
7
7
  var imports = {
package/dist/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  //#region deno.json
2
2
  var name = "@logtape/otel";
3
- var version = "1.2.1";
3
+ var version = "1.2.3";
4
4
  var license = "MIT";
5
5
  var exports = { ".": "./src/mod.ts" };
6
6
  var imports = {
package/dist/deno.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"deno.js","names":[],"sources":["../deno.json"],"sourcesContent":["{\n \"name\": \"@logtape/otel\",\n \"version\": \"1.2.1\",\n \"license\": \"MIT\",\n \"exports\": {\n \".\": \"./src/mod.ts\"\n },\n \"imports\": {\n \"@opentelemetry/api\": \"npm:@opentelemetry/api@^1.9.0\",\n \"@opentelemetry/api-logs\": \"npm:@opentelemetry/api-logs@^0.202.0\",\n \"@opentelemetry/exporter-logs-otlp-http\": \"npm:@opentelemetry/exporter-logs-otlp-http@^0.202.0\",\n \"@opentelemetry/otlp-exporter-base\": \"npm:@opentelemetry/otlp-exporter-base@^0.202.0\",\n \"@opentelemetry/resources\": \"npm:@opentelemetry/resources@^2.0.1\",\n \"@opentelemetry/sdk-logs\": \"npm:@opentelemetry/sdk-logs@^0.202.0\",\n \"@opentelemetry/semantic-conventions\": \"npm:@opentelemetry/semantic-conventions@^1.34.0\"\n },\n \"tasks\": {\n \"build\": \"pnpm build\",\n \"test\": \"deno test --allow-net --allow-env\"\n }\n}\n"],"mappings":";WACU;cACG;cACA;cACA,EACT,KAAK,eACN;cACU;CACT,sBAAsB;CACtB,2BAA2B;CAC3B,0CAA0C;CAC1C,qCAAqC;CACrC,4BAA4B;CAC5B,2BAA2B;CAC3B,uCAAuC;AACxC;YACQ;CACP,SAAS;CACT,QAAQ;AACT;mBAnBH;;;;;;;AAoBC"}
1
+ {"version":3,"file":"deno.js","names":[],"sources":["../deno.json"],"sourcesContent":["{\n \"name\": \"@logtape/otel\",\n \"version\": \"1.2.3\",\n \"license\": \"MIT\",\n \"exports\": {\n \".\": \"./src/mod.ts\"\n },\n \"imports\": {\n \"@opentelemetry/api\": \"npm:@opentelemetry/api@^1.9.0\",\n \"@opentelemetry/api-logs\": \"npm:@opentelemetry/api-logs@^0.202.0\",\n \"@opentelemetry/exporter-logs-otlp-http\": \"npm:@opentelemetry/exporter-logs-otlp-http@^0.202.0\",\n \"@opentelemetry/otlp-exporter-base\": \"npm:@opentelemetry/otlp-exporter-base@^0.202.0\",\n \"@opentelemetry/resources\": \"npm:@opentelemetry/resources@^2.0.1\",\n \"@opentelemetry/sdk-logs\": \"npm:@opentelemetry/sdk-logs@^0.202.0\",\n \"@opentelemetry/semantic-conventions\": \"npm:@opentelemetry/semantic-conventions@^1.34.0\"\n },\n \"tasks\": {\n \"build\": \"pnpm build\",\n \"test\": \"deno test --allow-net --allow-env\"\n }\n}\n"],"mappings":";WACU;cACG;cACA;cACA,EACT,KAAK,eACN;cACU;CACT,sBAAsB;CACtB,2BAA2B;CAC3B,0CAA0C;CAC1C,qCAAqC;CACrC,4BAA4B;CAC5B,2BAA2B;CAC3B,uCAAuC;AACxC;YACQ;CACP,SAAS;CACT,QAAQ;AACT;mBAnBH;;;;;;;AAoBC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logtape/otel",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "LogTape OpenTelemetry sink",
5
5
  "keywords": [
6
6
  "LogTape",
@@ -41,8 +41,11 @@
41
41
  "./package.json": "./package.json"
42
42
  },
43
43
  "sideEffects": false,
44
+ "files": [
45
+ "dist/"
46
+ ],
44
47
  "peerDependencies": {
45
- "@logtape/logtape": "^1.2.1"
48
+ "@logtape/logtape": "^1.2.3"
46
49
  },
47
50
  "dependencies": {
48
51
  "@opentelemetry/api": "^1.9.0",
package/deno.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "name": "@logtape/otel",
3
- "version": "1.2.1",
4
- "license": "MIT",
5
- "exports": {
6
- ".": "./src/mod.ts"
7
- },
8
- "imports": {
9
- "@opentelemetry/api": "npm:@opentelemetry/api@^1.9.0",
10
- "@opentelemetry/api-logs": "npm:@opentelemetry/api-logs@^0.202.0",
11
- "@opentelemetry/exporter-logs-otlp-http": "npm:@opentelemetry/exporter-logs-otlp-http@^0.202.0",
12
- "@opentelemetry/otlp-exporter-base": "npm:@opentelemetry/otlp-exporter-base@^0.202.0",
13
- "@opentelemetry/resources": "npm:@opentelemetry/resources@^2.0.1",
14
- "@opentelemetry/sdk-logs": "npm:@opentelemetry/sdk-logs@^0.202.0",
15
- "@opentelemetry/semantic-conventions": "npm:@opentelemetry/semantic-conventions@^1.34.0"
16
- },
17
- "tasks": {
18
- "build": "pnpm build",
19
- "test": "deno test --allow-net --allow-env"
20
- }
21
- }
package/sample.ts DELETED
@@ -1,27 +0,0 @@
1
- import { configure, getConsoleSink, getLogger } from "@logtape/logtape";
2
- import { getOpenTelemetrySink } from "@logtape/otel";
3
- import "@std/dotenv/load";
4
-
5
- await configure({
6
- sinks: {
7
- console: getConsoleSink(),
8
- otel: getOpenTelemetrySink({
9
- messageType: "array",
10
- diagnostics: true,
11
- }),
12
- },
13
- filters: {},
14
- loggers: [
15
- { category: [], sinks: ["console", "otel"], lowestLevel: "debug" },
16
- ],
17
- });
18
-
19
- getLogger(["test", "app"]).debug("hello {world} at {timestamp}", {
20
- world: "debug",
21
- timestamp: new Date(),
22
- });
23
- getLogger(["test", "app"]).info("hello {world} with {object}", {
24
- world: "info",
25
- object: new Uint8Array([1, 2, 3]),
26
- });
27
- getLogger(["test", "app"]).warn("hello {world}", { world: "warning" });
package/src/mod.test.ts DELETED
@@ -1,71 +0,0 @@
1
- // TODO: Add substantial tests for OpenTelemetry integration.
2
- // Current tests only verify basic browser compatibility and that the sink
3
- // can be created without errors. Future tests should include:
4
- // - Actual log record processing and OpenTelemetry output verification
5
- // - Integration with real OpenTelemetry collectors
6
- // - Message formatting and attribute handling
7
- // - Error handling scenarios
8
- // - Performance testing
9
-
10
- import { suite } from "@alinea/suite";
11
- import { assertEquals } from "@std/assert";
12
- import { getOpenTelemetrySink } from "./mod.ts";
13
-
14
- const test = suite(import.meta);
15
-
16
- test("getOpenTelemetrySink() creates sink without node:process dependency", () => {
17
- // This test should pass in all environments (Deno, Node.js, browsers)
18
- // without throwing errors about missing node:process
19
- const sink = getOpenTelemetrySink();
20
-
21
- assertEquals(typeof sink, "function");
22
- });
23
-
24
- test("getOpenTelemetrySink() works with explicit serviceName", () => {
25
- const sink = getOpenTelemetrySink({
26
- serviceName: "test-service",
27
- });
28
-
29
- assertEquals(typeof sink, "function");
30
- });
31
-
32
- test("getOpenTelemetrySink() handles missing environment variables gracefully", () => {
33
- // Should not throw even if OTEL_SERVICE_NAME is not set
34
- const sink = getOpenTelemetrySink({
35
- // serviceName not provided, should fall back to env var
36
- });
37
-
38
- assertEquals(typeof sink, "function");
39
- });
40
-
41
- test("getOpenTelemetrySink() with diagnostics enabled", () => {
42
- const sink = getOpenTelemetrySink({
43
- diagnostics: true,
44
- });
45
-
46
- assertEquals(typeof sink, "function");
47
- });
48
-
49
- test("getOpenTelemetrySink() with custom messageType", () => {
50
- const sink = getOpenTelemetrySink({
51
- messageType: "array",
52
- });
53
-
54
- assertEquals(typeof sink, "function");
55
- });
56
-
57
- test("getOpenTelemetrySink() with custom objectRenderer", () => {
58
- const sink = getOpenTelemetrySink({
59
- objectRenderer: "json",
60
- });
61
-
62
- assertEquals(typeof sink, "function");
63
- });
64
-
65
- test("getOpenTelemetrySink() with custom bodyFormatter", () => {
66
- const sink = getOpenTelemetrySink({
67
- messageType: (message) => message.join(" "),
68
- });
69
-
70
- assertEquals(typeof sink, "function");
71
- });
package/src/mod.ts DELETED
@@ -1,369 +0,0 @@
1
- import {
2
- getLogger,
3
- type Logger,
4
- type LogRecord,
5
- type Sink,
6
- } from "@logtape/logtape";
7
- import { diag, type DiagLogger, DiagLogLevel } from "@opentelemetry/api";
8
- import {
9
- type AnyValue,
10
- type LoggerProvider as LoggerProviderBase,
11
- type LogRecord as OTLogRecord,
12
- SeverityNumber,
13
- } from "@opentelemetry/api-logs";
14
- import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
15
- import type { OTLPExporterNodeConfigBase } from "@opentelemetry/otlp-exporter-base";
16
- import {
17
- defaultResource,
18
- resourceFromAttributes,
19
- } from "@opentelemetry/resources";
20
- import {
21
- LoggerProvider,
22
- type LogRecordProcessor,
23
- SimpleLogRecordProcessor,
24
- } from "@opentelemetry/sdk-logs";
25
- import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
26
- import metadata from "../deno.json" with { type: "json" };
27
-
28
- /**
29
- * Gets an environment variable value across different JavaScript runtimes.
30
- * @param name The environment variable name.
31
- * @returns The environment variable value, or undefined if not found.
32
- */
33
- function getEnvironmentVariable(name: string): string | undefined {
34
- // Deno runtime
35
- if (typeof Deno !== "undefined" && Deno.env) {
36
- try {
37
- return Deno.env.get(name);
38
- } catch {
39
- // Deno.env.get() can throw if permissions are not granted
40
- return undefined;
41
- }
42
- }
43
-
44
- // Node.js/Bun runtime
45
- if (
46
- typeof globalThis !== "undefined" && "process" in globalThis &&
47
- // @ts-ignore: process exists in Node.js/Bun
48
- typeof globalThis.process !== "undefined" &&
49
- // @ts-ignore: process.env exists in Node.js/Bun
50
- typeof globalThis.process.env === "object" &&
51
- // @ts-ignore: process.env exists in Node.js/Bun
52
- globalThis.process.env !== null
53
- ) {
54
- // @ts-ignore: process.env exists in Node.js/Bun
55
- return globalThis.process.env[name];
56
- }
57
-
58
- // Browser/other environments - no environment variables available
59
- return undefined;
60
- }
61
-
62
- /**
63
- * The OpenTelemetry logger provider.
64
- */
65
- type ILoggerProvider = LoggerProviderBase & {
66
- /**
67
- * Adds a new {@link LogRecordProcessor} to this logger.
68
- * @param processor the new LogRecordProcessor to be added.
69
- */
70
- addLogRecordProcessor(processor: LogRecordProcessor): void;
71
-
72
- /**
73
- * Flush all buffered data and shut down the LoggerProvider and all registered
74
- * LogRecordProcessor.
75
- *
76
- * Returns a promise which is resolved when all flushes are complete.
77
- */
78
- shutdown?: () => Promise<void>;
79
- };
80
-
81
- /**
82
- * The way to render the object in the log record. If `"json"`,
83
- * the object is rendered as a JSON string. If `"inspect"`,
84
- * the object is rendered using `util.inspect` in Node.js/Bun, or
85
- * `Deno.inspect` in Deno.
86
- */
87
- export type ObjectRenderer = "json" | "inspect";
88
-
89
- type Message = (string | null | undefined)[];
90
-
91
- /**
92
- * Custom `body` attribute formatter.
93
- * @since 0.3.0
94
- */
95
- export type BodyFormatter = (message: Message) => AnyValue;
96
-
97
- /**
98
- * Options for creating an OpenTelemetry sink.
99
- */
100
- export interface OpenTelemetrySinkOptions {
101
- /**
102
- * The OpenTelemetry logger provider to use.
103
- */
104
- loggerProvider?: ILoggerProvider;
105
-
106
- /**
107
- * The way to render the message in the log record. If `"string"`,
108
- * the message is rendered as a single string with the values are
109
- * interpolated into the message. If `"array"`, the message is
110
- * rendered as an array of strings. `"string"` by default.
111
- *
112
- * Or even fully customizable with a {@link BodyFormatter} function.
113
- * @since 0.2.0
114
- */
115
- messageType?: "string" | "array" | BodyFormatter;
116
-
117
- /**
118
- * The way to render the object in the log record. If `"json"`,
119
- * the object is rendered as a JSON string. If `"inspect"`,
120
- * the object is rendered using `util.inspect` in Node.js/Bun, or
121
- * `Deno.inspect` in Deno. `"inspect"` by default.
122
- */
123
- objectRenderer?: ObjectRenderer;
124
-
125
- /**
126
- * Whether to log diagnostics. Diagnostic logs are logged to
127
- * the `["logtape", "meta", "otel"]` category.
128
- * Turned off by default.
129
- */
130
- diagnostics?: boolean;
131
-
132
- /**
133
- * The OpenTelemetry OTLP exporter configuration to use.
134
- * Ignored if `loggerProvider` is provided.
135
- */
136
- otlpExporterConfig?: OTLPExporterNodeConfigBase;
137
-
138
- /**
139
- * The service name to use. If not provided, the service name is
140
- * taken from the `OTEL_SERVICE_NAME` environment variable.
141
- * Ignored if `loggerProvider` is provided.
142
- */
143
- serviceName?: string;
144
- }
145
-
146
- /**
147
- * Creates a sink that forwards log records to OpenTelemetry.
148
- * @param options Options for creating the sink.
149
- * @returns The sink.
150
- */
151
- export function getOpenTelemetrySink(
152
- options: OpenTelemetrySinkOptions = {},
153
- ): Sink {
154
- if (options.diagnostics) {
155
- diag.setLogger(new DiagLoggerAdaptor(), DiagLogLevel.DEBUG);
156
- }
157
-
158
- let loggerProvider: ILoggerProvider;
159
- if (options.loggerProvider == null) {
160
- const resource = defaultResource().merge(
161
- resourceFromAttributes({
162
- [ATTR_SERVICE_NAME]: options.serviceName ??
163
- getEnvironmentVariable("OTEL_SERVICE_NAME"),
164
- }),
165
- );
166
- loggerProvider = new LoggerProvider({ resource });
167
- const otlpExporter = new OTLPLogExporter(options.otlpExporterConfig);
168
- loggerProvider.addLogRecordProcessor(
169
- // @ts-ignore: it works anyway...
170
- new SimpleLogRecordProcessor(otlpExporter),
171
- );
172
- } else {
173
- loggerProvider = options.loggerProvider;
174
- }
175
- const objectRenderer = options.objectRenderer ?? "inspect";
176
- const logger = loggerProvider.getLogger(metadata.name, metadata.version);
177
- const sink = (record: LogRecord) => {
178
- const { category, level, message, timestamp, properties } = record;
179
- if (
180
- category[0] === "logtape" && category[1] === "meta" &&
181
- category[2] === "otel"
182
- ) {
183
- return;
184
- }
185
- const severityNumber = mapLevelToSeverityNumber(level);
186
- const attributes = convertToAttributes(properties, objectRenderer);
187
- attributes["category"] = [...category];
188
- logger.emit(
189
- {
190
- severityNumber,
191
- severityText: level,
192
- body: typeof options.messageType === "function"
193
- ? convertMessageToCustomBodyFormat(
194
- message,
195
- objectRenderer,
196
- options.messageType,
197
- )
198
- : options.messageType === "array"
199
- ? convertMessageToArray(message, objectRenderer)
200
- : convertMessageToString(message, objectRenderer),
201
- attributes,
202
- timestamp: new Date(timestamp),
203
- } satisfies OTLogRecord,
204
- );
205
- };
206
- if (loggerProvider.shutdown != null) {
207
- const shutdown = loggerProvider.shutdown.bind(loggerProvider);
208
- sink[Symbol.asyncDispose] = shutdown;
209
- }
210
- return sink;
211
- }
212
-
213
- function mapLevelToSeverityNumber(level: string): number {
214
- switch (level) {
215
- case "trace":
216
- return SeverityNumber.TRACE;
217
- case "debug":
218
- return SeverityNumber.DEBUG;
219
- case "info":
220
- return SeverityNumber.INFO;
221
- case "warning":
222
- return SeverityNumber.WARN;
223
- case "error":
224
- return SeverityNumber.ERROR;
225
- case "fatal":
226
- return SeverityNumber.FATAL;
227
- default:
228
- return SeverityNumber.UNSPECIFIED;
229
- }
230
- }
231
-
232
- function convertToAttributes(
233
- properties: Record<string, unknown>,
234
- objectRenderer: ObjectRenderer,
235
- ): Record<string, AnyValue> {
236
- const attributes: Record<string, AnyValue> = {};
237
- for (const [name, value] of Object.entries(properties)) {
238
- const key = `attributes.${name}`;
239
- if (value == null) continue;
240
- if (Array.isArray(value)) {
241
- let t = null;
242
- for (const v of value) {
243
- if (v == null) continue;
244
- if (t != null && typeof v !== t) {
245
- attributes[key] = value.map((v) =>
246
- convertToString(v, objectRenderer)
247
- );
248
- break;
249
- }
250
- t = typeof v;
251
- }
252
- attributes[key] = value;
253
- } else {
254
- const encoded = convertToString(value, objectRenderer);
255
- if (encoded == null) continue;
256
- attributes[key] = encoded;
257
- }
258
- }
259
- return attributes;
260
- }
261
-
262
- function convertToString(
263
- value: unknown,
264
- objectRenderer: ObjectRenderer,
265
- ): string | null | undefined {
266
- if (value === null || value === undefined || typeof value === "string") {
267
- return value;
268
- }
269
- if (objectRenderer === "inspect") return inspect(value);
270
- if (typeof value === "number" || typeof value === "boolean") {
271
- return value.toString();
272
- } else if (value instanceof Date) return value.toISOString();
273
- else return JSON.stringify(value);
274
- }
275
-
276
- function convertMessageToArray(
277
- message: readonly unknown[],
278
- objectRenderer: ObjectRenderer,
279
- ): AnyValue {
280
- const body: (string | null | undefined)[] = [];
281
- for (let i = 0; i < message.length; i += 2) {
282
- const msg = message[i] as string;
283
- body.push(msg);
284
- if (message.length <= i + 1) break;
285
- const val = message[i + 1];
286
- body.push(convertToString(val, objectRenderer));
287
- }
288
- return body;
289
- }
290
-
291
- function convertMessageToString(
292
- message: readonly unknown[],
293
- objectRenderer: ObjectRenderer,
294
- ): AnyValue {
295
- let body = "";
296
- for (let i = 0; i < message.length; i += 2) {
297
- const msg = message[i] as string;
298
- body += msg;
299
- if (message.length <= i + 1) break;
300
- const val = message[i + 1];
301
- const extra = convertToString(val, objectRenderer);
302
- body += extra ?? JSON.stringify(extra);
303
- }
304
- return body;
305
- }
306
-
307
- function convertMessageToCustomBodyFormat(
308
- message: readonly unknown[],
309
- objectRenderer: ObjectRenderer,
310
- bodyFormatter: BodyFormatter,
311
- ): AnyValue {
312
- const body = message.map((msg) => convertToString(msg, objectRenderer));
313
- return bodyFormatter(body);
314
- }
315
-
316
- /**
317
- * A platform-specific inspect function. In Deno, this is {@link Deno.inspect},
318
- * and in Node.js/Bun it is {@link util.inspect}. If neither is available, it
319
- * falls back to {@link JSON.stringify}.
320
- *
321
- * @param value The value to inspect.
322
- * @returns The string representation of the value.
323
- */
324
- const inspect: (value: unknown) => string =
325
- // @ts-ignore: Deno global
326
- "Deno" in globalThis && "inspect" in globalThis.Deno &&
327
- // @ts-ignore: Deno global
328
- typeof globalThis.Deno.inspect === "function"
329
- // @ts-ignore: Deno global
330
- ? globalThis.Deno.inspect
331
- // @ts-ignore: Node.js global
332
- : "util" in globalThis && "inspect" in globalThis.util &&
333
- // @ts-ignore: Node.js global
334
- globalThis.util.inspect === "function"
335
- // @ts-ignore: Node.js global
336
- ? globalThis.util.inspect
337
- : JSON.stringify;
338
-
339
- class DiagLoggerAdaptor implements DiagLogger {
340
- logger: Logger;
341
-
342
- constructor() {
343
- this.logger = getLogger(["logtape", "meta", "otel"]);
344
- }
345
-
346
- #escape(msg: string): string {
347
- return msg.replaceAll("{", "{{").replaceAll("}", "}}");
348
- }
349
-
350
- error(msg: string, ...values: unknown[]): void {
351
- this.logger.error(`${this.#escape(msg)}: {values}`, { values });
352
- }
353
-
354
- warn(msg: string, ...values: unknown[]): void {
355
- this.logger.warn(`${this.#escape(msg)}: {values}`, { values });
356
- }
357
-
358
- info(msg: string, ...values: unknown[]): void {
359
- this.logger.info(`${this.#escape(msg)}: {values}`, { values });
360
- }
361
-
362
- debug(msg: string, ...values: unknown[]): void {
363
- this.logger.debug(`${this.#escape(msg)}: {values}`, { values });
364
- }
365
-
366
- verbose(msg: string, ...values: unknown[]): void {
367
- this.logger.debug(`${this.#escape(msg)}: {values}`, { values });
368
- }
369
- }
package/tsdown.config.ts DELETED
@@ -1,11 +0,0 @@
1
- import { defineConfig } from "tsdown";
2
-
3
- export default defineConfig({
4
- entry: "src/mod.ts",
5
- dts: {
6
- sourcemap: true,
7
- },
8
- format: ["esm", "cjs"],
9
- platform: "neutral",
10
- unbundle: true,
11
- });