@logtape/otel 0.4.0-dev.8 → 0.12.0-dev.190
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 +9 -53
- package/deno.json +18 -0
- package/dist/_virtual/rolldown_runtime.cjs +30 -0
- package/dist/deno.cjs +31 -0
- package/dist/deno.js +26 -0
- package/dist/deno.js.map +1 -0
- package/dist/mod.cjs +157 -0
- package/dist/mod.d.cts +89 -0
- package/dist/mod.d.cts.map +1 -0
- package/dist/mod.d.ts +89 -0
- package/dist/mod.d.ts.map +1 -0
- package/dist/mod.js +157 -0
- package/dist/mod.js.map +1 -0
- package/mod.ts +336 -0
- package/package.json +34 -33
- package/sample.ts +27 -0
- package/tsdown.config.ts +11 -0
- package/esm/_dnt.shims.js +0 -57
- package/esm/deno.js +0 -24
- package/esm/mod.js +0 -210
- package/esm/package.json +0 -3
- package/script/_dnt.shims.js +0 -60
- package/script/deno.js +0 -26
- package/script/mod.js +0 -239
- package/script/package.json +0 -3
- package/types/_dnt.shims.d.ts +0 -2
- package/types/_dnt.shims.d.ts.map +0 -1
- package/types/deno.d.ts +0 -26
- package/types/deno.d.ts.map +0 -1
- package/types/mod.d.ts +0 -85
- package/types/mod.d.ts.map +0 -1
package/dist/mod.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import deno_default from "./deno.js";
|
|
2
|
+
import { getLogger } from "@logtape/logtape";
|
|
3
|
+
import { DiagLogLevel, diag } from "@opentelemetry/api";
|
|
4
|
+
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
5
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
6
|
+
import { defaultResource, resourceFromAttributes } from "@opentelemetry/resources";
|
|
7
|
+
import { LoggerProvider as LoggerProvider$1, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
8
|
+
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
|
|
9
|
+
import process from "node:process";
|
|
10
|
+
|
|
11
|
+
//#region mod.ts
|
|
12
|
+
/**
|
|
13
|
+
* Creates a sink that forwards log records to OpenTelemetry.
|
|
14
|
+
* @param options Options for creating the sink.
|
|
15
|
+
* @returns The sink.
|
|
16
|
+
*/
|
|
17
|
+
function getOpenTelemetrySink(options = {}) {
|
|
18
|
+
if (options.diagnostics) diag.setLogger(new DiagLoggerAdaptor(), DiagLogLevel.DEBUG);
|
|
19
|
+
let loggerProvider;
|
|
20
|
+
if (options.loggerProvider == null) {
|
|
21
|
+
const resource = defaultResource().merge(resourceFromAttributes({ [ATTR_SERVICE_NAME]: options.serviceName ?? process.env.OTEL_SERVICE_NAME }));
|
|
22
|
+
loggerProvider = new LoggerProvider$1({ resource });
|
|
23
|
+
const otlpExporter = new OTLPLogExporter(options.otlpExporterConfig);
|
|
24
|
+
loggerProvider.addLogRecordProcessor(new SimpleLogRecordProcessor(otlpExporter));
|
|
25
|
+
} else loggerProvider = options.loggerProvider;
|
|
26
|
+
const objectRenderer = options.objectRenderer ?? "inspect";
|
|
27
|
+
const logger = loggerProvider.getLogger(deno_default.name, deno_default.version);
|
|
28
|
+
const sink = (record) => {
|
|
29
|
+
const { category, level, message, timestamp, properties } = record;
|
|
30
|
+
if (category[0] === "logtape" && category[1] === "meta" && category[2] === "otel") return;
|
|
31
|
+
const severityNumber = mapLevelToSeverityNumber(level);
|
|
32
|
+
const attributes = convertToAttributes(properties, objectRenderer);
|
|
33
|
+
attributes["category"] = [...category];
|
|
34
|
+
logger.emit({
|
|
35
|
+
severityNumber,
|
|
36
|
+
severityText: level,
|
|
37
|
+
body: typeof options.messageType === "function" ? convertMessageToCustomBodyFormat(message, objectRenderer, options.messageType) : options.messageType === "array" ? convertMessageToArray(message, objectRenderer) : convertMessageToString(message, objectRenderer),
|
|
38
|
+
attributes,
|
|
39
|
+
timestamp: new Date(timestamp)
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
if (loggerProvider.shutdown != null) {
|
|
43
|
+
const shutdown = loggerProvider.shutdown.bind(loggerProvider);
|
|
44
|
+
sink[Symbol.asyncDispose] = shutdown;
|
|
45
|
+
}
|
|
46
|
+
return sink;
|
|
47
|
+
}
|
|
48
|
+
function mapLevelToSeverityNumber(level) {
|
|
49
|
+
switch (level) {
|
|
50
|
+
case "trace": return SeverityNumber.TRACE;
|
|
51
|
+
case "debug": return SeverityNumber.DEBUG;
|
|
52
|
+
case "info": return SeverityNumber.INFO;
|
|
53
|
+
case "warning": return SeverityNumber.WARN;
|
|
54
|
+
case "error": return SeverityNumber.ERROR;
|
|
55
|
+
case "fatal": return SeverityNumber.FATAL;
|
|
56
|
+
default: return SeverityNumber.UNSPECIFIED;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function convertToAttributes(properties, objectRenderer) {
|
|
60
|
+
const attributes = {};
|
|
61
|
+
for (const [name, value] of Object.entries(properties)) {
|
|
62
|
+
const key = `attributes.${name}`;
|
|
63
|
+
if (value == null) continue;
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
let t = null;
|
|
66
|
+
for (const v of value) {
|
|
67
|
+
if (v == null) continue;
|
|
68
|
+
if (t != null && typeof v !== t) {
|
|
69
|
+
attributes[key] = value.map((v$1) => convertToString(v$1, objectRenderer));
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
t = typeof v;
|
|
73
|
+
}
|
|
74
|
+
attributes[key] = value;
|
|
75
|
+
} else {
|
|
76
|
+
const encoded = convertToString(value, objectRenderer);
|
|
77
|
+
if (encoded == null) continue;
|
|
78
|
+
attributes[key] = encoded;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return attributes;
|
|
82
|
+
}
|
|
83
|
+
function convertToString(value, objectRenderer) {
|
|
84
|
+
if (value === null || value === void 0 || typeof value === "string") return value;
|
|
85
|
+
if (objectRenderer === "inspect") return inspect(value);
|
|
86
|
+
if (typeof value === "number" || typeof value === "boolean") return value.toString();
|
|
87
|
+
else if (value instanceof Date) return value.toISOString();
|
|
88
|
+
else return JSON.stringify(value);
|
|
89
|
+
}
|
|
90
|
+
function convertMessageToArray(message, objectRenderer) {
|
|
91
|
+
const body = [];
|
|
92
|
+
for (let i = 0; i < message.length; i += 2) {
|
|
93
|
+
const msg = message[i];
|
|
94
|
+
body.push(msg);
|
|
95
|
+
if (message.length <= i + 1) break;
|
|
96
|
+
const val = message[i + 1];
|
|
97
|
+
body.push(convertToString(val, objectRenderer));
|
|
98
|
+
}
|
|
99
|
+
return body;
|
|
100
|
+
}
|
|
101
|
+
function convertMessageToString(message, objectRenderer) {
|
|
102
|
+
let body = "";
|
|
103
|
+
for (let i = 0; i < message.length; i += 2) {
|
|
104
|
+
const msg = message[i];
|
|
105
|
+
body += msg;
|
|
106
|
+
if (message.length <= i + 1) break;
|
|
107
|
+
const val = message[i + 1];
|
|
108
|
+
const extra = convertToString(val, objectRenderer);
|
|
109
|
+
body += extra ?? JSON.stringify(extra);
|
|
110
|
+
}
|
|
111
|
+
return body;
|
|
112
|
+
}
|
|
113
|
+
function convertMessageToCustomBodyFormat(message, objectRenderer, bodyFormatter) {
|
|
114
|
+
const body = message.map((msg) => convertToString(msg, objectRenderer));
|
|
115
|
+
return bodyFormatter(body);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A platform-specific inspect function. In Deno, this is {@link Deno.inspect},
|
|
119
|
+
* and in Node.js/Bun it is {@link util.inspect}. If neither is available, it
|
|
120
|
+
* falls back to {@link JSON.stringify}.
|
|
121
|
+
*
|
|
122
|
+
* @param value The value to inspect.
|
|
123
|
+
* @returns The string representation of the value.
|
|
124
|
+
*/
|
|
125
|
+
const inspect = "Deno" in globalThis && "inspect" in globalThis.Deno && typeof globalThis.Deno.inspect === "function" ? globalThis.Deno.inspect : "util" in globalThis && "inspect" in globalThis.util && globalThis.util.inspect === "function" ? globalThis.util.inspect : JSON.stringify;
|
|
126
|
+
var DiagLoggerAdaptor = class {
|
|
127
|
+
logger;
|
|
128
|
+
constructor() {
|
|
129
|
+
this.logger = getLogger([
|
|
130
|
+
"logtape",
|
|
131
|
+
"meta",
|
|
132
|
+
"otel"
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
#escape(msg) {
|
|
136
|
+
return msg.replaceAll("{", "{{").replaceAll("}", "}}");
|
|
137
|
+
}
|
|
138
|
+
error(msg, ...values) {
|
|
139
|
+
this.logger.error(`${this.#escape(msg)}: {values}`, { values });
|
|
140
|
+
}
|
|
141
|
+
warn(msg, ...values) {
|
|
142
|
+
this.logger.warn(`${this.#escape(msg)}: {values}`, { values });
|
|
143
|
+
}
|
|
144
|
+
info(msg, ...values) {
|
|
145
|
+
this.logger.info(`${this.#escape(msg)}: {values}`, { values });
|
|
146
|
+
}
|
|
147
|
+
debug(msg, ...values) {
|
|
148
|
+
this.logger.debug(`${this.#escape(msg)}: {values}`, { values });
|
|
149
|
+
}
|
|
150
|
+
verbose(msg, ...values) {
|
|
151
|
+
this.logger.debug(`${this.#escape(msg)}: {values}`, { values });
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
export { getOpenTelemetrySink };
|
|
157
|
+
//# sourceMappingURL=mod.js.map
|
package/dist/mod.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mod.js","names":["options: OpenTelemetrySinkOptions","loggerProvider: ILoggerProvider","LoggerProvider","metadata","record: LogRecord","level: string","properties: Record<string, unknown>","objectRenderer: ObjectRenderer","attributes: Record<string, AnyValue>","v","value: unknown","message: readonly unknown[]","body: (string | null | undefined)[]","bodyFormatter: BodyFormatter","inspect: (value: unknown) => string","msg: string","#escape"],"sources":["../mod.ts"],"sourcesContent":["import {\n getLogger,\n type Logger,\n type LogRecord,\n type Sink,\n} from \"@logtape/logtape\";\nimport { diag, type DiagLogger, DiagLogLevel } from \"@opentelemetry/api\";\nimport {\n type AnyValue,\n type LoggerProvider as LoggerProviderBase,\n type LogRecord as OTLogRecord,\n SeverityNumber,\n} from \"@opentelemetry/api-logs\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-http\";\nimport type { OTLPExporterNodeConfigBase } from \"@opentelemetry/otlp-exporter-base\";\nimport {\n defaultResource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport {\n LoggerProvider,\n type LogRecordProcessor,\n SimpleLogRecordProcessor,\n} from \"@opentelemetry/sdk-logs\";\nimport { ATTR_SERVICE_NAME } from \"@opentelemetry/semantic-conventions\";\nimport process from \"node:process\";\nimport metadata from \"./deno.json\" with { type: \"json\" };\n\n/**\n * The OpenTelemetry logger provider.\n */\ntype ILoggerProvider = LoggerProviderBase & {\n /**\n * Adds a new {@link LogRecordProcessor} to this logger.\n * @param processor the new LogRecordProcessor to be added.\n */\n addLogRecordProcessor(processor: LogRecordProcessor): void;\n\n /**\n * Flush all buffered data and shut down the LoggerProvider and all registered\n * LogRecordProcessor.\n *\n * Returns a promise which is resolved when all flushes are complete.\n */\n shutdown?: () => Promise<void>;\n};\n\n/**\n * The way to render the object in the log record. If `\"json\"`,\n * the object is rendered as a JSON string. If `\"inspect\"`,\n * the object is rendered using `util.inspect` in Node.js/Bun, or\n * `Deno.inspect` in Deno.\n */\nexport type ObjectRenderer = \"json\" | \"inspect\";\n\ntype Message = (string | null | undefined)[];\n\n/**\n * Custom `body` attribute formatter.\n * @since 0.3.0\n */\nexport type BodyFormatter = (message: Message) => AnyValue;\n\n/**\n * Options for creating an OpenTelemetry sink.\n */\nexport interface OpenTelemetrySinkOptions {\n /**\n * The OpenTelemetry logger provider to use.\n */\n loggerProvider?: ILoggerProvider;\n\n /**\n * The way to render the message in the log record. If `\"string\"`,\n * the message is rendered as a single string with the values are\n * interpolated into the message. If `\"array\"`, the message is\n * rendered as an array of strings. `\"string\"` by default.\n *\n * Or even fully customizable with a {@link BodyFormatter} function.\n * @since 0.2.0\n */\n messageType?: \"string\" | \"array\" | BodyFormatter;\n\n /**\n * The way to render the object in the log record. If `\"json\"`,\n * the object is rendered as a JSON string. If `\"inspect\"`,\n * the object is rendered using `util.inspect` in Node.js/Bun, or\n * `Deno.inspect` in Deno. `\"inspect\"` by default.\n */\n objectRenderer?: ObjectRenderer;\n\n /**\n * Whether to log diagnostics. Diagnostic logs are logged to\n * the `[\"logtape\", \"meta\", \"otel\"]` category.\n * Turned off by default.\n */\n diagnostics?: boolean;\n\n /**\n * The OpenTelemetry OTLP exporter configuration to use.\n * Ignored if `loggerProvider` is provided.\n */\n otlpExporterConfig?: OTLPExporterNodeConfigBase;\n\n /**\n * The service name to use. If not provided, the service name is\n * taken from the `OTEL_SERVICE_NAME` environment variable.\n * Ignored if `loggerProvider` is provided.\n */\n serviceName?: string;\n}\n\n/**\n * Creates a sink that forwards log records to OpenTelemetry.\n * @param options Options for creating the sink.\n * @returns The sink.\n */\nexport function getOpenTelemetrySink(\n options: OpenTelemetrySinkOptions = {},\n): Sink {\n if (options.diagnostics) {\n diag.setLogger(new DiagLoggerAdaptor(), DiagLogLevel.DEBUG);\n }\n\n let loggerProvider: ILoggerProvider;\n if (options.loggerProvider == null) {\n const resource = defaultResource().merge(\n resourceFromAttributes({\n [ATTR_SERVICE_NAME]: options.serviceName ??\n process.env.OTEL_SERVICE_NAME,\n }),\n );\n loggerProvider = new LoggerProvider({ resource });\n const otlpExporter = new OTLPLogExporter(options.otlpExporterConfig);\n loggerProvider.addLogRecordProcessor(\n // @ts-ignore: it works anyway...\n new SimpleLogRecordProcessor(otlpExporter),\n );\n } else {\n loggerProvider = options.loggerProvider;\n }\n const objectRenderer = options.objectRenderer ?? \"inspect\";\n const logger = loggerProvider.getLogger(metadata.name, metadata.version);\n const sink = (record: LogRecord) => {\n const { category, level, message, timestamp, properties } = record;\n if (\n category[0] === \"logtape\" && category[1] === \"meta\" &&\n category[2] === \"otel\"\n ) {\n return;\n }\n const severityNumber = mapLevelToSeverityNumber(level);\n const attributes = convertToAttributes(properties, objectRenderer);\n attributes[\"category\"] = [...category];\n logger.emit(\n {\n severityNumber,\n severityText: level,\n body: typeof options.messageType === \"function\"\n ? convertMessageToCustomBodyFormat(\n message,\n objectRenderer,\n options.messageType,\n )\n : options.messageType === \"array\"\n ? convertMessageToArray(message, objectRenderer)\n : convertMessageToString(message, objectRenderer),\n attributes,\n timestamp: new Date(timestamp),\n } satisfies OTLogRecord,\n );\n };\n if (loggerProvider.shutdown != null) {\n const shutdown = loggerProvider.shutdown.bind(loggerProvider);\n sink[Symbol.asyncDispose] = shutdown;\n }\n return sink;\n}\n\nfunction mapLevelToSeverityNumber(level: string): number {\n switch (level) {\n case \"trace\":\n return SeverityNumber.TRACE;\n case \"debug\":\n return SeverityNumber.DEBUG;\n case \"info\":\n return SeverityNumber.INFO;\n case \"warning\":\n return SeverityNumber.WARN;\n case \"error\":\n return SeverityNumber.ERROR;\n case \"fatal\":\n return SeverityNumber.FATAL;\n default:\n return SeverityNumber.UNSPECIFIED;\n }\n}\n\nfunction convertToAttributes(\n properties: Record<string, unknown>,\n objectRenderer: ObjectRenderer,\n): Record<string, AnyValue> {\n const attributes: Record<string, AnyValue> = {};\n for (const [name, value] of Object.entries(properties)) {\n const key = `attributes.${name}`;\n if (value == null) continue;\n if (Array.isArray(value)) {\n let t = null;\n for (const v of value) {\n if (v == null) continue;\n if (t != null && typeof v !== t) {\n attributes[key] = value.map((v) =>\n convertToString(v, objectRenderer)\n );\n break;\n }\n t = typeof v;\n }\n attributes[key] = value;\n } else {\n const encoded = convertToString(value, objectRenderer);\n if (encoded == null) continue;\n attributes[key] = encoded;\n }\n }\n return attributes;\n}\n\nfunction convertToString(\n value: unknown,\n objectRenderer: ObjectRenderer,\n): string | null | undefined {\n if (value === null || value === undefined || typeof value === \"string\") {\n return value;\n }\n if (objectRenderer === \"inspect\") return inspect(value);\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return value.toString();\n } else if (value instanceof Date) return value.toISOString();\n else return JSON.stringify(value);\n}\n\nfunction convertMessageToArray(\n message: readonly unknown[],\n objectRenderer: ObjectRenderer,\n): AnyValue {\n const body: (string | null | undefined)[] = [];\n for (let i = 0; i < message.length; i += 2) {\n const msg = message[i] as string;\n body.push(msg);\n if (message.length <= i + 1) break;\n const val = message[i + 1];\n body.push(convertToString(val, objectRenderer));\n }\n return body;\n}\n\nfunction convertMessageToString(\n message: readonly unknown[],\n objectRenderer: ObjectRenderer,\n): AnyValue {\n let body = \"\";\n for (let i = 0; i < message.length; i += 2) {\n const msg = message[i] as string;\n body += msg;\n if (message.length <= i + 1) break;\n const val = message[i + 1];\n const extra = convertToString(val, objectRenderer);\n body += extra ?? JSON.stringify(extra);\n }\n return body;\n}\n\nfunction convertMessageToCustomBodyFormat(\n message: readonly unknown[],\n objectRenderer: ObjectRenderer,\n bodyFormatter: BodyFormatter,\n): AnyValue {\n const body = message.map((msg) => convertToString(msg, objectRenderer));\n return bodyFormatter(body);\n}\n\n/**\n * A platform-specific inspect function. In Deno, this is {@link Deno.inspect},\n * and in Node.js/Bun it is {@link util.inspect}. If neither is available, it\n * falls back to {@link JSON.stringify}.\n *\n * @param value The value to inspect.\n * @returns The string representation of the value.\n */\nconst inspect: (value: unknown) => string =\n // @ts-ignore: Deno global\n \"Deno\" in globalThis && \"inspect\" in globalThis.Deno &&\n // @ts-ignore: Deno global\n typeof globalThis.Deno.inspect === \"function\"\n // @ts-ignore: Deno global\n ? globalThis.Deno.inspect\n // @ts-ignore: Node.js global\n : \"util\" in globalThis && \"inspect\" in globalThis.util &&\n // @ts-ignore: Node.js global\n globalThis.util.inspect === \"function\"\n // @ts-ignore: Node.js global\n ? globalThis.util.inspect\n : JSON.stringify;\n\nclass DiagLoggerAdaptor implements DiagLogger {\n logger: Logger;\n\n constructor() {\n this.logger = getLogger([\"logtape\", \"meta\", \"otel\"]);\n }\n\n #escape(msg: string): string {\n return msg.replaceAll(\"{\", \"{{\").replaceAll(\"}\", \"}}\");\n }\n\n error(msg: string, ...values: unknown[]): void {\n this.logger.error(`${this.#escape(msg)}: {values}`, { values });\n }\n\n warn(msg: string, ...values: unknown[]): void {\n this.logger.warn(`${this.#escape(msg)}: {values}`, { values });\n }\n\n info(msg: string, ...values: unknown[]): void {\n this.logger.info(`${this.#escape(msg)}: {values}`, { values });\n }\n\n debug(msg: string, ...values: unknown[]): void {\n this.logger.debug(`${this.#escape(msg)}: {values}`, { values });\n }\n\n verbose(msg: string, ...values: unknown[]): void {\n this.logger.debug(`${this.#escape(msg)}: {values}`, { values });\n }\n}\n"],"mappings":"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"}
|
package/mod.ts
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
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 process from "node:process";
|
|
27
|
+
import metadata from "./deno.json" with { type: "json" };
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The OpenTelemetry logger provider.
|
|
31
|
+
*/
|
|
32
|
+
type ILoggerProvider = LoggerProviderBase & {
|
|
33
|
+
/**
|
|
34
|
+
* Adds a new {@link LogRecordProcessor} to this logger.
|
|
35
|
+
* @param processor the new LogRecordProcessor to be added.
|
|
36
|
+
*/
|
|
37
|
+
addLogRecordProcessor(processor: LogRecordProcessor): void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Flush all buffered data and shut down the LoggerProvider and all registered
|
|
41
|
+
* LogRecordProcessor.
|
|
42
|
+
*
|
|
43
|
+
* Returns a promise which is resolved when all flushes are complete.
|
|
44
|
+
*/
|
|
45
|
+
shutdown?: () => Promise<void>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The way to render the object in the log record. If `"json"`,
|
|
50
|
+
* the object is rendered as a JSON string. If `"inspect"`,
|
|
51
|
+
* the object is rendered using `util.inspect` in Node.js/Bun, or
|
|
52
|
+
* `Deno.inspect` in Deno.
|
|
53
|
+
*/
|
|
54
|
+
export type ObjectRenderer = "json" | "inspect";
|
|
55
|
+
|
|
56
|
+
type Message = (string | null | undefined)[];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Custom `body` attribute formatter.
|
|
60
|
+
* @since 0.3.0
|
|
61
|
+
*/
|
|
62
|
+
export type BodyFormatter = (message: Message) => AnyValue;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Options for creating an OpenTelemetry sink.
|
|
66
|
+
*/
|
|
67
|
+
export interface OpenTelemetrySinkOptions {
|
|
68
|
+
/**
|
|
69
|
+
* The OpenTelemetry logger provider to use.
|
|
70
|
+
*/
|
|
71
|
+
loggerProvider?: ILoggerProvider;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The way to render the message in the log record. If `"string"`,
|
|
75
|
+
* the message is rendered as a single string with the values are
|
|
76
|
+
* interpolated into the message. If `"array"`, the message is
|
|
77
|
+
* rendered as an array of strings. `"string"` by default.
|
|
78
|
+
*
|
|
79
|
+
* Or even fully customizable with a {@link BodyFormatter} function.
|
|
80
|
+
* @since 0.2.0
|
|
81
|
+
*/
|
|
82
|
+
messageType?: "string" | "array" | BodyFormatter;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The way to render the object in the log record. If `"json"`,
|
|
86
|
+
* the object is rendered as a JSON string. If `"inspect"`,
|
|
87
|
+
* the object is rendered using `util.inspect` in Node.js/Bun, or
|
|
88
|
+
* `Deno.inspect` in Deno. `"inspect"` by default.
|
|
89
|
+
*/
|
|
90
|
+
objectRenderer?: ObjectRenderer;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Whether to log diagnostics. Diagnostic logs are logged to
|
|
94
|
+
* the `["logtape", "meta", "otel"]` category.
|
|
95
|
+
* Turned off by default.
|
|
96
|
+
*/
|
|
97
|
+
diagnostics?: boolean;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* The OpenTelemetry OTLP exporter configuration to use.
|
|
101
|
+
* Ignored if `loggerProvider` is provided.
|
|
102
|
+
*/
|
|
103
|
+
otlpExporterConfig?: OTLPExporterNodeConfigBase;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* The service name to use. If not provided, the service name is
|
|
107
|
+
* taken from the `OTEL_SERVICE_NAME` environment variable.
|
|
108
|
+
* Ignored if `loggerProvider` is provided.
|
|
109
|
+
*/
|
|
110
|
+
serviceName?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Creates a sink that forwards log records to OpenTelemetry.
|
|
115
|
+
* @param options Options for creating the sink.
|
|
116
|
+
* @returns The sink.
|
|
117
|
+
*/
|
|
118
|
+
export function getOpenTelemetrySink(
|
|
119
|
+
options: OpenTelemetrySinkOptions = {},
|
|
120
|
+
): Sink {
|
|
121
|
+
if (options.diagnostics) {
|
|
122
|
+
diag.setLogger(new DiagLoggerAdaptor(), DiagLogLevel.DEBUG);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let loggerProvider: ILoggerProvider;
|
|
126
|
+
if (options.loggerProvider == null) {
|
|
127
|
+
const resource = defaultResource().merge(
|
|
128
|
+
resourceFromAttributes({
|
|
129
|
+
[ATTR_SERVICE_NAME]: options.serviceName ??
|
|
130
|
+
process.env.OTEL_SERVICE_NAME,
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
loggerProvider = new LoggerProvider({ resource });
|
|
134
|
+
const otlpExporter = new OTLPLogExporter(options.otlpExporterConfig);
|
|
135
|
+
loggerProvider.addLogRecordProcessor(
|
|
136
|
+
// @ts-ignore: it works anyway...
|
|
137
|
+
new SimpleLogRecordProcessor(otlpExporter),
|
|
138
|
+
);
|
|
139
|
+
} else {
|
|
140
|
+
loggerProvider = options.loggerProvider;
|
|
141
|
+
}
|
|
142
|
+
const objectRenderer = options.objectRenderer ?? "inspect";
|
|
143
|
+
const logger = loggerProvider.getLogger(metadata.name, metadata.version);
|
|
144
|
+
const sink = (record: LogRecord) => {
|
|
145
|
+
const { category, level, message, timestamp, properties } = record;
|
|
146
|
+
if (
|
|
147
|
+
category[0] === "logtape" && category[1] === "meta" &&
|
|
148
|
+
category[2] === "otel"
|
|
149
|
+
) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const severityNumber = mapLevelToSeverityNumber(level);
|
|
153
|
+
const attributes = convertToAttributes(properties, objectRenderer);
|
|
154
|
+
attributes["category"] = [...category];
|
|
155
|
+
logger.emit(
|
|
156
|
+
{
|
|
157
|
+
severityNumber,
|
|
158
|
+
severityText: level,
|
|
159
|
+
body: typeof options.messageType === "function"
|
|
160
|
+
? convertMessageToCustomBodyFormat(
|
|
161
|
+
message,
|
|
162
|
+
objectRenderer,
|
|
163
|
+
options.messageType,
|
|
164
|
+
)
|
|
165
|
+
: options.messageType === "array"
|
|
166
|
+
? convertMessageToArray(message, objectRenderer)
|
|
167
|
+
: convertMessageToString(message, objectRenderer),
|
|
168
|
+
attributes,
|
|
169
|
+
timestamp: new Date(timestamp),
|
|
170
|
+
} satisfies OTLogRecord,
|
|
171
|
+
);
|
|
172
|
+
};
|
|
173
|
+
if (loggerProvider.shutdown != null) {
|
|
174
|
+
const shutdown = loggerProvider.shutdown.bind(loggerProvider);
|
|
175
|
+
sink[Symbol.asyncDispose] = shutdown;
|
|
176
|
+
}
|
|
177
|
+
return sink;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function mapLevelToSeverityNumber(level: string): number {
|
|
181
|
+
switch (level) {
|
|
182
|
+
case "trace":
|
|
183
|
+
return SeverityNumber.TRACE;
|
|
184
|
+
case "debug":
|
|
185
|
+
return SeverityNumber.DEBUG;
|
|
186
|
+
case "info":
|
|
187
|
+
return SeverityNumber.INFO;
|
|
188
|
+
case "warning":
|
|
189
|
+
return SeverityNumber.WARN;
|
|
190
|
+
case "error":
|
|
191
|
+
return SeverityNumber.ERROR;
|
|
192
|
+
case "fatal":
|
|
193
|
+
return SeverityNumber.FATAL;
|
|
194
|
+
default:
|
|
195
|
+
return SeverityNumber.UNSPECIFIED;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function convertToAttributes(
|
|
200
|
+
properties: Record<string, unknown>,
|
|
201
|
+
objectRenderer: ObjectRenderer,
|
|
202
|
+
): Record<string, AnyValue> {
|
|
203
|
+
const attributes: Record<string, AnyValue> = {};
|
|
204
|
+
for (const [name, value] of Object.entries(properties)) {
|
|
205
|
+
const key = `attributes.${name}`;
|
|
206
|
+
if (value == null) continue;
|
|
207
|
+
if (Array.isArray(value)) {
|
|
208
|
+
let t = null;
|
|
209
|
+
for (const v of value) {
|
|
210
|
+
if (v == null) continue;
|
|
211
|
+
if (t != null && typeof v !== t) {
|
|
212
|
+
attributes[key] = value.map((v) =>
|
|
213
|
+
convertToString(v, objectRenderer)
|
|
214
|
+
);
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
t = typeof v;
|
|
218
|
+
}
|
|
219
|
+
attributes[key] = value;
|
|
220
|
+
} else {
|
|
221
|
+
const encoded = convertToString(value, objectRenderer);
|
|
222
|
+
if (encoded == null) continue;
|
|
223
|
+
attributes[key] = encoded;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return attributes;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function convertToString(
|
|
230
|
+
value: unknown,
|
|
231
|
+
objectRenderer: ObjectRenderer,
|
|
232
|
+
): string | null | undefined {
|
|
233
|
+
if (value === null || value === undefined || typeof value === "string") {
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
if (objectRenderer === "inspect") return inspect(value);
|
|
237
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
238
|
+
return value.toString();
|
|
239
|
+
} else if (value instanceof Date) return value.toISOString();
|
|
240
|
+
else return JSON.stringify(value);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function convertMessageToArray(
|
|
244
|
+
message: readonly unknown[],
|
|
245
|
+
objectRenderer: ObjectRenderer,
|
|
246
|
+
): AnyValue {
|
|
247
|
+
const body: (string | null | undefined)[] = [];
|
|
248
|
+
for (let i = 0; i < message.length; i += 2) {
|
|
249
|
+
const msg = message[i] as string;
|
|
250
|
+
body.push(msg);
|
|
251
|
+
if (message.length <= i + 1) break;
|
|
252
|
+
const val = message[i + 1];
|
|
253
|
+
body.push(convertToString(val, objectRenderer));
|
|
254
|
+
}
|
|
255
|
+
return body;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function convertMessageToString(
|
|
259
|
+
message: readonly unknown[],
|
|
260
|
+
objectRenderer: ObjectRenderer,
|
|
261
|
+
): AnyValue {
|
|
262
|
+
let body = "";
|
|
263
|
+
for (let i = 0; i < message.length; i += 2) {
|
|
264
|
+
const msg = message[i] as string;
|
|
265
|
+
body += msg;
|
|
266
|
+
if (message.length <= i + 1) break;
|
|
267
|
+
const val = message[i + 1];
|
|
268
|
+
const extra = convertToString(val, objectRenderer);
|
|
269
|
+
body += extra ?? JSON.stringify(extra);
|
|
270
|
+
}
|
|
271
|
+
return body;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function convertMessageToCustomBodyFormat(
|
|
275
|
+
message: readonly unknown[],
|
|
276
|
+
objectRenderer: ObjectRenderer,
|
|
277
|
+
bodyFormatter: BodyFormatter,
|
|
278
|
+
): AnyValue {
|
|
279
|
+
const body = message.map((msg) => convertToString(msg, objectRenderer));
|
|
280
|
+
return bodyFormatter(body);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* A platform-specific inspect function. In Deno, this is {@link Deno.inspect},
|
|
285
|
+
* and in Node.js/Bun it is {@link util.inspect}. If neither is available, it
|
|
286
|
+
* falls back to {@link JSON.stringify}.
|
|
287
|
+
*
|
|
288
|
+
* @param value The value to inspect.
|
|
289
|
+
* @returns The string representation of the value.
|
|
290
|
+
*/
|
|
291
|
+
const inspect: (value: unknown) => string =
|
|
292
|
+
// @ts-ignore: Deno global
|
|
293
|
+
"Deno" in globalThis && "inspect" in globalThis.Deno &&
|
|
294
|
+
// @ts-ignore: Deno global
|
|
295
|
+
typeof globalThis.Deno.inspect === "function"
|
|
296
|
+
// @ts-ignore: Deno global
|
|
297
|
+
? globalThis.Deno.inspect
|
|
298
|
+
// @ts-ignore: Node.js global
|
|
299
|
+
: "util" in globalThis && "inspect" in globalThis.util &&
|
|
300
|
+
// @ts-ignore: Node.js global
|
|
301
|
+
globalThis.util.inspect === "function"
|
|
302
|
+
// @ts-ignore: Node.js global
|
|
303
|
+
? globalThis.util.inspect
|
|
304
|
+
: JSON.stringify;
|
|
305
|
+
|
|
306
|
+
class DiagLoggerAdaptor implements DiagLogger {
|
|
307
|
+
logger: Logger;
|
|
308
|
+
|
|
309
|
+
constructor() {
|
|
310
|
+
this.logger = getLogger(["logtape", "meta", "otel"]);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#escape(msg: string): string {
|
|
314
|
+
return msg.replaceAll("{", "{{").replaceAll("}", "}}");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
error(msg: string, ...values: unknown[]): void {
|
|
318
|
+
this.logger.error(`${this.#escape(msg)}: {values}`, { values });
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
warn(msg: string, ...values: unknown[]): void {
|
|
322
|
+
this.logger.warn(`${this.#escape(msg)}: {values}`, { values });
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
info(msg: string, ...values: unknown[]): void {
|
|
326
|
+
this.logger.info(`${this.#escape(msg)}: {values}`, { values });
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
debug(msg: string, ...values: unknown[]): void {
|
|
330
|
+
this.logger.debug(`${this.#escape(msg)}: {values}`, { values });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
verbose(msg: string, ...values: unknown[]): void {
|
|
334
|
+
this.logger.debug(`${this.#escape(msg)}: {values}`, { values });
|
|
335
|
+
}
|
|
336
|
+
}
|
package/package.json
CHANGED
|
@@ -1,60 +1,61 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@logtape/otel",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "LogTape OpenTelemetry
|
|
3
|
+
"version": "0.12.0-dev.190+d31c97b2",
|
|
4
|
+
"description": "LogTape OpenTelemetry sink",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"LogTape",
|
|
7
7
|
"OpenTelemetry",
|
|
8
8
|
"otel"
|
|
9
9
|
],
|
|
10
|
+
"license": "MIT",
|
|
10
11
|
"author": {
|
|
11
12
|
"name": "Hong Minhee",
|
|
12
13
|
"email": "hong@minhee.org",
|
|
13
14
|
"url": "https://hongminhee.org/"
|
|
14
15
|
},
|
|
15
|
-
"homepage": "https://
|
|
16
|
+
"homepage": "https://logtape.org/",
|
|
16
17
|
"repository": {
|
|
17
18
|
"type": "git",
|
|
18
|
-
"url": "git+https://github.com/dahlia/logtape
|
|
19
|
+
"url": "git+https://github.com/dahlia/logtape.git",
|
|
20
|
+
"directory": "otel/"
|
|
19
21
|
},
|
|
20
|
-
"license": "MIT",
|
|
21
22
|
"bugs": {
|
|
22
|
-
"url": "https://github.com/dahlia/logtape
|
|
23
|
+
"url": "https://github.com/dahlia/logtape/issues"
|
|
23
24
|
},
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
"funding": [
|
|
26
|
+
"https://github.com/sponsors/dahlia"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"module": "./dist/mod.js",
|
|
30
|
+
"main": "./dist/mod.cjs",
|
|
31
|
+
"types": "./dist/mod.d.ts",
|
|
27
32
|
"exports": {
|
|
28
33
|
".": {
|
|
29
|
-
"import":
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"types": "./types/mod.d.ts",
|
|
35
|
-
"default": "./script/mod.js"
|
|
36
|
-
}
|
|
37
|
-
}
|
|
34
|
+
"import": "./dist/mod.js",
|
|
35
|
+
"require": "./dist/mod.cjs",
|
|
36
|
+
"types": "./dist/mod.d.ts"
|
|
37
|
+
},
|
|
38
|
+
"./package.json": "./package.json"
|
|
38
39
|
},
|
|
39
|
-
"
|
|
40
|
-
"
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@logtape/logtape": "0.12.0-dev.190+d31c97b2"
|
|
41
42
|
},
|
|
42
|
-
"funding": [
|
|
43
|
-
"https://github.com/sponsors/dahlia"
|
|
44
|
-
],
|
|
45
43
|
"dependencies": {
|
|
46
|
-
"@logtape/logtape": "^0.12.0-dev.186",
|
|
47
44
|
"@opentelemetry/api": "^1.9.0",
|
|
48
|
-
"@opentelemetry/api-logs": "^0.
|
|
49
|
-
"@opentelemetry/exporter-logs-otlp-http": "^0.
|
|
50
|
-
"@opentelemetry/otlp-exporter-base": "^0.
|
|
51
|
-
"@opentelemetry/resources": "^
|
|
52
|
-
"@opentelemetry/sdk-logs": "^0.
|
|
53
|
-
"@opentelemetry/semantic-conventions": "^1.
|
|
45
|
+
"@opentelemetry/api-logs": "^0.202.0",
|
|
46
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.202.0",
|
|
47
|
+
"@opentelemetry/otlp-exporter-base": "^0.202.0",
|
|
48
|
+
"@opentelemetry/resources": "^2.0.1",
|
|
49
|
+
"@opentelemetry/sdk-logs": "^0.202.0",
|
|
50
|
+
"@opentelemetry/semantic-conventions": "^1.34.0"
|
|
54
51
|
},
|
|
55
52
|
"devDependencies": {
|
|
56
|
-
"@
|
|
57
|
-
"
|
|
53
|
+
"@std/dotenv": "npm:@jsr/std__dotenv@^0.225.5",
|
|
54
|
+
"tsdown": "^0.12.7",
|
|
55
|
+
"typescript": "^5.8.3"
|
|
58
56
|
},
|
|
59
|
-
"
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsdown",
|
|
59
|
+
"prepublish": "tsdown"
|
|
60
|
+
}
|
|
60
61
|
}
|
package/sample.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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"], level: "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" });
|