jsonl-logger 0.3.0 → 0.5.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 +53 -1
- package/dist/google-cloud-logging.js +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +7 -7
- package/dist/intercept.js +7 -7
- package/dist/preload.js +7 -7
- package/dist/types.d.ts +11 -0
- package/dist/victoria-logs.js +1 -1
- package/package.json +2 -2
- package/src/google-cloud-logging.ts +8 -0
- package/src/index.ts +52 -10
- package/src/intercept.ts +16 -2
- package/src/types.ts +19 -0
- package/src/victoria-logs.ts +7 -0
package/README.md
CHANGED
|
@@ -83,6 +83,58 @@ console.log('plain text') // → structured JSON
|
|
|
83
83
|
originalConsole.log('bypass interception')
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
## OpenTelemetry
|
|
87
|
+
|
|
88
|
+
The logger supports automatic trace context injection. Supply a `traceContext` getter that returns the active span's trace/span IDs — the formatter maps them to platform-specific fields automatically.
|
|
89
|
+
|
|
90
|
+
### With `@opentelemetry/api`
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { trace } from '@opentelemetry/api'
|
|
94
|
+
import { Logger } from 'jsonl-logger'
|
|
95
|
+
|
|
96
|
+
const logger = new Logger({}, {
|
|
97
|
+
traceContext: () => {
|
|
98
|
+
const span = trace.getActiveSpan()
|
|
99
|
+
if (!span) return undefined
|
|
100
|
+
const { traceId, spanId, traceFlags } = span.spanContext()
|
|
101
|
+
return { traceId, spanId, traceFlags }
|
|
102
|
+
},
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
logger.info('request handled', { path: '/api' })
|
|
106
|
+
// GCL output includes "logging.googleapis.com/trace", "logging.googleapis.com/spanId", etc.
|
|
107
|
+
// VictoriaLogs output includes "trace_id", "span_id", etc.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Custom trace context
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
const logger = new Logger({}, {
|
|
114
|
+
traceContext: () => ({
|
|
115
|
+
traceId: myTracer.currentTraceId(),
|
|
116
|
+
spanId: myTracer.currentSpanId(),
|
|
117
|
+
}),
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The `traceContext` option is also available on `intercept()`:
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { intercept } from 'jsonl-logger/intercept'
|
|
125
|
+
|
|
126
|
+
intercept({
|
|
127
|
+
traceContext: () => {
|
|
128
|
+
const span = trace.getActiveSpan()
|
|
129
|
+
if (!span) return undefined
|
|
130
|
+
const { traceId, spanId, traceFlags } = span.spanContext()
|
|
131
|
+
return { traceId, spanId, traceFlags }
|
|
132
|
+
},
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Child loggers inherit the `traceContext` getter from their parent.
|
|
137
|
+
|
|
86
138
|
## Next.js Integration
|
|
87
139
|
|
|
88
140
|
The preload module reads `LOG_FORMAT` and only activates when it's set. Safe to include unconditionally — it's a no-op without `LOG_FORMAT`.
|
|
@@ -188,7 +240,7 @@ The logger auto-detects the runtime and uses the fastest available I/O:
|
|
|
188
240
|
|
|
189
241
|
| Subpath | Export |
|
|
190
242
|
|---------|--------|
|
|
191
|
-
| `jsonl-logger` | `Logger`, `logger`, `errorInfo()`, types (`ErrorInfo`, `LogRecord`, etc.) |
|
|
243
|
+
| `jsonl-logger` | `Logger`, `logger`, `errorInfo()`, types (`ErrorInfo`, `LogRecord`, `TraceContext`, etc.) |
|
|
192
244
|
| `jsonl-logger/google-cloud-logging` | `GoogleCloudLogging` formatter |
|
|
193
245
|
| `jsonl-logger/victoria-logs` | `VictoriaLogs` formatter |
|
|
194
246
|
| `jsonl-logger/intercept` | `intercept()`, `originalConsole` |
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var g={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},a={messageKey:"message",format(e){let t={message:e.message,timestamp:e.timestamp,severity:g[e.level],...e.context};if(e.trace){if(t["logging.googleapis.com/trace"]=e.trace.traceId,t["logging.googleapis.com/spanId"]=e.trace.spanId,e.trace.traceFlags!==void 0)t["logging.googleapis.com/trace_sampled"]=(e.trace.traceFlags&1)===1}return t}};export{a as GoogleCloudLogging};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ErrorInfo, LogContext, LoggerOptions } from './types';
|
|
2
|
-
export type { ErrorInfo, Formatter, FormatterName, InterceptOptions, LogContext, LoggerOptions, LogLevel, LogRecord, } from './types';
|
|
2
|
+
export type { ErrorInfo, Formatter, FormatterName, InterceptOptions, LabelStyle, LogContext, LoggerOptions, LogLevel, LogRecord, TraceContext, } from './types';
|
|
3
3
|
export { logLevelValues, stripAnsi } from './types';
|
|
4
4
|
export declare function errorInfo(err: Error): ErrorInfo;
|
|
5
5
|
export declare class Logger {
|
|
@@ -7,10 +7,13 @@ export declare class Logger {
|
|
|
7
7
|
private min;
|
|
8
8
|
private json;
|
|
9
9
|
private fmt;
|
|
10
|
+
private labels;
|
|
11
|
+
private tc?;
|
|
10
12
|
constructor(context?: LogContext, options?: LoggerOptions);
|
|
11
13
|
child(context: LogContext): Logger;
|
|
12
|
-
private
|
|
14
|
+
private emit;
|
|
13
15
|
private logPlain;
|
|
16
|
+
log(message: string, meta?: LogContext): void;
|
|
14
17
|
debug(message: string, meta?: LogContext): void;
|
|
15
18
|
info(message: string, meta?: LogContext): void;
|
|
16
19
|
warn(message: string, meta?: LogContext): void;
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
`);else if(
|
|
3
|
-
`);if(
|
|
4
|
-
${
|
|
5
|
-
Caused by: ${
|
|
6
|
-
${
|
|
7
|
-
Caused by: ${
|
|
1
|
+
var S={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},L={messageKey:"message",format(t){let e={message:t.message,timestamp:t.timestamp,severity:S[t.level],...t.context};if(t.trace){if(e["logging.googleapis.com/trace"]=t.trace.traceId,e["logging.googleapis.com/spanId"]=t.trace.spanId,t.trace.traceFlags!==void 0)e["logging.googleapis.com/trace_sampled"]=(t.trace.traceFlags&1)===1}return e}};var x={messageKey:"_msg",format(t){let e={_msg:t.message,_time:t.timestamp,level:t.level,...t.context};if(t.trace){if(e.trace_id=t.trace.traceId,e.span_id=t.trace.spanId,t.trace.traceFlags!==void 0)e.trace_flags=t.trace.traceFlags}return e}};var R=process.env.LOG_LABELS==="text"?"text":process.env.LOG_LABELS==="none"?"none":"icon",c=process.env.LOG_FORMAT,w=!!c,a={debug:0,info:1,warn:2,error:3,fatal:4},y=/\x1b\[[0-9;]*m/g;function p(t){return t.replace(y,"")}function d(t,e,o="error"){if(t[`${o}.name`]=e.name,t[`${o}.message`]=e.message,e.stack)t[`${o}.stack`]=e.stack;if(e.cause)d(t,e.cause,`${o}.cause`)}var u=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",C=u==="deno"?new TextEncoder:null;function I(t,e){if(u==="node")(e?process.stderr??process.stdout:process.stdout).write(`${t}
|
|
2
|
+
`);else if(u==="deno"&&C){let o=C.encode(`${t}
|
|
3
|
+
`);if(e)Deno.stderr.writeSync(o);else Deno.stdout.writeSync(o)}else if(e)console.error(t);else console.log(t)}var E={"google-cloud-logging":L,"victoria-logs":x},O=c&&E[c]||L,F=w,j=process.env.LOG_LEVEL||(F?"info":"debug"),N={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function $(t,e){return e.add(t),{name:t.name,message:t.message,stack:t.stack,...t.cause instanceof Error&&!e.has(t.cause)?{cause:$(t.cause,e)}:{}}}function _(t){return $(t,new WeakSet)}var G={debug:"◆",info:"●",warn:"▲",error:"✖",fatal:"‼"};class h{ctx;min;json;fmt;labels;tc;constructor(t,e){this.ctx=t||{},this.json=e?.json??F,this.fmt=e?.formatter??O,this.labels=e?.labels??R,this.tc=e?.traceContext;let o=e?.level??j;this.min=a[o]??a.info}child(t){let e=new h({...this.ctx,...t},{json:this.json,formatter:this.fmt,labels:this.labels,traceContext:this.tc});return e.min=this.min,e}emit(t,e,o,i,v){if(a[t]<this.min)return;let n={level:t,message:this.json?p(e).trim():e,timestamp:new Date().toISOString(),context:o?{...this.ctx,...o}:this.ctx};if(this.tc)n.trace=this.tc();if(i)n.error=_(i);if(this.json){let g=this.fmt.format(n);if(n.error)d(g,n.error);I(JSON.stringify(g),N[t])}else this.logPlain(t,n,v)}logPlain(t,e,o){let i={debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m"},v="\x1B[0m",n=i[t],g=new Date(e.timestamp).toLocaleTimeString("en-US",{hour12:!1}),r;if(this.labels==="none")r=" ";else if(this.labels==="text")r=o?" ":t.toUpperCase().padEnd(5);else r=o?" ":`${G[t]} `;let b=e.context,k=Object.keys(b).length>0?` ${JSON.stringify(b)}`:"",m="";if(e.error){let s=e.error,f=!0;while(s){if(s.stack)m+=f?`
|
|
4
|
+
${s.stack}`:`
|
|
5
|
+
Caused by: ${s.stack}`;else m+=f?`
|
|
6
|
+
${s.name}: ${s.message}`:`
|
|
7
|
+
Caused by: ${s.name}: ${s.message}`;s=s.cause,f=!1}}let l=`${n}${g} ${r}\x1B[0m ${e.message}${k}${m}`;switch(t){case"debug":console.debug(l);break;case"warn":console.warn(l);break;case"error":case"fatal":console.error(l);break;default:console.log(l)}}log(t,e){this.emit("info",t,e,void 0,!0)}debug(t,e){this.emit("debug",t,e)}info(t,e){this.emit("info",t,e)}warn(t,e){this.emit("warn",t,e)}error(t,e,o){this.emit("error",t,e,o)}fatal(t,e,o){this.emit("fatal",t,e,o)}}var K=new h;export{p as stripAnsi,K as logger,a as logLevelValues,_ as errorInfo,h as Logger};
|
package/dist/intercept.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
`);else if(
|
|
3
|
-
`);if(
|
|
4
|
-
${
|
|
5
|
-
Caused by: ${
|
|
6
|
-
${
|
|
7
|
-
Caused by: ${
|
|
1
|
+
var y={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},l={messageKey:"message",format(t){let o={message:t.message,timestamp:t.timestamp,severity:y[t.level],...t.context};if(t.trace){if(o["logging.googleapis.com/trace"]=t.trace.traceId,o["logging.googleapis.com/spanId"]=t.trace.spanId,t.trace.traceFlags!==void 0)o["logging.googleapis.com/trace_sampled"]=(t.trace.traceFlags&1)===1}return o}};var F={messageKey:"_msg",format(t){let o={_msg:t.message,_time:t.timestamp,level:t.level,...t.context};if(t.trace){if(o.trace_id=t.trace.traceId,o.span_id=t.trace.spanId,t.trace.traceFlags!==void 0)o.trace_flags=t.trace.traceFlags}return o}};var $=process.env.LOG_LABELS==="text"?"text":process.env.LOG_LABELS==="none"?"none":"icon",R=process.env.LOG_FORMAT,k=!!R,m={debug:0,info:1,warn:2,error:3,fatal:4},_=/\x1b\[[0-9;]*m/g;function b(t){return t.replace(_,"")}function u(t,o,e="error"){if(t[`${e}.name`]=o.name,t[`${e}.message`]=o.message,o.stack)t[`${e}.stack`]=o.stack;if(o.cause)u(t,o.cause,`${e}.cause`)}var I=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",v=I==="deno"?new TextEncoder:null;function h(t,o){if(I==="node")(o?process.stderr??process.stdout:process.stdout).write(`${t}
|
|
2
|
+
`);else if(I==="deno"&&v){let e=v.encode(`${t}
|
|
3
|
+
`);if(o)Deno.stderr.writeSync(e);else Deno.stdout.writeSync(e)}else if(o)console.error(t);else console.log(t)}var E={"google-cloud-logging":l,"victoria-logs":F},N=R&&E[R]||l,S=k,T=process.env.LOG_LEVEL||(S?"info":"debug"),G={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function r(t,o){return o.add(t),{name:t.name,message:t.message,stack:t.stack,...t.cause instanceof Error&&!o.has(t.cause)?{cause:r(t.cause,o)}:{}}}function d(t){return r(t,new WeakSet)}var A={debug:"◆",info:"●",warn:"▲",error:"✖",fatal:"‼"};class w{ctx;min;json;fmt;labels;tc;constructor(t,o){this.ctx=t||{},this.json=o?.json??S,this.fmt=o?.formatter??N,this.labels=o?.labels??$,this.tc=o?.traceContext;let e=o?.level??T;this.min=m[e]??m.info}child(t){let o=new w({...this.ctx,...t},{json:this.json,formatter:this.fmt,labels:this.labels,traceContext:this.tc});return o.min=this.min,o}emit(t,o,e,n,f){if(m[t]<this.min)return;let i={level:t,message:this.json?b(o).trim():o,timestamp:new Date().toISOString(),context:e?{...this.ctx,...e}:this.ctx};if(this.tc)i.trace=this.tc();if(n)i.error=d(n);if(this.json){let s=this.fmt.format(i);if(i.error)u(s,i.error);h(JSON.stringify(s),G[t])}else this.logPlain(t,i,f)}logPlain(t,o,e){let n={debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m"},f="\x1B[0m",i=n[t],s=new Date(o.timestamp).toLocaleTimeString("en-US",{hour12:!1}),a;if(this.labels==="none")a=" ";else if(this.labels==="text")a=e?" ":t.toUpperCase().padEnd(5);else a=e?" ":`${A[t]} `;let p=o.context,x=Object.keys(p).length>0?` ${JSON.stringify(p)}`:"",g="";if(o.error){let c=o.error,C=!0;while(c){if(c.stack)g+=C?`
|
|
4
|
+
${c.stack}`:`
|
|
5
|
+
Caused by: ${c.stack}`;else g+=C?`
|
|
6
|
+
${c.name}: ${c.message}`:`
|
|
7
|
+
Caused by: ${c.name}: ${c.message}`;c=c.cause,C=!1}}let L=`${i}${s} ${a}\x1B[0m ${o.message}${x}${g}`;switch(t){case"debug":console.debug(L);break;case"warn":console.warn(L);break;case"error":case"fatal":console.error(L);break;default:console.log(L)}}log(t,o){this.emit("info",t,o,void 0,!0)}debug(t,o){this.emit("debug",t,o)}info(t,o){this.emit("info",t,o)}warn(t,o){this.emit("warn",t,o)}error(t,o,e){this.emit("error",t,o,e)}fatal(t,o,e){this.emit("fatal",t,o,e)}}var X=new w;var tt={log:console.log.bind(console),info:console.info.bind(console),warn:console.warn.bind(console),error:console.error.bind(console),debug:console.debug.bind(console)};function J(...t){let o="";for(let e=0;e<t.length;e++){if(e>0)o+=" ";let n=t[e];if(typeof n==="string")o+=b(n);else if(n instanceof Error)o+=n.message;else try{o+=JSON.stringify(n)}catch{o+=String(n)}}return o.trim()}function U(...t){let o;for(let e of t)if(typeof e==="object"&&e!==null&&!(e instanceof Error)&&!Array.isArray(e)){if(!o)o={};Object.assign(o,e)}return o}function V(...t){for(let o of t)if(o instanceof Error)return o}var O={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function W(t,o,e,n,f){let i=`"${o.messageKey}"`;return(...s)=>{if(s.length===1&&typeof s[0]==="string"&&s[0].charCodeAt(0)===123&&s[0].includes(i)){h(s[0],O[t]);return}if(m[t]<e)return;let a=J(...s);if(n&&!n(t,a))return;let p=U(...s),x=V(...s),g={level:t,message:a,timestamp:new Date().toISOString(),context:p||{},error:x?d(x):void 0,trace:f?.()},L=o.format(g);if(g.error)u(L,g.error);h(JSON.stringify(L),O[t])}}var j="__jsonlLoggerIntercepted";function ot(t){if(globalThis[j])return;globalThis[j]=!0;let o=t?.formatter??l,e=m[t?.level??"debug"],n=t?.filter,f=t?.traceContext,i=[["log","info"],["info","info"],["warn","warn"],["error","error"],["debug","debug"]];for(let[s,a]of i)console[s]=W(a,o,e,n,f)}export{tt as originalConsole,ot as intercept};
|
package/dist/preload.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var N={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},
|
|
2
|
-
`);else if(
|
|
3
|
-
`);if(
|
|
4
|
-
${
|
|
5
|
-
Caused by: ${
|
|
6
|
-
${
|
|
7
|
-
Caused by: ${
|
|
1
|
+
var N={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},m={messageKey:"message",format(t){let o={message:t.message,timestamp:t.timestamp,severity:N[t.level],...t.context};if(t.trace){if(o["logging.googleapis.com/trace"]=t.trace.traceId,o["logging.googleapis.com/spanId"]=t.trace.spanId,t.trace.traceFlags!==void 0)o["logging.googleapis.com/trace_sampled"]=(t.trace.traceFlags&1)===1}return o}};var R={messageKey:"_msg",format(t){let o={_msg:t.message,_time:t.timestamp,level:t.level,...t.context};if(t.trace){if(o.trace_id=t.trace.traceId,o.span_id=t.trace.spanId,t.trace.traceFlags!==void 0)o.trace_flags=t.trace.traceFlags}return o}};var I=process.env.LOG_LABELS==="text"?"text":process.env.LOG_LABELS==="none"?"none":"icon",l=process.env.LOG_FORMAT,$=!!l,g={debug:0,info:1,warn:2,error:3,fatal:4},_=/\x1b\[[0-9;]*m/g;function u(t){return t.replace(_,"")}function p(t,o,e="error"){if(t[`${e}.name`]=o.name,t[`${e}.message`]=o.message,o.stack)t[`${e}.stack`]=o.stack;if(o.cause)p(t,o.cause,`${e}.cause`)}var F=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",w=F==="deno"?new TextEncoder:null;function b(t,o){if(F==="node")(o?process.stderr??process.stdout:process.stdout).write(`${t}
|
|
2
|
+
`);else if(F==="deno"&&w){let e=w.encode(`${t}
|
|
3
|
+
`);if(o)Deno.stderr.writeSync(e);else Deno.stdout.writeSync(e)}else if(o)console.error(t);else console.log(t)}var j={"google-cloud-logging":m,"victoria-logs":R},G=l&&j[l]||m,k=$,T=process.env.LOG_LEVEL||(k?"info":"debug"),V={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function S(t,o){return o.add(t),{name:t.name,message:t.message,stack:t.stack,...t.cause instanceof Error&&!o.has(t.cause)?{cause:S(t.cause,o)}:{}}}function C(t){return S(t,new WeakSet)}var A={debug:"◆",info:"●",warn:"▲",error:"✖",fatal:"‼"};class v{ctx;min;json;fmt;labels;tc;constructor(t,o){this.ctx=t||{},this.json=o?.json??k,this.fmt=o?.formatter??G,this.labels=o?.labels??I,this.tc=o?.traceContext;let e=o?.level??T;this.min=g[e]??g.info}child(t){let o=new v({...this.ctx,...t},{json:this.json,formatter:this.fmt,labels:this.labels,traceContext:this.tc});return o.min=this.min,o}emit(t,o,e,n,r){if(g[t]<this.min)return;let i={level:t,message:this.json?u(o).trim():o,timestamp:new Date().toISOString(),context:e?{...this.ctx,...e}:this.ctx};if(this.tc)i.trace=this.tc();if(n)i.error=C(n);if(this.json){let s=this.fmt.format(i);if(i.error)p(s,i.error);b(JSON.stringify(s),V[t])}else this.logPlain(t,i,r)}logPlain(t,o,e){let n={debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m"},r="\x1B[0m",i=n[t],s=new Date(o.timestamp).toLocaleTimeString("en-US",{hour12:!1}),c;if(this.labels==="none")c=" ";else if(this.labels==="text")c=e?" ":t.toUpperCase().padEnd(5);else c=e?" ":`${A[t]} `;let h=o.context,x=Object.keys(h).length>0?` ${JSON.stringify(h)}`:"",f="";if(o.error){let a=o.error,d=!0;while(a){if(a.stack)f+=d?`
|
|
4
|
+
${a.stack}`:`
|
|
5
|
+
Caused by: ${a.stack}`;else f+=d?`
|
|
6
|
+
${a.name}: ${a.message}`:`
|
|
7
|
+
Caused by: ${a.name}: ${a.message}`;a=a.cause,d=!1}}let L=`${i}${s} ${c}\x1B[0m ${o.message}${x}${f}`;switch(t){case"debug":console.debug(L);break;case"warn":console.warn(L);break;case"error":case"fatal":console.error(L);break;default:console.log(L)}}log(t,o){this.emit("info",t,o,void 0,!0)}debug(t,o){this.emit("debug",t,o)}info(t,o){this.emit("info",t,o)}warn(t,o){this.emit("warn",t,o)}error(t,o,e){this.emit("error",t,o,e)}fatal(t,o,e){this.emit("fatal",t,o,e)}}var Y=new v;var ot={log:console.log.bind(console),info:console.info.bind(console),warn:console.warn.bind(console),error:console.error.bind(console),debug:console.debug.bind(console)};function J(...t){let o="";for(let e=0;e<t.length;e++){if(e>0)o+=" ";let n=t[e];if(typeof n==="string")o+=u(n);else if(n instanceof Error)o+=n.message;else try{o+=JSON.stringify(n)}catch{o+=String(n)}}return o.trim()}function U(...t){let o;for(let e of t)if(typeof e==="object"&&e!==null&&!(e instanceof Error)&&!Array.isArray(e)){if(!o)o={};Object.assign(o,e)}return o}function W(...t){for(let o of t)if(o instanceof Error)return o}var y={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function B(t,o,e,n,r){let i=`"${o.messageKey}"`;return(...s)=>{if(s.length===1&&typeof s[0]==="string"&&s[0].charCodeAt(0)===123&&s[0].includes(i)){b(s[0],y[t]);return}if(g[t]<e)return;let c=J(...s);if(n&&!n(t,c))return;let h=U(...s),x=W(...s),f={level:t,message:c,timestamp:new Date().toISOString(),context:h||{},error:x?C(x):void 0,trace:r?.()},L=o.format(f);if(f.error)p(L,f.error);b(JSON.stringify(L),y[t])}}var O="__jsonlLoggerIntercepted";function E(t){if(globalThis[O])return;globalThis[O]=!0;let o=t?.formatter??m,e=g[t?.level??"debug"],n=t?.filter,r=t?.traceContext,i=[["log","info"],["info","info"],["warn","warn"],["error","error"],["debug","debug"]];for(let[s,c]of i)console[s]=B(c,o,e,n,r)}if(l){let o={"google-cloud-logging":m,"victoria-logs":R}[l]??m,e=process.env.LOG_LEVEL||"info";E({formatter:o,level:e})}
|
package/dist/types.d.ts
CHANGED
|
@@ -6,21 +6,31 @@ export type ErrorInfo = {
|
|
|
6
6
|
stack?: string;
|
|
7
7
|
cause?: ErrorInfo;
|
|
8
8
|
};
|
|
9
|
+
export type TraceContext = {
|
|
10
|
+
traceId: string;
|
|
11
|
+
spanId: string;
|
|
12
|
+
traceFlags?: number;
|
|
13
|
+
};
|
|
9
14
|
export type LogRecord = {
|
|
10
15
|
level: LogLevel;
|
|
11
16
|
message: string;
|
|
12
17
|
timestamp: string;
|
|
13
18
|
context: LogContext;
|
|
14
19
|
error?: ErrorInfo;
|
|
20
|
+
trace?: TraceContext;
|
|
15
21
|
};
|
|
16
22
|
export type Formatter = {
|
|
17
23
|
format: (record: LogRecord) => Record<string, unknown>;
|
|
18
24
|
messageKey: string;
|
|
19
25
|
};
|
|
26
|
+
export type LabelStyle = 'icon' | 'none' | 'text';
|
|
27
|
+
export declare const defaultLabelStyle: LabelStyle;
|
|
20
28
|
export type LoggerOptions = {
|
|
21
29
|
formatter?: Formatter;
|
|
22
30
|
json?: boolean;
|
|
31
|
+
labels?: LabelStyle;
|
|
23
32
|
level?: LogLevel;
|
|
33
|
+
traceContext?: () => TraceContext | undefined;
|
|
24
34
|
};
|
|
25
35
|
export type FormatterName = 'google-cloud-logging' | 'victoria-logs';
|
|
26
36
|
export declare const defaultFormat: FormatterName | undefined;
|
|
@@ -29,6 +39,7 @@ export type InterceptOptions = {
|
|
|
29
39
|
formatter?: Formatter;
|
|
30
40
|
filter?: (level: LogLevel, message: string) => boolean;
|
|
31
41
|
level?: LogLevel;
|
|
42
|
+
traceContext?: () => TraceContext | undefined;
|
|
32
43
|
};
|
|
33
44
|
export declare const logLevelValues: Record<LogLevel, number>;
|
|
34
45
|
export declare function stripAnsi(str: string): string;
|
package/dist/victoria-logs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var a={messageKey:"_msg",format(t){let e={_msg:t.message,_time:t.timestamp,level:t.level,...t.context};if(t.trace){if(e.trace_id=t.trace.traceId,e.span_id=t.trace.spanId,t.trace.traceFlags!==void 0)e.trace_flags=t.trace.traceFlags}return e}};export{a as VictoriaLogs};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jsonl-logger",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Lightweight ESM-only JSON Lines logger with pluggable formatters for Google Cloud Logging, VictoriaLogs, and more",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"test": "bun test"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@biomejs/biome": "2.4.
|
|
52
|
+
"@biomejs/biome": "2.4.3",
|
|
53
53
|
"@types/bun": "1.3.9",
|
|
54
54
|
"typescript": "5.9.3"
|
|
55
55
|
},
|
|
@@ -17,6 +17,14 @@ export const GoogleCloudLogging: Formatter = {
|
|
|
17
17
|
severity: severityMap[record.level],
|
|
18
18
|
...record.context,
|
|
19
19
|
}
|
|
20
|
+
if (record.trace) {
|
|
21
|
+
entry['logging.googleapis.com/trace'] = record.trace.traceId
|
|
22
|
+
entry['logging.googleapis.com/spanId'] = record.trace.spanId
|
|
23
|
+
if (record.trace.traceFlags !== undefined) {
|
|
24
|
+
entry['logging.googleapis.com/trace_sampled'] =
|
|
25
|
+
(record.trace.traceFlags & 1) === 1
|
|
26
|
+
}
|
|
27
|
+
}
|
|
20
28
|
return entry
|
|
21
29
|
},
|
|
22
30
|
}
|
package/src/index.ts
CHANGED
|
@@ -3,13 +3,16 @@ import type {
|
|
|
3
3
|
ErrorInfo,
|
|
4
4
|
Formatter,
|
|
5
5
|
FormatterName,
|
|
6
|
+
LabelStyle,
|
|
6
7
|
LogContext,
|
|
7
8
|
LoggerOptions,
|
|
8
9
|
LogLevel,
|
|
9
10
|
LogRecord,
|
|
11
|
+
TraceContext,
|
|
10
12
|
} from './types'
|
|
11
13
|
import {
|
|
12
14
|
defaultFormat,
|
|
15
|
+
defaultLabelStyle,
|
|
13
16
|
flattenError,
|
|
14
17
|
isJsonMode,
|
|
15
18
|
logLevelValues,
|
|
@@ -23,10 +26,12 @@ export type {
|
|
|
23
26
|
Formatter,
|
|
24
27
|
FormatterName,
|
|
25
28
|
InterceptOptions,
|
|
29
|
+
LabelStyle,
|
|
26
30
|
LogContext,
|
|
27
31
|
LoggerOptions,
|
|
28
32
|
LogLevel,
|
|
29
33
|
LogRecord,
|
|
34
|
+
TraceContext,
|
|
30
35
|
} from './types'
|
|
31
36
|
export { logLevelValues, stripAnsi } from './types'
|
|
32
37
|
|
|
@@ -66,16 +71,28 @@ export function errorInfo(err: Error): ErrorInfo {
|
|
|
66
71
|
return extractErrorInfo(err, new WeakSet())
|
|
67
72
|
}
|
|
68
73
|
|
|
74
|
+
const levelIcons: Record<LogLevel, string> = {
|
|
75
|
+
debug: '◆',
|
|
76
|
+
info: '●',
|
|
77
|
+
warn: '▲',
|
|
78
|
+
error: '✖',
|
|
79
|
+
fatal: '‼',
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
export class Logger {
|
|
70
83
|
private ctx: LogContext
|
|
71
84
|
private min: number
|
|
72
85
|
private json: boolean
|
|
73
86
|
private fmt: Formatter
|
|
87
|
+
private labels: LabelStyle
|
|
88
|
+
private tc?: () => TraceContext | undefined
|
|
74
89
|
|
|
75
90
|
constructor(context?: LogContext, options?: LoggerOptions) {
|
|
76
91
|
this.ctx = context || {}
|
|
77
92
|
this.json = options?.json ?? defaultJson
|
|
78
93
|
this.fmt = options?.formatter ?? defaultFormatter
|
|
94
|
+
this.labels = options?.labels ?? defaultLabelStyle
|
|
95
|
+
this.tc = options?.traceContext
|
|
79
96
|
const level: LogLevel = options?.level ?? defaultLevel
|
|
80
97
|
this.min = logLevelValues[level] ?? logLevelValues.info
|
|
81
98
|
}
|
|
@@ -86,17 +103,20 @@ export class Logger {
|
|
|
86
103
|
{
|
|
87
104
|
json: this.json,
|
|
88
105
|
formatter: this.fmt,
|
|
106
|
+
labels: this.labels,
|
|
107
|
+
traceContext: this.tc,
|
|
89
108
|
},
|
|
90
109
|
)
|
|
91
110
|
child.min = this.min
|
|
92
111
|
return child
|
|
93
112
|
}
|
|
94
113
|
|
|
95
|
-
private
|
|
114
|
+
private emit(
|
|
96
115
|
level: LogLevel,
|
|
97
116
|
message: string,
|
|
98
117
|
meta?: LogContext,
|
|
99
118
|
err?: Error,
|
|
119
|
+
neutral?: boolean,
|
|
100
120
|
): void {
|
|
101
121
|
if (logLevelValues[level] < this.min) return
|
|
102
122
|
|
|
@@ -107,6 +127,10 @@ export class Logger {
|
|
|
107
127
|
context: meta ? { ...this.ctx, ...meta } : this.ctx,
|
|
108
128
|
}
|
|
109
129
|
|
|
130
|
+
if (this.tc) {
|
|
131
|
+
record.trace = this.tc()
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
if (err) {
|
|
111
135
|
record.error = errorInfo(err)
|
|
112
136
|
}
|
|
@@ -116,11 +140,15 @@ export class Logger {
|
|
|
116
140
|
if (record.error) flattenError(formatted, record.error)
|
|
117
141
|
write(JSON.stringify(formatted), isErrorLevel[level])
|
|
118
142
|
} else {
|
|
119
|
-
this.logPlain(level, record)
|
|
143
|
+
this.logPlain(level, record, neutral)
|
|
120
144
|
}
|
|
121
145
|
}
|
|
122
146
|
|
|
123
|
-
private logPlain(
|
|
147
|
+
private logPlain(
|
|
148
|
+
level: LogLevel,
|
|
149
|
+
record: LogRecord,
|
|
150
|
+
neutral?: boolean,
|
|
151
|
+
): void {
|
|
124
152
|
const colors: Record<LogLevel, string> = {
|
|
125
153
|
debug: '\x1b[36m',
|
|
126
154
|
info: '\x1b[32m',
|
|
@@ -134,7 +162,15 @@ export class Logger {
|
|
|
134
162
|
const time = new Date(record.timestamp).toLocaleTimeString('en-US', {
|
|
135
163
|
hour12: false,
|
|
136
164
|
})
|
|
137
|
-
|
|
165
|
+
|
|
166
|
+
let levelStr: string
|
|
167
|
+
if (this.labels === 'none') {
|
|
168
|
+
levelStr = ' '
|
|
169
|
+
} else if (this.labels === 'text') {
|
|
170
|
+
levelStr = neutral ? ' ' : level.toUpperCase().padEnd(5)
|
|
171
|
+
} else {
|
|
172
|
+
levelStr = neutral ? ' ' : `${levelIcons[level]} `
|
|
173
|
+
}
|
|
138
174
|
|
|
139
175
|
const ctx = record.context
|
|
140
176
|
const metaStr = Object.keys(ctx).length > 0 ? ` ${JSON.stringify(ctx)}` : ''
|
|
@@ -145,7 +181,9 @@ export class Logger {
|
|
|
145
181
|
let isRoot = true
|
|
146
182
|
while (current) {
|
|
147
183
|
if (current.stack) {
|
|
148
|
-
errStr += isRoot
|
|
184
|
+
errStr += isRoot
|
|
185
|
+
? `\n${current.stack}`
|
|
186
|
+
: `\nCaused by: ${current.stack}`
|
|
149
187
|
} else {
|
|
150
188
|
errStr += isRoot
|
|
151
189
|
? `\n ${current.name}: ${current.message}`
|
|
@@ -174,24 +212,28 @@ export class Logger {
|
|
|
174
212
|
}
|
|
175
213
|
}
|
|
176
214
|
|
|
215
|
+
log(message: string, meta?: LogContext): void {
|
|
216
|
+
this.emit('info', message, meta, undefined, true)
|
|
217
|
+
}
|
|
218
|
+
|
|
177
219
|
debug(message: string, meta?: LogContext): void {
|
|
178
|
-
this.
|
|
220
|
+
this.emit('debug', message, meta)
|
|
179
221
|
}
|
|
180
222
|
|
|
181
223
|
info(message: string, meta?: LogContext): void {
|
|
182
|
-
this.
|
|
224
|
+
this.emit('info', message, meta)
|
|
183
225
|
}
|
|
184
226
|
|
|
185
227
|
warn(message: string, meta?: LogContext): void {
|
|
186
|
-
this.
|
|
228
|
+
this.emit('warn', message, meta)
|
|
187
229
|
}
|
|
188
230
|
|
|
189
231
|
error(message: string, meta?: LogContext, error?: Error): void {
|
|
190
|
-
this.
|
|
232
|
+
this.emit('error', message, meta, error)
|
|
191
233
|
}
|
|
192
234
|
|
|
193
235
|
fatal(message: string, meta?: LogContext, error?: Error): void {
|
|
194
|
-
this.
|
|
236
|
+
this.emit('fatal', message, meta, error)
|
|
195
237
|
}
|
|
196
238
|
}
|
|
197
239
|
|
package/src/intercept.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { GoogleCloudLogging } from './google-cloud-logging'
|
|
2
2
|
import { errorInfo } from './index'
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
Formatter,
|
|
5
|
+
InterceptOptions,
|
|
6
|
+
LogLevel,
|
|
7
|
+
TraceContext,
|
|
8
|
+
} from './types'
|
|
4
9
|
import { flattenError, logLevelValues, stripAnsi, write } from './types'
|
|
5
10
|
|
|
6
11
|
type ConsoleMethods = {
|
|
@@ -75,6 +80,7 @@ function createOverride(
|
|
|
75
80
|
formatter: Formatter,
|
|
76
81
|
minLevel: number,
|
|
77
82
|
filter?: (level: LogLevel, message: string) => boolean,
|
|
83
|
+
traceContext?: () => TraceContext | undefined,
|
|
78
84
|
): (...args: unknown[]) => void {
|
|
79
85
|
const msgKey = `"${formatter.messageKey}"`
|
|
80
86
|
|
|
@@ -105,6 +111,7 @@ function createOverride(
|
|
|
105
111
|
timestamp: new Date().toISOString(),
|
|
106
112
|
context: meta || {},
|
|
107
113
|
error: error ? errorInfo(error) : undefined,
|
|
114
|
+
trace: traceContext?.(),
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
const formatted = formatter.format(record)
|
|
@@ -122,6 +129,7 @@ export function intercept(options?: InterceptOptions): void {
|
|
|
122
129
|
const formatter = options?.formatter ?? GoogleCloudLogging
|
|
123
130
|
const minLevel = logLevelValues[options?.level ?? 'debug']
|
|
124
131
|
const filter = options?.filter
|
|
132
|
+
const traceContext = options?.traceContext
|
|
125
133
|
|
|
126
134
|
const methodMap: [keyof ConsoleMethods, LogLevel][] = [
|
|
127
135
|
['log', 'info'],
|
|
@@ -132,6 +140,12 @@ export function intercept(options?: InterceptOptions): void {
|
|
|
132
140
|
]
|
|
133
141
|
|
|
134
142
|
for (const [method, level] of methodMap) {
|
|
135
|
-
console[method] = createOverride(
|
|
143
|
+
console[method] = createOverride(
|
|
144
|
+
level,
|
|
145
|
+
formatter,
|
|
146
|
+
minLevel,
|
|
147
|
+
filter,
|
|
148
|
+
traceContext,
|
|
149
|
+
)
|
|
136
150
|
}
|
|
137
151
|
}
|
package/src/types.ts
CHANGED
|
@@ -9,12 +9,19 @@ export type ErrorInfo = {
|
|
|
9
9
|
cause?: ErrorInfo
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
export type TraceContext = {
|
|
13
|
+
traceId: string
|
|
14
|
+
spanId: string
|
|
15
|
+
traceFlags?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
12
18
|
export type LogRecord = {
|
|
13
19
|
level: LogLevel
|
|
14
20
|
message: string
|
|
15
21
|
timestamp: string
|
|
16
22
|
context: LogContext
|
|
17
23
|
error?: ErrorInfo
|
|
24
|
+
trace?: TraceContext
|
|
18
25
|
}
|
|
19
26
|
|
|
20
27
|
export type Formatter = {
|
|
@@ -22,10 +29,21 @@ export type Formatter = {
|
|
|
22
29
|
messageKey: string
|
|
23
30
|
}
|
|
24
31
|
|
|
32
|
+
export type LabelStyle = 'icon' | 'none' | 'text'
|
|
33
|
+
|
|
34
|
+
export const defaultLabelStyle: LabelStyle =
|
|
35
|
+
(process.env.LOG_LABELS as LabelStyle) === 'text'
|
|
36
|
+
? 'text'
|
|
37
|
+
: (process.env.LOG_LABELS as LabelStyle) === 'none'
|
|
38
|
+
? 'none'
|
|
39
|
+
: 'icon'
|
|
40
|
+
|
|
25
41
|
export type LoggerOptions = {
|
|
26
42
|
formatter?: Formatter
|
|
27
43
|
json?: boolean
|
|
44
|
+
labels?: LabelStyle
|
|
28
45
|
level?: LogLevel
|
|
46
|
+
traceContext?: () => TraceContext | undefined
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
export type FormatterName = 'google-cloud-logging' | 'victoria-logs'
|
|
@@ -37,6 +55,7 @@ export type InterceptOptions = {
|
|
|
37
55
|
formatter?: Formatter
|
|
38
56
|
filter?: (level: LogLevel, message: string) => boolean
|
|
39
57
|
level?: LogLevel
|
|
58
|
+
traceContext?: () => TraceContext | undefined
|
|
40
59
|
}
|
|
41
60
|
|
|
42
61
|
export const logLevelValues: Record<LogLevel, number> = {
|
package/src/victoria-logs.ts
CHANGED
|
@@ -9,6 +9,13 @@ export const VictoriaLogs: Formatter = {
|
|
|
9
9
|
level: record.level,
|
|
10
10
|
...record.context,
|
|
11
11
|
}
|
|
12
|
+
if (record.trace) {
|
|
13
|
+
entry.trace_id = record.trace.traceId
|
|
14
|
+
entry.span_id = record.trace.spanId
|
|
15
|
+
if (record.trace.traceFlags !== undefined) {
|
|
16
|
+
entry.trace_flags = record.trace.traceFlags
|
|
17
|
+
}
|
|
18
|
+
}
|
|
12
19
|
return entry
|
|
13
20
|
},
|
|
14
21
|
}
|