@loggerjs/loki 0.5.6 → 0.7.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 CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <a href="https://jskits.github.io/loggerjs/"><img src="https://raw.githubusercontent.com/jskits/loggerjs/main/docs/public/logo.png" alt="LoggerJS logo" width="64" height="64" /></a>
3
+ </p>
4
+
1
5
  # @loggerjs/loki
2
6
 
3
7
  > Push LoggerJS logs to Grafana Loki.
package/dist/index.cjs CHANGED
@@ -89,7 +89,7 @@ function lokiTransport(options) {
89
89
  headers,
90
90
  body: JSON.stringify(createPayload(events, options))
91
91
  });
92
- if (!response.ok) throw new Error(`lokiTransport failed with status ${response.status}`);
92
+ if (!response.ok) throw (0, _loggerjs_core.httpStatusError)("lokiTransport", response);
93
93
  };
94
94
  return {
95
95
  name: transportName,
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { toLevelValue, type LogEvent, type LoggerLevel, type Transport } from \"@loggerjs/core\";\n\nexport interface LokiTransportOptions {\n url: string;\n name?: string;\n minLevel?: LoggerLevel;\n headers?: Record<string, string>;\n labels?: Record<string, string | number | boolean | null | undefined>;\n labelTags?: readonly string[];\n defaultLabels?: boolean;\n structuredMetadata?: boolean;\n tenantId?: string;\n line?: (event: LogEvent) => string;\n fetchFn?: typeof fetch;\n}\n\nexport interface LokiPushPayload {\n streams: Array<{\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }>;\n}\n\nfunction sanitizeLabelName(name: string): string {\n const normalized = name.replace(/[^A-Za-z0-9_]/g, \"_\");\n if (/^[A-Za-z_]/.test(normalized)) return normalized;\n return `_${normalized}`;\n}\n\nfunction compact<T extends Record<string, unknown>>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) delete value[key];\n }\n return value;\n}\n\nfunction timestampUnixNs(event: LogEvent): string {\n return (BigInt(Math.trunc(event.time)) * 1_000_000n).toString();\n}\n\nfunction defaultLine(event: LogEvent): string {\n return event.message;\n}\n\nfunction metadataFor(event: LogEvent): Record<string, unknown> {\n return compact({\n id: event.id,\n seq: event.seq,\n logger: event.logger,\n level: event.levelName,\n type: event.type,\n tags: event.tags,\n data: event.data,\n error: event.error,\n context: event.context,\n trace: event.trace,\n source: event.source,\n });\n}\n\nfunction labelEntries(labels: Record<string, string>): string {\n let out = \"\";\n for (const [key, value] of Object.entries(labels)) out += `${key}=${value}\\n`;\n return out;\n}\n\nfunction streamLabels(event: LogEvent, options: LokiTransportOptions): Record<string, string> {\n const labels: Record<string, string> = {};\n if (options.defaultLabels ?? true) {\n labels.level = event.levelName;\n labels.logger = event.logger;\n }\n for (const [key, value] of Object.entries(options.labels ?? {})) {\n if (value !== undefined && value !== null) labels[sanitizeLabelName(key)] = String(value);\n }\n for (const tag of options.labelTags ?? []) {\n const value = event.tags?.[tag];\n if (value !== undefined && value !== null) labels[sanitizeLabelName(tag)] = String(value);\n }\n return labels;\n}\n\nfunction createPayload(events: LogEvent[], options: LokiTransportOptions): LokiPushPayload {\n const streams = new Map<\n string,\n {\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }\n >();\n const line = options.line ?? defaultLine;\n const includeMetadata = options.structuredMetadata ?? true;\n\n for (const event of events) {\n const labels = streamLabels(event, options);\n const key = labelEntries(labels);\n let stream = streams.get(key);\n if (!stream) {\n stream = { stream: labels, values: [] };\n streams.set(key, stream);\n }\n const value: [string, string] | [string, string, Record<string, unknown>] = includeMetadata\n ? [timestampUnixNs(event), line(event), metadataFor(event)]\n : [timestampUnixNs(event), line(event)];\n stream.values.push(value);\n }\n\n return { streams: [...streams.values()] };\n}\n\nexport function lokiTransport(options: LokiTransportOptions): Transport {\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n const transportName = options.name ?? \"loki\";\n\n const send = async (events: LogEvent[]) => {\n if (events.length === 0) return;\n if (!fetchFn) throw new Error(\"fetch is not available for lokiTransport\");\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n ...options.headers,\n };\n if (options.tenantId) headers[\"x-scope-orgid\"] = options.tenantId;\n const response = await fetchFn(options.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(createPayload(events, options)),\n });\n if (!response.ok) throw new Error(`lokiTransport failed with status ${response.status}`);\n };\n\n return {\n name: transportName,\n minLevel: options.minLevel,\n log(event) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n return send([event]);\n },\n logBatch(events) {\n const selected =\n options.minLevel === undefined\n ? events\n : events.filter((event) => event.level >= toLevelValue(options.minLevel));\n return send([...selected]);\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n toLevelValue,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n httpStatusError,\n} from \"@loggerjs/core\";\n\nexport interface LokiTransportOptions {\n url: string;\n name?: string;\n minLevel?: LoggerLevel;\n headers?: Record<string, string>;\n labels?: Record<string, string | number | boolean | null | undefined>;\n labelTags?: readonly string[];\n defaultLabels?: boolean;\n structuredMetadata?: boolean;\n tenantId?: string;\n line?: (event: LogEvent) => string;\n fetchFn?: typeof fetch;\n}\n\nexport interface LokiPushPayload {\n streams: Array<{\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }>;\n}\n\nfunction sanitizeLabelName(name: string): string {\n const normalized = name.replace(/[^A-Za-z0-9_]/g, \"_\");\n if (/^[A-Za-z_]/.test(normalized)) return normalized;\n return `_${normalized}`;\n}\n\nfunction compact<T extends Record<string, unknown>>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) delete value[key];\n }\n return value;\n}\n\nfunction timestampUnixNs(event: LogEvent): string {\n return (BigInt(Math.trunc(event.time)) * 1_000_000n).toString();\n}\n\nfunction defaultLine(event: LogEvent): string {\n return event.message;\n}\n\nfunction metadataFor(event: LogEvent): Record<string, unknown> {\n return compact({\n id: event.id,\n seq: event.seq,\n logger: event.logger,\n level: event.levelName,\n type: event.type,\n tags: event.tags,\n data: event.data,\n error: event.error,\n context: event.context,\n trace: event.trace,\n source: event.source,\n });\n}\n\nfunction labelEntries(labels: Record<string, string>): string {\n let out = \"\";\n for (const [key, value] of Object.entries(labels)) out += `${key}=${value}\\n`;\n return out;\n}\n\nfunction streamLabels(event: LogEvent, options: LokiTransportOptions): Record<string, string> {\n const labels: Record<string, string> = {};\n if (options.defaultLabels ?? true) {\n labels.level = event.levelName;\n labels.logger = event.logger;\n }\n for (const [key, value] of Object.entries(options.labels ?? {})) {\n if (value !== undefined && value !== null) labels[sanitizeLabelName(key)] = String(value);\n }\n for (const tag of options.labelTags ?? []) {\n const value = event.tags?.[tag];\n if (value !== undefined && value !== null) labels[sanitizeLabelName(tag)] = String(value);\n }\n return labels;\n}\n\nfunction createPayload(events: LogEvent[], options: LokiTransportOptions): LokiPushPayload {\n const streams = new Map<\n string,\n {\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }\n >();\n const line = options.line ?? defaultLine;\n const includeMetadata = options.structuredMetadata ?? true;\n\n for (const event of events) {\n const labels = streamLabels(event, options);\n const key = labelEntries(labels);\n let stream = streams.get(key);\n if (!stream) {\n stream = { stream: labels, values: [] };\n streams.set(key, stream);\n }\n const value: [string, string] | [string, string, Record<string, unknown>] = includeMetadata\n ? [timestampUnixNs(event), line(event), metadataFor(event)]\n : [timestampUnixNs(event), line(event)];\n stream.values.push(value);\n }\n\n return { streams: [...streams.values()] };\n}\n\nexport function lokiTransport(options: LokiTransportOptions): Transport {\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n const transportName = options.name ?? \"loki\";\n\n const send = async (events: LogEvent[]) => {\n if (events.length === 0) return;\n if (!fetchFn) throw new Error(\"fetch is not available for lokiTransport\");\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n ...options.headers,\n };\n if (options.tenantId) headers[\"x-scope-orgid\"] = options.tenantId;\n const response = await fetchFn(options.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(createPayload(events, options)),\n });\n if (!response.ok) throw httpStatusError(\"lokiTransport\", response);\n };\n\n return {\n name: transportName,\n minLevel: options.minLevel,\n log(event) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n return send([event]);\n },\n logBatch(events) {\n const selected =\n options.minLevel === undefined\n ? events\n : events.filter((event) => event.level >= toLevelValue(options.minLevel));\n return send([...selected]);\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,QAAQ,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE/F,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC;QACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;KAC7E,CAAC,CAAC;CACJ;AAyFD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAmCtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,WAAW,EAChB,KAAK,SAAS,EAEf,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;IACtE,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,MAAM,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC;QACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;KAC7E,CAAC,CAAC;CACJ;AAyFD,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,SAAS,CAmCtE"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { toLevelValue } from "@loggerjs/core";
1
+ import { httpStatusError, toLevelValue } from "@loggerjs/core";
2
2
  //#region src/index.ts
3
3
  function sanitizeLabelName(name) {
4
4
  const normalized = name.replace(/[^A-Za-z0-9_]/g, "_");
@@ -88,7 +88,7 @@ function lokiTransport(options) {
88
88
  headers,
89
89
  body: JSON.stringify(createPayload(events, options))
90
90
  });
91
- if (!response.ok) throw new Error(`lokiTransport failed with status ${response.status}`);
91
+ if (!response.ok) throw httpStatusError("lokiTransport", response);
92
92
  };
