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 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 t={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},r={messageKey:"message",format(e){return{message:e.message,timestamp:e.timestamp,severity:t[e.level],...e.context}}};export{r as GoogleCloudLogging};
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 log;
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 F={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},L={messageKey:"message",format(o){return{message:o.message,timestamp:o.timestamp,severity:F[o.level],...o.context}}};var d={messageKey:"_msg",format(o){return{_msg:o.message,_time:o.timestamp,level:o.level,...o.context}}};var i=process.env.LOG_FORMAT,R=!!i,r={debug:0,info:1,warn:2,error:3,fatal:4},I=/\x1b\[[0-9;]*m/g;function u(o){return o.replace(I,"")}function v(o,t,e="error"){if(o[`${e}.name`]=t.name,o[`${e}.message`]=t.message,t.stack)o[`${e}.stack`]=t.stack;if(t.cause)v(o,t.cause,`${e}.cause`)}var f=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",x=f==="deno"?new TextEncoder:null;function w(o,t){if(f==="node")(t?process.stderr??process.stdout:process.stdout).write(`${o}
2
- `);else if(f==="deno"&&x){let e=x.encode(`${o}
3
- `);if(t)Deno.stderr.writeSync(e);else Deno.stdout.writeSync(e)}else if(t)console.error(o);else console.log(o)}var y={"google-cloud-logging":L,"victoria-logs":d},E=i&&y[i]||L,b=R,O=process.env.LOG_LEVEL||(b?"info":"debug"),S={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function C(o,t){return t.add(o),{name:o.name,message:o.message,stack:o.stack,...o.cause instanceof Error&&!t.has(o.cause)?{cause:C(o.cause,t)}:{}}}function j(o){return C(o,new WeakSet)}class p{ctx;min;json;fmt;constructor(o,t){this.ctx=o||{},this.json=t?.json??b,this.fmt=t?.formatter??E;let e=t?.level??O;this.min=r[e]??r.info}child(o){let t=new p({...this.ctx,...o},{json:this.json,formatter:this.fmt});return t.min=this.min,t}log(o,t,e,m){if(r[o]<this.min)return;let s={level:o,message:this.json?u(t).trim():t,timestamp:new Date().toISOString(),context:e?{...this.ctx,...e}:this.ctx};if(m)s.error=j(m);if(this.json){let g=this.fmt.format(s);if(s.error)v(g,s.error);w(JSON.stringify(g),S[o])}else this.logPlain(o,s)}logPlain(o,t){let e={debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m"},m="\x1B[0m",s=e[o],g=new Date(t.timestamp).toLocaleTimeString("en-US",{hour12:!1}),k=o.toUpperCase().padEnd(5),h=t.context,$=Object.keys(h).length>0?` ${JSON.stringify(h)}`:"",c="";if(t.error){let n=t.error,l=!0;while(n){if(n.stack)c+=l?`
4
- ${n.stack}`:`
5
- Caused by: ${n.stack}`;else c+=l?`
6
- ${n.name}: ${n.message}`:`
7
- Caused by: ${n.name}: ${n.message}`;n=n.cause,l=!1}}let a=`${s}${g} ${k}\x1B[0m ${t.message}${$}${c}`;switch(o){case"debug":console.debug(a);break;case"warn":console.warn(a);break;case"error":case"fatal":console.error(a);break;default:console.log(a)}}debug(o,t){this.log("debug",o,t)}info(o,t){this.log("info",o,t)}warn(o,t){this.log("warn",o,t)}error(o,t,e){this.log("error",o,t,e)}fatal(o,t,e){this.log("fatal",o,t,e)}}var U=new p;export{u as stripAnsi,U as logger,r as logLevelValues,j as errorInfo,p as Logger};
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 d={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},a={messageKey:"message",format(o){return{message:o.message,timestamp:o.timestamp,severity:d[o.level],...o.context}}};var l={messageKey:"_msg",format(o){return{_msg:o.message,_time:o.timestamp,level:o.level,...o.context}}};var b=process.env.LOG_FORMAT,I=!!b,m={debug:0,info:1,warn:2,error:3,fatal:4},E=/\x1b\[[0-9;]*m/g;function u(o){return o.replace(E,"")}function h(o,t,n="error"){if(o[`${n}.name`]=t.name,o[`${n}.message`]=t.message,t.stack)o[`${n}.stack`]=t.stack;if(t.cause)h(o,t.cause,`${n}.cause`)}var w=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",F=w==="deno"?new TextEncoder:null;function R(o,t){if(w==="node")(t?process.stderr??process.stdout:process.stdout).write(`${o}
2
- `);else if(w==="deno"&&F){let n=F.encode(`${o}
3
- `);if(t)Deno.stderr.writeSync(n);else Deno.stdout.writeSync(n)}else if(t)console.error(o);else console.log(o)}var N={"google-cloud-logging":a,"victoria-logs":l},y=b&&N[b]||a,v=I,G=process.env.LOG_LEVEL||(v?"info":"debug"),_={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function O(o,t){return t.add(o),{name:o.name,message:o.message,stack:o.stack,...o.cause instanceof Error&&!t.has(o.cause)?{cause:O(o.cause,t)}:{}}}function k(o){return O(o,new WeakSet)}class ${ctx;min;json;fmt;constructor(o,t){this.ctx=o||{},this.json=t?.json??v,this.fmt=t?.formatter??y;let n=t?.level??G;this.min=m[n]??m.info}child(o){let t=new $({...this.ctx,...o},{json:this.json,formatter:this.fmt});return t.min=this.min,t}log(o,t,n,s){if(m[o]<this.min)return;let c={level:o,message:this.json?u(t).trim():t,timestamp:new Date().toISOString(),context:n?{...this.ctx,...n}:this.ctx};if(s)c.error=k(s);if(this.json){let e=this.fmt.format(c);if(c.error)h(e,c.error);R(JSON.stringify(e),_[o])}else this.logPlain(o,c)}logPlain(o,t){let n={debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m"},s="\x1B[0m",c=n[o],e=new Date(t.timestamp).toLocaleTimeString("en-US",{hour12:!1}),f=o.toUpperCase().padEnd(5),x=t.context,p=Object.keys(x).length>0?` ${JSON.stringify(x)}`:"",L="";if(t.error){let i=t.error,C=!0;while(i){if(i.stack)L+=C?`
4
- ${i.stack}`:`
5
- Caused by: ${i.stack}`;else L+=C?`
6
- ${i.name}: ${i.message}`:`
7
- Caused by: ${i.name}: ${i.message}`;i=i.cause,C=!1}}let g=`${c}${e} ${f}\x1B[0m ${t.message}${p}${L}`;switch(o){case"debug":console.debug(g);break;case"warn":console.warn(g);break;case"error":case"fatal":console.error(g);break;default:console.log(g)}}debug(o,t){this.log("debug",o,t)}info(o,t){this.log("info",o,t)}warn(o,t){this.log("warn",o,t)}error(o,t,n){this.log("error",o,t,n)}fatal(o,t,n){this.log("fatal",o,t,n)}}var P=new $;var K={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 A(...o){let t="";for(let n=0;n<o.length;n++){if(n>0)t+=" ";let s=o[n];if(typeof s==="string")t+=u(s);else if(s instanceof Error)t+=s.message;else try{t+=JSON.stringify(s)}catch{t+=String(s)}}return t.trim()}function J(...o){let t;for(let n of o)if(typeof n==="object"&&n!==null&&!(n instanceof Error)&&!Array.isArray(n)){if(!t)t={};Object.assign(t,n)}return t}function U(...o){for(let t of o)if(t instanceof Error)return t}var S={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function V(o,t,n,s){let c=`"${t.messageKey}"`;return(...e)=>{if(e.length===1&&typeof e[0]==="string"&&e[0].charCodeAt(0)===123&&e[0].includes(c)){R(e[0],S[o]);return}if(m[o]<n)return;let f=A(...e);if(s&&!s(o,f))return;let x=J(...e),p=U(...e),L={level:o,message:f,timestamp:new Date().toISOString(),context:x||{},error:p?k(p):void 0},g=t.format(L);if(L.error)h(g,L.error);R(JSON.stringify(g),S[o])}}var j="__jsonlLoggerIntercepted";function M(o){if(globalThis[j])return;globalThis[j]=!0;let t=o?.formatter??a,n=m[o?.level??"debug"],s=o?.filter,c=[["log","info"],["info","info"],["warn","warn"],["error","error"],["debug","debug"]];for(let[e,f]of c)console[e]=V(f,t,n,s)}export{K as originalConsole,M as intercept};
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"},c={messageKey:"message",format(o){return{message:o.message,timestamp:o.timestamp,severity:N[o.level],...o.context}}};var x={messageKey:"_msg",format(o){return{_msg:o.message,_time:o.timestamp,level:o.level,...o.context}}};var a=process.env.LOG_FORMAT,v=!!a,L={debug:0,info:1,warn:2,error:3,fatal:4},S=/\x1b\[[0-9;]*m/g;function u(o){return o.replace(S,"")}function p(o,t,n="error"){if(o[`${n}.name`]=t.name,o[`${n}.message`]=t.message,t.stack)o[`${n}.stack`]=t.stack;if(t.cause)p(o,t.cause,`${n}.cause`)}var C=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",k=C==="deno"?new TextEncoder:null;function l(o,t){if(C==="node")(t?process.stderr??process.stdout:process.stdout).write(`${o}
2
- `);else if(C==="deno"&&k){let n=k.encode(`${o}
3
- `);if(t)Deno.stderr.writeSync(n);else Deno.stdout.writeSync(n)}else if(t)console.error(o);else console.log(o)}var j={"google-cloud-logging":c,"victoria-logs":x},y=a&&j[a]||c,$=v,G=process.env.LOG_LEVEL||($?"info":"debug"),V={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function d(o,t){return t.add(o),{name:o.name,message:o.message,stack:o.stack,...o.cause instanceof Error&&!t.has(o.cause)?{cause:d(o.cause,t)}:{}}}function F(o){return d(o,new WeakSet)}class w{ctx;min;json;fmt;constructor(o,t){this.ctx=o||{},this.json=t?.json??$,this.fmt=t?.formatter??y;let n=t?.level??G;this.min=L[n]??L.info}child(o){let t=new w({...this.ctx,...o},{json:this.json,formatter:this.fmt});return t.min=this.min,t}log(o,t,n,e){if(L[o]<this.min)return;let i={level:o,message:this.json?u(t).trim():t,timestamp:new Date().toISOString(),context:n?{...this.ctx,...n}:this.ctx};if(e)i.error=F(e);if(this.json){let s=this.fmt.format(i);if(i.error)p(s,i.error);l(JSON.stringify(s),V[o])}else this.logPlain(o,i)}logPlain(o,t){let n={debug:"\x1B[36m",info:"\x1B[32m",warn:"\x1B[33m",error:"\x1B[31m",fatal:"\x1B[35m"},e="\x1B[0m",i=n[o],s=new Date(t.timestamp).toLocaleTimeString("en-US",{hour12:!1}),r=o.toUpperCase().padEnd(5),R=t.context,h=Object.keys(R).length>0?` ${JSON.stringify(R)}`:"",g="";if(t.error){let m=t.error,b=!0;while(m){if(m.stack)g+=b?`
4
- ${m.stack}`:`
5
- Caused by: ${m.stack}`;else g+=b?`
6
- ${m.name}: ${m.message}`:`
7
- Caused by: ${m.name}: ${m.message}`;m=m.cause,b=!1}}let f=`${i}${s} ${r}\x1B[0m ${t.message}${h}${g}`;switch(o){case"debug":console.debug(f);break;case"warn":console.warn(f);break;case"error":case"fatal":console.error(f);break;default:console.log(f)}}debug(o,t){this.log("debug",o,t)}info(o,t){this.log("info",o,t)}warn(o,t){this.log("warn",o,t)}error(o,t,n){this.log("error",o,t,n)}fatal(o,t,n){this.log("fatal",o,t,n)}}var P=new w;var K={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 _(...o){let t="";for(let n=0;n<o.length;n++){if(n>0)t+=" ";let e=o[n];if(typeof e==="string")t+=u(e);else if(e instanceof Error)t+=e.message;else try{t+=JSON.stringify(e)}catch{t+=String(e)}}return t.trim()}function A(...o){let t;for(let n of o)if(typeof n==="object"&&n!==null&&!(n instanceof Error)&&!Array.isArray(n)){if(!t)t={};Object.assign(t,n)}return t}function J(...o){for(let t of o)if(t instanceof Error)return t}var I={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function U(o,t,n,e){let i=`"${t.messageKey}"`;return(...s)=>{if(s.length===1&&typeof s[0]==="string"&&s[0].charCodeAt(0)===123&&s[0].includes(i)){l(s[0],I[o]);return}if(L[o]<n)return;let r=_(...s);if(e&&!e(o,r))return;let R=A(...s),h=J(...s),g={level:o,message:r,timestamp:new Date().toISOString(),context:R||{},error:h?F(h):void 0},f=t.format(g);if(g.error)p(f,g.error);l(JSON.stringify(f),I[o])}}var O="__jsonlLoggerIntercepted";function E(o){if(globalThis[O])return;globalThis[O]=!0;let t=o?.formatter??c,n=L[o?.level??"debug"],e=o?.filter,i=[["log","info"],["info","info"],["warn","warn"],["error","error"],["debug","debug"]];for(let[s,r]of i)console[s]=U(r,t,n,e)}if(a){let t={"google-cloud-logging":c,"victoria-logs":x}[a]??c,n=process.env.LOG_LEVEL||"info";E({formatter:t,level:n})}
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;
@@ -1 +1 @@
1
- var o={messageKey:"_msg",format(t){return{_msg:t.message,_time:t.timestamp,level:t.level,...t.context}}};export{o as VictoriaLogs};
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.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.2",
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 log(
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(level: LogLevel, record: LogRecord): void {
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
- const levelStr = level.toUpperCase().padEnd(5)
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 ? `\n${current.stack}` : `\nCaused by: ${current.stack}`
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.log('debug', message, meta)
220
+ this.emit('debug', message, meta)
179
221
  }
180
222
 
181
223
  info(message: string, meta?: LogContext): void {
182
- this.log('info', message, meta)
224
+ this.emit('info', message, meta)
183
225
  }
184
226
 
185
227
  warn(message: string, meta?: LogContext): void {
186
- this.log('warn', message, meta)
228
+ this.emit('warn', message, meta)
187
229
  }
188
230
 
189
231
  error(message: string, meta?: LogContext, error?: Error): void {
190
- this.log('error', message, meta, error)
232
+ this.emit('error', message, meta, error)
191
233
  }
192
234
 
193
235
  fatal(message: string, meta?: LogContext, error?: Error): void {
194
- this.log('fatal', message, meta, error)
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 { Formatter, InterceptOptions, LogLevel } from './types'
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(level, formatter, minLevel, filter)
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> = {
@@ -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
  }