@unhingged/logit 0.0.0-stage → 0.1.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/dist/otel.js ADDED
@@ -0,0 +1,304 @@
1
+ import {
2
+ CLEF_MEDIA_TYPE,
3
+ DEFAULT_DESTRUCTURING,
4
+ DEFAULT_OUTPUT_TEMPLATE,
5
+ LEVELS,
6
+ LEVEL_CODES,
7
+ LEVEL_NUMBERS,
8
+ LEVEL_ORDER,
9
+ LevelSwitch,
10
+ LogContext,
11
+ LogEvent,
12
+ Logger,
13
+ LoggerConfiguration,
14
+ Matching,
15
+ Operation,
16
+ SERILOG_LEVEL_NAMES,
17
+ ansi,
18
+ audit,
19
+ batchingSink,
20
+ byExcluding,
21
+ byIncludingOnly,
22
+ byTransforming,
23
+ callbackSink,
24
+ captureError,
25
+ captureProperty,
26
+ captureValue,
27
+ clefFormatter,
28
+ closeAndFlush,
29
+ conditional,
30
+ configure,
31
+ consoleSink,
32
+ createLogger,
33
+ detectColors,
34
+ diagnosticContext,
35
+ formatDate,
36
+ formatError,
37
+ formatLevel,
38
+ formatNumber,
39
+ fromLogContext,
40
+ httpSink,
41
+ isLevel,
42
+ isoTimestamp,
43
+ jsonFormatter,
44
+ jsonReplacer,
45
+ levelAtLeast,
46
+ log,
47
+ mapSink,
48
+ memorySink,
49
+ parseLevel,
50
+ parseTemplate,
51
+ prettyFormatter,
52
+ rateLimited,
53
+ renderTemplate,
54
+ renderValue,
55
+ resolveFormatter,
56
+ resolveLevel,
57
+ resolveStyles,
58
+ restricted,
59
+ sampled,
60
+ selfLog,
61
+ seqSink,
62
+ streamSink,
63
+ templateHash,
64
+ textFormatter,
65
+ toJson,
66
+ withComputed,
67
+ withEnvironment,
68
+ withProperties,
69
+ withProperty,
70
+ withTraceContext
71
+ } from "./chunk-GW7WQELF.js";
72
+
73
+ // src/otel.ts
74
+ var OTEL_SEVERITY = {
75
+ verbose: { number: 1, text: "TRACE" },
76
+ debug: { number: 5, text: "DEBUG" },
77
+ info: { number: 9, text: "INFO" },
78
+ warn: { number: 13, text: "WARN" },
79
+ error: { number: 17, text: "ERROR" },
80
+ fatal: { number: 21, text: "FATAL" }
81
+ };
82
+ function toAnyValue(value) {
83
+ if (value === null || value === void 0) return { stringValue: "" };
84
+ switch (typeof value) {
85
+ case "string":
86
+ return { stringValue: value };
87
+ case "number":
88
+ return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
89
+ case "boolean":
90
+ return { boolValue: value };
91
+ case "bigint":
92
+ return { intValue: value.toString() };
93
+ default:
94
+ break;
95
+ }
96
+ if (value instanceof Date) return { stringValue: value.toISOString() };
97
+ if (Array.isArray(value)) return { arrayValue: { values: value.map(toAnyValue) } };
98
+ if (typeof value === "object") return { kvlistValue: { values: toKeyValues(value) } };
99
+ return { stringValue: String(value) };
100
+ }
101
+ function toKeyValues(record) {
102
+ return Object.keys(record).map((key) => ({ key, value: toAnyValue(record[key]) }));
103
+ }
104
+ function encodeOtlpLogs(events, options = {}) {
105
+ const templateAttribute = options.templateAttribute === void 0 ? "message_template.text" : options.templateAttribute;
106
+ return {
107
+ resourceLogs: [
108
+ {
109
+ resource: { attributes: toKeyValues({ ...options.resource }) },
110
+ scopeLogs: [
111
+ {
112
+ scope: options.scope ?? { name: "@unhingged/logit" },
113
+ logRecords: events.map((event) => {
114
+ const attributes = [];
115
+ if (templateAttribute) attributes.push({ key: templateAttribute, value: { stringValue: event.messageTemplate } });
116
+ for (const key of Object.keys(event.properties)) attributes.push({ key, value: toAnyValue(event.properties[key]) });
117
+ if (event.error) {
118
+ attributes.push({ key: "exception.type", value: { stringValue: event.error.name } });
119
+ attributes.push({ key: "exception.message", value: { stringValue: event.error.message } });
120
+ attributes.push({ key: "exception.stacktrace", value: { stringValue: formatError(event.error) } });
121
+ }
122
+ const severity = OTEL_SEVERITY[event.level];
123
+ const nanos = `${event.timestamp.getTime()}000000`;
124
+ return {
125
+ timeUnixNano: nanos,
126
+ observedTimeUnixNano: nanos,
127
+ severityNumber: severity.number,
128
+ severityText: severity.text,
129
+ body: { stringValue: event.renderMessage({ literal: true }) },
130
+ attributes,
131
+ ...event.traceId ? { traceId: event.traceId } : {},
132
+ ...event.spanId ? { spanId: event.spanId } : {}
133
+ };
134
+ })
135
+ }
136
+ ]
137
+ }
138
+ ]
139
+ };
140
+ }
141
+ function env(name) {
142
+ return globalThis.process?.env?.[name];
143
+ }
144
+ function envHeaders() {
145
+ const raw = env("OTEL_EXPORTER_OTLP_LOGS_HEADERS") ?? env("OTEL_EXPORTER_OTLP_HEADERS");
146
+ const out = {};
147
+ if (!raw) return out;
148
+ for (const pair of raw.split(",")) {
149
+ const i = pair.indexOf("=");
150
+ if (i > 0) out[pair.slice(0, i).trim()] = decodeURIComponent(pair.slice(i + 1).trim());
151
+ }
152
+ return out;
153
+ }
154
+ function otlpHttpSink(options = {}) {
155
+ const fetchImpl = options.fetch ?? globalThis.fetch;
156
+ if (!fetchImpl) throw new Error("logit: otlpHttpSink needs fetch \u2014 pass one in options.fetch");
157
+ const url = options.url ?? env("OTEL_EXPORTER_OTLP_LOGS_ENDPOINT") ?? `${(env("OTEL_EXPORTER_OTLP_ENDPOINT") ?? "http://localhost:4318").replace(/\/$/, "")}/v1/logs`;
158
+ const serviceName = env("OTEL_SERVICE_NAME");
159
+ const resource = { ...serviceName ? { "service.name": serviceName } : {}, ...options.resource };
160
+ const timeout = options.timeout ?? 1e4;
161
+ return batchingSink(
162
+ async (events) => {
163
+ const controller = typeof AbortController === "function" ? new AbortController() : void 0;
164
+ const timer = controller ? setTimeout(() => controller.abort(), timeout) : void 0;
165
+ timer?.unref?.();
166
+ try {
167
+ const response = await fetchImpl(url, {
168
+ method: "POST",
169
+ headers: { "content-type": "application/json", ...envHeaders(), ...options.headers },
170
+ body: JSON.stringify(encodeOtlpLogs(events, { ...options, resource })),
171
+ signal: controller?.signal,
172
+ keepalive: true
173
+ });
174
+ if (!response.ok) throw new Error(`OTLP export failed: HTTP ${response.status}`);
175
+ } finally {
176
+ if (timer) clearTimeout(timer);
177
+ }
178
+ },
179
+ { name: `otlp(${url})`, ...options }
180
+ );
181
+ }
182
+ function flattenAttributes(record, prefix = "", into = {}) {
183
+ for (const key of Object.keys(record)) {
184
+ const value = record[key];
185
+ const name = prefix ? `${prefix}.${key}` : key;
186
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
187
+ flattenAttributes(value, name, into);
188
+ } else if (value instanceof Date) {
189
+ into[name] = value.toISOString();
190
+ } else if (Array.isArray(value)) {
191
+ into[name] = value.every((v) => v === null || ["string", "number", "boolean"].includes(typeof v)) ? value : JSON.stringify(value);
192
+ } else {
193
+ into[name] = value;
194
+ }
195
+ }
196
+ return into;
197
+ }
198
+ function otelBridgeSink(provider, options = {}) {
199
+ const scope = options.scope ?? { name: "@unhingged/logit" };
200
+ const otelLogger = provider.getLogger(scope.name, scope.version);
201
+ const templateAttribute = options.templateAttribute === void 0 ? "message_template.text" : options.templateAttribute;
202
+ const flatten = options.flatten ?? true;
203
+ return {
204
+ name: "otel-bridge",
205
+ restrictedToMinimumLevel: options.restrictedToMinimumLevel,
206
+ audit: options.audit,
207
+ emit(event) {
208
+ const raw = { ...event.properties };
209
+ if (templateAttribute) raw[templateAttribute] = event.messageTemplate;
210
+ if (event.error) {
211
+ raw["exception.type"] = event.error.name;
212
+ raw["exception.message"] = event.error.message;
213
+ raw["exception.stacktrace"] = formatError(event.error);
214
+ }
215
+ const severity = OTEL_SEVERITY[event.level];
216
+ otelLogger.emit({
217
+ timestamp: event.timestamp,
218
+ observedTimestamp: event.timestamp,
219
+ severityNumber: severity.number,
220
+ severityText: severity.text,
221
+ body: event.renderMessage({ literal: true }),
222
+ attributes: flatten ? flattenAttributes(raw) : raw
223
+ });
224
+ }
225
+ };
226
+ }
227
+ export {
228
+ CLEF_MEDIA_TYPE,
229
+ DEFAULT_DESTRUCTURING,
230
+ DEFAULT_OUTPUT_TEMPLATE,
231
+ LEVELS,
232
+ LEVEL_CODES,
233
+ LEVEL_NUMBERS,
234
+ LEVEL_ORDER,
235
+ LevelSwitch,
236
+ LogContext,
237
+ LogEvent,
238
+ Logger,
239
+ LoggerConfiguration,
240
+ Matching,
241
+ OTEL_SEVERITY,
242
+ Operation,
243
+ SERILOG_LEVEL_NAMES,
244
+ ansi,
245
+ audit,
246
+ batchingSink,
247
+ byExcluding,
248
+ byIncludingOnly,
249
+ byTransforming,
250
+ callbackSink,
251
+ captureError,
252
+ captureProperty,
253
+ captureValue,
254
+ clefFormatter,
255
+ closeAndFlush,
256
+ conditional,
257
+ configure,
258
+ consoleSink,
259
+ createLogger,
260
+ detectColors,
261
+ diagnosticContext,
262
+ encodeOtlpLogs,
263
+ flattenAttributes,
264
+ formatDate,
265
+ formatError,
266
+ formatLevel,
267
+ formatNumber,
268
+ fromLogContext,
269
+ httpSink,
270
+ isLevel,
271
+ isoTimestamp,
272
+ jsonFormatter,
273
+ jsonReplacer,
274
+ levelAtLeast,
275
+ log,
276
+ mapSink,
277
+ memorySink,
278
+ otelBridgeSink,
279
+ otlpHttpSink,
280
+ parseLevel,
281
+ parseTemplate,
282
+ prettyFormatter,
283
+ rateLimited,
284
+ renderTemplate,
285
+ renderValue,
286
+ resolveFormatter,
287
+ resolveLevel,
288
+ resolveStyles,
289
+ restricted,
290
+ sampled,
291
+ selfLog,
292
+ seqSink,
293
+ streamSink,
294
+ templateHash,
295
+ textFormatter,
296
+ toAnyValue,
297
+ toJson,
298
+ withComputed,
299
+ withEnvironment,
300
+ withProperties,
301
+ withProperty,
302
+ withTraceContext
303
+ };
304
+ //# sourceMappingURL=otel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/otel.ts"],"sourcesContent":["/**\n * `@unhingged/logit/otel` — OpenTelemetry without the SDK:\n *\n * otlpHttpSink() OTLP/HTTP JSON straight to a collector, Grafana,\n * Honeycomb, Datadog, Dash0… (`/v1/logs`), batched.\n * otelBridgeSink() hands events to an OpenTelemetry `LoggerProvider`\n * you already have, structurally typed so this package\n * does not depend on `@opentelemetry/api-logs`.\n *\n * Mapping follows Serilog.Sinks.OpenTelemetry: the rendered message is the\n * body, every property is an attribute of the same name, the template goes\n * in `message_template.text`, the error in `exception.*`.\n */\nimport type { LogEvent } from './event';\nimport { formatError } from './format/text';\nimport type { LogLevel } from './levels';\nimport { type BatchingOptions, type BatchingSink, batchingSink } from './sinks/batching';\nimport type { FetchLike } from './sinks/http';\nimport type { Sink, SinkOptions } from './sinks/types';\n\nexport * from './index';\n\n/** OpenTelemetry severity numbers for the six levels. */\nexport const OTEL_SEVERITY: Readonly<Record<LogLevel, { number: number; text: string }>> = {\n verbose: { number: 1, text: 'TRACE' },\n debug: { number: 5, text: 'DEBUG' },\n info: { number: 9, text: 'INFO' },\n warn: { number: 13, text: 'WARN' },\n error: { number: 17, text: 'ERROR' },\n fatal: { number: 21, text: 'FATAL' },\n};\n\ntype AnyValue =\n | { stringValue: string }\n | { intValue: string }\n | { doubleValue: number }\n | { boolValue: boolean }\n | { arrayValue: { values: AnyValue[] } }\n | { kvlistValue: { values: KeyValue[] } };\n\ninterface KeyValue {\n key: string;\n value: AnyValue;\n}\n\n/** Encodes a captured value as an OTLP `AnyValue` (the protobuf JSON mapping: int64 as a string). */\nexport function toAnyValue(value: unknown): AnyValue {\n if (value === null || value === undefined) return { stringValue: '' };\n switch (typeof value) {\n case 'string':\n return { stringValue: value };\n case 'number':\n return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };\n case 'boolean':\n return { boolValue: value };\n case 'bigint':\n return { intValue: value.toString() };\n default:\n break;\n }\n if (value instanceof Date) return { stringValue: value.toISOString() };\n if (Array.isArray(value)) return { arrayValue: { values: value.map(toAnyValue) } };\n if (typeof value === 'object') return { kvlistValue: { values: toKeyValues(value as Record<string, unknown>) } };\n return { stringValue: String(value) };\n}\n\nfunction toKeyValues(record: Record<string, unknown>): KeyValue[] {\n return Object.keys(record).map((key) => ({ key, value: toAnyValue(record[key]) }));\n}\n\nexport interface OtlpAttributes {\n [key: string]: unknown;\n}\n\nexport interface OtlpEncodeOptions {\n /** Resource attributes — `service.name` at least. */\n resource?: OtlpAttributes;\n scope?: { name: string; version?: string };\n /** Attribute name for the message template. Default `message_template.text` (Serilog's). `false` to omit. */\n templateAttribute?: string | false;\n}\n\n/** Builds the `ExportLogsServiceRequest` JSON for a batch. */\nexport function encodeOtlpLogs(events: LogEvent[], options: OtlpEncodeOptions = {}): unknown {\n const templateAttribute = options.templateAttribute === undefined ? 'message_template.text' : options.templateAttribute;\n return {\n resourceLogs: [\n {\n resource: { attributes: toKeyValues({ ...options.resource }) },\n scopeLogs: [\n {\n scope: options.scope ?? { name: '@unhingged/logit' },\n logRecords: events.map((event) => {\n const attributes: KeyValue[] = [];\n if (templateAttribute) attributes.push({ key: templateAttribute, value: { stringValue: event.messageTemplate } });\n for (const key of Object.keys(event.properties)) attributes.push({ key, value: toAnyValue(event.properties[key]) });\n if (event.error) {\n attributes.push({ key: 'exception.type', value: { stringValue: event.error.name } });\n attributes.push({ key: 'exception.message', value: { stringValue: event.error.message } });\n attributes.push({ key: 'exception.stacktrace', value: { stringValue: formatError(event.error) } });\n }\n const severity = OTEL_SEVERITY[event.level];\n const nanos = `${event.timestamp.getTime()}000000`;\n return {\n timeUnixNano: nanos,\n observedTimeUnixNano: nanos,\n severityNumber: severity.number,\n severityText: severity.text,\n body: { stringValue: event.renderMessage({ literal: true }) },\n attributes,\n ...(event.traceId ? { traceId: event.traceId } : {}),\n ...(event.spanId ? { spanId: event.spanId } : {}),\n };\n }),\n },\n ],\n },\n ],\n };\n}\n\nexport interface OtlpHttpSinkOptions extends BatchingOptions, OtlpEncodeOptions {\n /** Default `http://localhost:4318/v1/logs`, or `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` / `OTEL_EXPORTER_OTLP_ENDPOINT` + `/v1/logs`. */\n url?: string;\n headers?: Record<string, string>;\n timeout?: number;\n fetch?: FetchLike;\n}\n\nfunction env(name: string): string | undefined {\n return (globalThis as { process?: { env?: Record<string, string | undefined> } }).process?.env?.[name];\n}\n\n/** `OTEL_EXPORTER_OTLP_HEADERS=\"key=value,key2=value2\"` */\nfunction envHeaders(): Record<string, string> {\n const raw = env('OTEL_EXPORTER_OTLP_LOGS_HEADERS') ?? env('OTEL_EXPORTER_OTLP_HEADERS');\n const out: Record<string, string> = {};\n if (!raw) return out;\n for (const pair of raw.split(',')) {\n const i = pair.indexOf('=');\n if (i > 0) out[pair.slice(0, i).trim()] = decodeURIComponent(pair.slice(i + 1).trim());\n }\n return out;\n}\n\n/** OTLP/HTTP JSON to `/v1/logs`. Honours the standard `OTEL_EXPORTER_OTLP_*` variables. */\nexport function otlpHttpSink(options: OtlpHttpSinkOptions = {}): BatchingSink {\n const fetchImpl: FetchLike = options.fetch ?? ((globalThis as { fetch?: FetchLike }).fetch as FetchLike);\n if (!fetchImpl) throw new Error('logit: otlpHttpSink needs fetch — pass one in options.fetch');\n const url = options.url ?? env('OTEL_EXPORTER_OTLP_LOGS_ENDPOINT') ?? `${(env('OTEL_EXPORTER_OTLP_ENDPOINT') ?? 'http://localhost:4318').replace(/\\/$/, '')}/v1/logs`;\n const serviceName = env('OTEL_SERVICE_NAME');\n const resource = { ...(serviceName ? { 'service.name': serviceName } : {}), ...options.resource };\n const timeout = options.timeout ?? 10_000;\n\n return batchingSink(\n async (events) => {\n const controller = typeof AbortController === 'function' ? new AbortController() : undefined;\n const timer = controller ? setTimeout(() => controller.abort(), timeout) : undefined;\n (timer as { unref?: () => void } | undefined)?.unref?.();\n try {\n const response = await fetchImpl(url, {\n method: 'POST',\n headers: { 'content-type': 'application/json', ...envHeaders(), ...options.headers },\n body: JSON.stringify(encodeOtlpLogs(events, { ...options, resource })),\n signal: controller?.signal,\n keepalive: true,\n });\n if (!response.ok) throw new Error(`OTLP export failed: HTTP ${response.status}`);\n } finally {\n if (timer) clearTimeout(timer);\n }\n },\n { name: `otlp(${url})`, ...options },\n );\n}\n\n/** The slice of `@opentelemetry/api-logs` we call — structural on purpose. */\nexport interface OtelLogRecord {\n timestamp?: number | Date;\n observedTimestamp?: number | Date;\n severityNumber?: number;\n severityText?: string;\n body?: string;\n attributes?: Record<string, unknown>;\n}\n\nexport interface OtelLogger {\n emit(record: OtelLogRecord): void;\n}\n\nexport interface OtelLoggerProvider {\n getLogger(name: string, version?: string, options?: unknown): OtelLogger;\n}\n\nexport interface OtelBridgeOptions extends SinkOptions {\n scope?: { name: string; version?: string };\n templateAttribute?: string | false;\n /** OpenTelemetry attributes are flat: nested objects are flattened with dots (`user.id`). Default true; `false` passes them through. */\n flatten?: boolean;\n}\n\n/** Flattens nested objects into dotted keys for the OTel attributes API. */\nexport function flattenAttributes(record: Record<string, unknown>, prefix = '', into: Record<string, unknown> = {}): Record<string, unknown> {\n for (const key of Object.keys(record)) {\n const value = record[key];\n const name = prefix ? `${prefix}.${key}` : key;\n if (value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof Date)) {\n flattenAttributes(value as Record<string, unknown>, name, into);\n } else if (value instanceof Date) {\n into[name] = value.toISOString();\n } else if (Array.isArray(value)) {\n into[name] = value.every((v) => v === null || ['string', 'number', 'boolean'].includes(typeof v)) ? value : JSON.stringify(value);\n } else {\n into[name] = value as unknown;\n }\n }\n return into;\n}\n\n/**\n * Hands each event to `provider.getLogger(scope)` — use it when the OTel SDK\n * is already set up (`logs.getLoggerProvider()`), so trace context, resource\n * and exporters are all the SDK's.\n */\nexport function otelBridgeSink(provider: OtelLoggerProvider, options: OtelBridgeOptions = {}): Sink {\n const scope = options.scope ?? { name: '@unhingged/logit' };\n const otelLogger = provider.getLogger(scope.name, scope.version);\n const templateAttribute = options.templateAttribute === undefined ? 'message_template.text' : options.templateAttribute;\n const flatten = options.flatten ?? true;\n return {\n name: 'otel-bridge',\n restrictedToMinimumLevel: options.restrictedToMinimumLevel,\n audit: options.audit,\n emit(event: LogEvent) {\n const raw: Record<string, unknown> = { ...event.properties };\n if (templateAttribute) raw[templateAttribute] = event.messageTemplate;\n if (event.error) {\n raw['exception.type'] = event.error.name;\n raw['exception.message'] = event.error.message;\n raw['exception.stacktrace'] = formatError(event.error);\n }\n const severity = OTEL_SEVERITY[event.level];\n otelLogger.emit({\n timestamp: event.timestamp,\n observedTimestamp: event.timestamp,\n severityNumber: severity.number,\n severityText: severity.text,\n body: event.renderMessage({ literal: true }),\n attributes: flatten ? flattenAttributes(raw) : raw,\n });\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,34 @@
1
+ import { Logger, LogLevel } from './index.js';
2
+
3
+ declare const REQUEST_COMPLETION_TEMPLATE = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
4
+ interface RequestInfo {
5
+ method: string;
6
+ path: string;
7
+ /** Full URL or path + query, used for the log only when `includeQuery`. */
8
+ url?: string;
9
+ requestId?: string;
10
+ headers?: {
11
+ get(name: string): string | null | undefined;
12
+ } | Record<string, string | string[] | undefined>;
13
+ }
14
+ interface RequestLoggingOptions {
15
+ logger?: Logger;
16
+ /** The completion message. Default `REQUEST_COMPLETION_TEMPLATE`. */
17
+ messageTemplate?: string;
18
+ /** Picks the level — the default is `error` for an exception or a 5xx, `info` otherwise. */
19
+ getLevel?: (status: number, elapsedMs: number, error: unknown) => LogLevel;
20
+ /** Adds properties to the completion event (Serilog's `EnrichDiagnosticContext`). */
21
+ enrich?: (set: (name: string, value: unknown) => void, request: RequestInfo, response: {
22
+ status: number;
23
+ }) => void;
24
+ /** Requests to skip entirely — health checks, static assets. */
25
+ ignore?: (request: RequestInfo) => boolean;
26
+ /** Header to read a request id from (set by load balancers; Vercel sends `x-vercel-id`). Default `x-request-id`, then `x-vercel-id`. */
27
+ requestIdHeader?: string | string[];
28
+ /** Generate an id when the request has none. Default: `crypto.randomUUID()`. */
29
+ generateRequestId?: () => string;
30
+ /** `SourceContext` for the completion event. Default `'http'`. */
31
+ source?: string;
32
+ }
33
+
34
+ export { type RequestLoggingOptions as R, REQUEST_COMPLETION_TEMPLATE as a, type RequestInfo as b };
@@ -0,0 +1,34 @@
1
+ import { Logger, LogLevel } from './index.cjs';
2
+
3
+ declare const REQUEST_COMPLETION_TEMPLATE = "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms";
4
+ interface RequestInfo {
5
+ method: string;
6
+ path: string;
7
+ /** Full URL or path + query, used for the log only when `includeQuery`. */
8
+ url?: string;
9
+ requestId?: string;
10
+ headers?: {
11
+ get(name: string): string | null | undefined;
12
+ } | Record<string, string | string[] | undefined>;
13
+ }
14
+ interface RequestLoggingOptions {
15
+ logger?: Logger;
16
+ /** The completion message. Default `REQUEST_COMPLETION_TEMPLATE`. */
17
+ messageTemplate?: string;
18
+ /** Picks the level — the default is `error` for an exception or a 5xx, `info` otherwise. */
19
+ getLevel?: (status: number, elapsedMs: number, error: unknown) => LogLevel;
20
+ /** Adds properties to the completion event (Serilog's `EnrichDiagnosticContext`). */
21
+ enrich?: (set: (name: string, value: unknown) => void, request: RequestInfo, response: {
22
+ status: number;
23
+ }) => void;
24
+ /** Requests to skip entirely — health checks, static assets. */
25
+ ignore?: (request: RequestInfo) => boolean;
26
+ /** Header to read a request id from (set by load balancers; Vercel sends `x-vercel-id`). Default `x-request-id`, then `x-vercel-id`. */
27
+ requestIdHeader?: string | string[];
28
+ /** Generate an id when the request has none. Default: `crypto.randomUUID()`. */
29
+ generateRequestId?: () => string;
30
+ /** `SourceContext` for the completion event. Default `'http'`. */
31
+ source?: string;
32
+ }
33
+
34
+ export { type RequestLoggingOptions as R, REQUEST_COMPLETION_TEMPLATE as a, type RequestInfo as b };
package/package.json CHANGED
@@ -1,6 +1,91 @@
1
1
  {
2
2
  "name": "@unhingged/logit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Structured logging for Node.js, Next.js, Bun and the edge — message templates, destructuring, enrichers, ambient context, sinks and output templates, in the mould of Serilog.",
5
+ "author": "Zoltán Balogh",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "sideEffects": [
9
+ "./dist/node.js",
10
+ "./dist/node.cjs",
11
+ "./dist/next.js",
12
+ "./dist/next.cjs",
13
+ "./dist/express.js",
14
+ "./dist/express.cjs"
15
+ ],
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js",
23
+ "require": "./dist/index.cjs"
24
+ },
25
+ "./node": {
26
+ "types": "./dist/node.d.ts",
27
+ "import": "./dist/node.js",
28
+ "require": "./dist/node.cjs"
29
+ },
30
+ "./next": {
31
+ "types": "./dist/next.d.ts",
32
+ "import": "./dist/next.js",
33
+ "require": "./dist/next.cjs"
34
+ },
35
+ "./express": {
36
+ "types": "./dist/express.d.ts",
37
+ "import": "./dist/express.js",
38
+ "require": "./dist/express.cjs"
39
+ },
40
+ "./otel": {
41
+ "types": "./dist/otel.d.ts",
42
+ "import": "./dist/otel.js",
43
+ "require": "./dist/otel.cjs"
44
+ },
45
+ "./package.json": "./package.json"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "README.md",
50
+ "LICENSE"
51
+ ],
52
+ "publishConfig": {
53
+ "access": "public"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup",
57
+ "dev": "tsup --watch",
58
+ "test": "bun test",
59
+ "typecheck": "tsc --noEmit"
60
+ },
61
+ "devDependencies": {
62
+ "@types/node": "^22.0.0",
63
+ "tsup": "^8.3.5",
64
+ "typescript": "^5.6.3"
65
+ },
66
+ "engines": {
67
+ "node": ">=20.16"
68
+ },
69
+ "repository": {
70
+ "type": "git",
71
+ "url": "git+https://github.com/unhingged/logit.git",
72
+ "directory": "packages/logit"
73
+ },
74
+ "homepage": "https://logit.unhingged.com",
75
+ "bugs": {
76
+ "url": "https://github.com/unhingged/logit/issues"
77
+ },
78
+ "keywords": [
79
+ "logging",
80
+ "logger",
81
+ "structured-logging",
82
+ "message-templates",
83
+ "serilog",
84
+ "nextjs",
85
+ "clef",
86
+ "seq",
87
+ "opentelemetry",
88
+ "otlp",
89
+ "typescript"
90
+ ]
91
+ }