93
93
  return {
94
94
  name: transportName,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { toLevelValue, type LogEvent, type LoggerLevel, type Transport } from \"@loggerjs/core\";\n\nexport interface LokiTransportOptions {\n url: string;\n name?: string;\n minLevel?: LoggerLevel;\n headers?: Record<string, string>;\n labels?: Record<string, string | number | boolean | null | undefined>;\n labelTags?: readonly string[];\n defaultLabels?: boolean;\n structuredMetadata?: boolean;\n tenantId?: string;\n line?: (event: LogEvent) => string;\n fetchFn?: typeof fetch;\n}\n\nexport interface LokiPushPayload {\n streams: Array<{\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }>;\n}\n\nfunction sanitizeLabelName(name: string): string {\n const normalized = name.replace(/[^A-Za-z0-9_]/g, \"_\");\n if (/^[A-Za-z_]/.test(normalized)) return normalized;\n return `_${normalized}`;\n}\n\nfunction compact<T extends Record<string, unknown>>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) delete value[key];\n }\n return value;\n}\n\nfunction timestampUnixNs(event: LogEvent): string {\n return (BigInt(Math.trunc(event.time)) * 1_000_000n).toString();\n}\n\nfunction defaultLine(event: LogEvent): string {\n return event.message;\n}\n\nfunction metadataFor(event: LogEvent): Record<string, unknown> {\n return compact({\n id: event.id,\n seq: event.seq,\n logger: event.logger,\n level: event.levelName,\n type: event.type,\n tags: event.tags,\n data: event.data,\n error: event.error,\n context: event.context,\n trace: event.trace,\n source: event.source,\n });\n}\n\nfunction labelEntries(labels: Record<string, string>): string {\n let out = \"\";\n for (const [key, value] of Object.entries(labels)) out += `${key}=${value}\\n`;\n return out;\n}\n\nfunction streamLabels(event: LogEvent, options: LokiTransportOptions): Record<string, string> {\n const labels: Record<string, string> = {};\n if (options.defaultLabels ?? true) {\n labels.level = event.levelName;\n labels.logger = event.logger;\n }\n for (const [key, value] of Object.entries(options.labels ?? {})) {\n if (value !== undefined && value !== null) labels[sanitizeLabelName(key)] = String(value);\n }\n for (const tag of options.labelTags ?? []) {\n const value = event.tags?.[tag];\n if (value !== undefined && value !== null) labels[sanitizeLabelName(tag)] = String(value);\n }\n return labels;\n}\n\nfunction createPayload(events: LogEvent[], options: LokiTransportOptions): LokiPushPayload {\n const streams = new Map<\n string,\n {\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }\n >();\n const line = options.line ?? defaultLine;\n const includeMetadata = options.structuredMetadata ?? true;\n\n for (const event of events) {\n const labels = streamLabels(event, options);\n const key = labelEntries(labels);\n let stream = streams.get(key);\n if (!stream) {\n stream = { stream: labels, values: [] };\n streams.set(key, stream);\n }\n const value: [string, string] | [string, string, Record<string, unknown>] = includeMetadata\n ? [timestampUnixNs(event), line(event), metadataFor(event)]\n : [timestampUnixNs(event), line(event)];\n stream.values.push(value);\n }\n\n return { streams: [...streams.values()] };\n}\n\nexport function lokiTransport(options: LokiTransportOptions): Transport {\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n const transportName = options.name ?? \"loki\";\n\n const send = async (events: LogEvent[]) => {\n if (events.length === 0) return;\n if (!fetchFn) throw new Error(\"fetch is not available for lokiTransport\");\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n ...options.headers,\n };\n if (options.tenantId) headers[\"x-scope-orgid\"] = options.tenantId;\n const response = await fetchFn(options.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(createPayload(events, options)),\n });\n if (!response.ok) throw new Error(`lokiTransport failed with status ${response.status}`);\n };\n\n return {\n name: transportName,\n minLevel: options.minLevel,\n log(event) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n return send([event]);\n },\n logBatch(events) {\n const selected =\n options.minLevel === undefined\n ? events\n : events.filter((event) => event.level >= toLevelValue(options.minLevel));\n return send([...selected]);\n },\n };\n}\n"],"mappings":";;AAuBA,SAAS,kBAAkB,MAAsB;CAC/C,MAAM,aAAa,KAAK,QAAQ,kBAAkB,GAAG;CACrD,IAAI,aAAa,KAAK,UAAU,GAAG,OAAO;CAC1C,OAAO,IAAI;AACb;AAEA,SAAS,QAA2C,OAAa;CAC/D,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,MAAM,SAAS,KAAA,GAAW,OAAO,MAAM;CAE7C,OAAO;AACT;AAEA,SAAS,gBAAgB,OAAyB;CAChD,QAAQ,OAAO,KAAK,MAAM,MAAM,IAAI,CAAC,IAAI,SAAA,CAAY,SAAS;AAChE;AAEA,SAAS,YAAY,OAAyB;CAC5C,OAAO,MAAM;AACf;AAEA,SAAS,YAAY,OAA0C;CAC7D,OAAO,QAAQ;EACb,IAAI,MAAM;EACV,KAAK,MAAM;EACX,QAAQ,MAAM;EACd,OAAO,MAAM;EACb,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,OAAO,MAAM;EACb,SAAS,MAAM;EACf,OAAO,MAAM;EACb,QAAQ,MAAM;CAChB,CAAC;AACH;AAEA,SAAS,aAAa,QAAwC;CAC5D,IAAI,MAAM;CACV,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM;CAC1E,OAAO;AACT;AAEA,SAAS,aAAa,OAAiB,SAAuD;CAC5F,MAAM,SAAiC,CAAC;CACxC,IAAI,QAAQ,iBAAiB,MAAM;EACjC,OAAO,QAAQ,MAAM;EACrB,OAAO,SAAS,MAAM;CACxB;CACA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,UAAU,CAAC,CAAC,GAC5D,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO,kBAAkB,GAAG,KAAK,OAAO,KAAK;CAE1F,KAAK,MAAM,OAAO,QAAQ,aAAa,CAAC,GAAG;EACzC,MAAM,QAAQ,MAAM,OAAO;EAC3B,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO,kBAAkB,GAAG,KAAK,OAAO,KAAK;CAC1F;CACA,OAAO;AACT;AAEA,SAAS,cAAc,QAAoB,SAAgD;CACzF,MAAM,0BAAU,IAAI,IAMlB;CACF,MAAM,OAAO,QAAQ,QAAQ;CAC7B,MAAM,kBAAkB,QAAQ,sBAAsB;CAEtD,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,aAAa,OAAO,OAAO;EAC1C,MAAM,MAAM,aAAa,MAAM;EAC/B,IAAI,SAAS,QAAQ,IAAI,GAAG;EAC5B,IAAI,CAAC,QAAQ;GACX,SAAS;IAAE,QAAQ;IAAQ,QAAQ,CAAC;GAAE;GACtC,QAAQ,IAAI,KAAK,MAAM;EACzB;EACA,MAAM,QAAsE,kBACxE;GAAC,gBAAgB,KAAK;GAAG,KAAK,KAAK;GAAG,YAAY,KAAK;EAAC,IACxD,CAAC,gBAAgB,KAAK,GAAG,KAAK,KAAK,CAAC;EACxC,OAAO,OAAO,KAAK,KAAK;CAC1B;CAEA,OAAO,EAAE,SAAS,CAAC,GAAG,QAAQ,OAAO,CAAC,EAAE;AAC1C;AAEA,SAAgB,cAAc,SAA0C;CACtE,MAAM,UAAU,QAAQ,WAAW,WAAW,OAAO,KAAK,UAAU;CACpE,MAAM,gBAAgB,QAAQ,QAAQ;CAEtC,MAAM,OAAO,OAAO,WAAuB;EACzC,IAAI,OAAO,WAAW,GAAG;EACzB,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,0CAA0C;EACxE,MAAM,UAAkC;GACtC,gBAAgB;GAChB,GAAG,QAAQ;EACb;EACA,IAAI,QAAQ,UAAU,QAAQ,mBAAmB,QAAQ;EACzD,MAAM,WAAW,MAAM,QAAQ,QAAQ,KAAK;GAC1C,QAAQ;GACR;GACA,MAAM,KAAK,UAAU,cAAc,QAAQ,OAAO,CAAC;EACrD,CAAC;EACD,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,oCAAoC,SAAS,QAAQ;CACzF;CAEA,OAAO;EACL,MAAM;EACN,UAAU,QAAQ;EAClB,IAAI,OAAO;GACT,IAAI,QAAQ,aAAa,KAAA,KAAa,MAAM,QAAQ,aAAa,QAAQ,QAAQ,GAAG;GACpF,OAAO,KAAK,CAAC,KAAK,CAAC;EACrB;EACA,SAAS,QAAQ;GAKf,OAAO,KAAK,CAAC,GAHX,QAAQ,aAAa,KAAA,IACjB,SACA,OAAO,QAAQ,UAAU,MAAM,SAAS,aAAa,QAAQ,QAAQ,CAAC,CACpD,CAAC;EAC3B;CACF;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import {\n toLevelValue,\n type LogEvent,\n type LoggerLevel,\n type Transport,\n httpStatusError,\n} from \"@loggerjs/core\";\n\nexport interface LokiTransportOptions {\n url: string;\n name?: string;\n minLevel?: LoggerLevel;\n headers?: Record<string, string>;\n labels?: Record<string, string | number | boolean | null | undefined>;\n labelTags?: readonly string[];\n defaultLabels?: boolean;\n structuredMetadata?: boolean;\n tenantId?: string;\n line?: (event: LogEvent) => string;\n fetchFn?: typeof fetch;\n}\n\nexport interface LokiPushPayload {\n streams: Array<{\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }>;\n}\n\nfunction sanitizeLabelName(name: string): string {\n const normalized = name.replace(/[^A-Za-z0-9_]/g, \"_\");\n if (/^[A-Za-z_]/.test(normalized)) return normalized;\n return `_${normalized}`;\n}\n\nfunction compact<T extends Record<string, unknown>>(value: T): T {\n for (const key of Object.keys(value)) {\n if (value[key] === undefined) delete value[key];\n }\n return value;\n}\n\nfunction timestampUnixNs(event: LogEvent): string {\n return (BigInt(Math.trunc(event.time)) * 1_000_000n).toString();\n}\n\nfunction defaultLine(event: LogEvent): string {\n return event.message;\n}\n\nfunction metadataFor(event: LogEvent): Record<string, unknown> {\n return compact({\n id: event.id,\n seq: event.seq,\n logger: event.logger,\n level: event.levelName,\n type: event.type,\n tags: event.tags,\n data: event.data,\n error: event.error,\n context: event.context,\n trace: event.trace,\n source: event.source,\n });\n}\n\nfunction labelEntries(labels: Record<string, string>): string {\n let out = \"\";\n for (const [key, value] of Object.entries(labels)) out += `${key}=${value}\\n`;\n return out;\n}\n\nfunction streamLabels(event: LogEvent, options: LokiTransportOptions): Record<string, string> {\n const labels: Record<string, string> = {};\n if (options.defaultLabels ?? true) {\n labels.level = event.levelName;\n labels.logger = event.logger;\n }\n for (const [key, value] of Object.entries(options.labels ?? {})) {\n if (value !== undefined && value !== null) labels[sanitizeLabelName(key)] = String(value);\n }\n for (const tag of options.labelTags ?? []) {\n const value = event.tags?.[tag];\n if (value !== undefined && value !== null) labels[sanitizeLabelName(tag)] = String(value);\n }\n return labels;\n}\n\nfunction createPayload(events: LogEvent[], options: LokiTransportOptions): LokiPushPayload {\n const streams = new Map<\n string,\n {\n stream: Record<string, string>;\n values: Array<[string, string] | [string, string, Record<string, unknown>]>;\n }\n >();\n const line = options.line ?? defaultLine;\n const includeMetadata = options.structuredMetadata ?? true;\n\n for (const event of events) {\n const labels = streamLabels(event, options);\n const key = labelEntries(labels);\n let stream = streams.get(key);\n if (!stream) {\n stream = { stream: labels, values: [] };\n streams.set(key, stream);\n }\n const value: [string, string] | [string, string, Record<string, unknown>] = includeMetadata\n ? [timestampUnixNs(event), line(event), metadataFor(event)]\n : [timestampUnixNs(event), line(event)];\n stream.values.push(value);\n }\n\n return { streams: [...streams.values()] };\n}\n\nexport function lokiTransport(options: LokiTransportOptions): Transport {\n const fetchFn = options.fetchFn ?? globalThis.fetch?.bind(globalThis);\n const transportName = options.name ?? \"loki\";\n\n const send = async (events: LogEvent[]) => {\n if (events.length === 0) return;\n if (!fetchFn) throw new Error(\"fetch is not available for lokiTransport\");\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n ...options.headers,\n };\n if (options.tenantId) headers[\"x-scope-orgid\"] = options.tenantId;\n const response = await fetchFn(options.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(createPayload(events, options)),\n });\n if (!response.ok) throw httpStatusError(\"lokiTransport\", response);\n };\n\n return {\n name: transportName,\n minLevel: options.minLevel,\n log(event) {\n if (options.minLevel !== undefined && event.level < toLevelValue(options.minLevel)) return;\n return send([event]);\n },\n logBatch(events) {\n const selected =\n options.minLevel === undefined\n ? events\n : events.filter((event) => event.level >= toLevelValue(options.minLevel));\n return send([...selected]);\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loggerjs/loki",
3
- "version": "0.5.6",
3
+ "version": "0.7.0",
4
4
  "description": "Grafana Loki transport for loggerjs.",
5
5
  "homepage": "https://github.com/jskits/loggerjs/tree/main/packages/loki#readme",
6
6
  "bugs": {
@@ -39,7 +39,10 @@
39
39
  "provenance": true
40
40
  },
41
41
  "dependencies": {
42
- "@loggerjs/core": "0.5.6"
42
+ "@loggerjs/core": "^0.7.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=20.19.0"
43
46
  },
44
47
  "scripts": {
45
48
  "build": "pnpm run clean && pnpm run build:js && pnpm run build:types",