jsonl-logger 0.2.3 → 0.3.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,8 @@
1
+ [![Monthly Downloads](https://img.shields.io/npm/dm/jsonl-logger.svg)](https://www.npmjs.com/package/jsonl-logger)
2
+ [![NPM](https://img.shields.io/npm/v/jsonl-logger.svg 'NPM package version')](https://www.npmjs.com/package/jsonl-logger)
3
+ [![CI](https://github.com/annexare/jsonl-logger/actions/workflows/ci.yml/badge.svg)](https://github.com/annexare/jsonl-logger/actions/workflows/ci.yml)
4
+
5
+
1
6
  # JSONL Logger
2
7
 
3
8
  Lightweight JSON Lines logger with pluggable formatters (**Google Cloud Logging**, **VictoriaLogs**). Modern **ESM**-only, zero dependencies, Bun-first, works on Node.js and Deno.
@@ -122,6 +127,49 @@ requestLogger.info('Processing request')
122
127
  // All entries include requestId and service
123
128
  ```
124
129
 
130
+ ## Error Handling
131
+
132
+ Errors passed to `error()` / `fatal()` capture the full stack trace and `error.cause` chain:
133
+
134
+ ```typescript
135
+ const inner = new Error('ECONNREFUSED')
136
+ const outer = new Error('fetch failed', { cause: inner })
137
+ logger.error('API call failed', { endpoint: '/users' }, outer)
138
+ ```
139
+
140
+ **Dev mode** (no `LOG_FORMAT`) — colored plain text with full stack:
141
+ ```
142
+ 18:42:05 ERROR API call failed {"endpoint":"/users"}
143
+ Error: fetch failed
144
+ at handler (/app/api/route.ts:42:5)
145
+ Caused by: Error: ECONNREFUSED
146
+ at connect (/app/db.ts:10:3)
147
+ ```
148
+
149
+ **Production** (`LOG_FORMAT` set) — structured JSON with `error.*` and `error.cause.*` fields:
150
+ ```json
151
+ {
152
+ "message": "API call failed",
153
+ "severity": "ERROR",
154
+ "endpoint": "/users",
155
+ "error.name": "Error",
156
+ "error.message": "fetch failed",
157
+ "error.stack": "Error: fetch failed\n at handler ...",
158
+ "error.cause.name": "Error",
159
+ "error.cause.message": "ECONNREFUSED",
160
+ "error.cause.stack": "Error: ECONNREFUSED\n at connect ..."
161
+ }
162
+ ```
163
+
164
+ The `errorInfo()` helper is exported for use in custom formatters:
165
+
166
+ ```typescript
167
+ import { errorInfo } from 'jsonl-logger'
168
+
169
+ const info = errorInfo(caughtError)
170
+ // { name, message, stack, cause?: { name, message, stack, cause?: ... } }
171
+ ```
172
+
125
173
  ## Environment Variables
126
174
 
127
175
  | Variable | Default | Description |
@@ -140,7 +188,7 @@ The logger auto-detects the runtime and uses the fastest available I/O:
140
188
 
141
189
  | Subpath | Export |
142
190
  |---------|--------|
143
- | `jsonl-logger` | `Logger`, `logger`, types |
191
+ | `jsonl-logger` | `Logger`, `logger`, `errorInfo()`, types (`ErrorInfo`, `LogRecord`, etc.) |
144
192
  | `jsonl-logger/google-cloud-logging` | `GoogleCloudLogging` formatter |
145
193
  | `jsonl-logger/victoria-logs` | `VictoriaLogs` formatter |
146
194
  | `jsonl-logger/intercept` | `intercept()`, `originalConsole` |
@@ -1 +1 @@
1
- var t={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},o={messageKey:"message",format(e){let r={message:e.message,timestamp:e.timestamp,severity:t[e.level],...e.context};if(e.error){if(r["error.name"]=e.error.name,r["error.message"]=e.error.message,e.error.stack)r["error.stack"]=e.error.stack}return r}};export{o as GoogleCloudLogging};
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};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import type { LogContext, LoggerOptions } from './types';
2
- export type { Formatter, FormatterName, InterceptOptions, LogContext, LoggerOptions, LogLevel, LogRecord, } from './types';
1
+ import type { ErrorInfo, LogContext, LoggerOptions } from './types';
2
+ export type { ErrorInfo, Formatter, FormatterName, InterceptOptions, LogContext, LoggerOptions, LogLevel, LogRecord, } from './types';
3
3
  export { logLevelValues, stripAnsi } from './types';
4
+ export declare function errorInfo(err: Error): ErrorInfo;
4
5
  export declare class Logger {
5
6
  private ctx;
6
7
  private min;
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
- var C={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},a={messageKey:"message",format(o){let t={message:o.message,timestamp:o.timestamp,severity:C[o.level],...o.context};if(o.error){if(t["error.name"]=o.error.name,t["error.message"]=o.error.message,o.error.stack)t["error.stack"]=o.error.stack}return t}};var f={messageKey:"_msg",format(o){let t={_msg:o.message,_time:o.timestamp,level:o.level,...o.context};if(o.error){if(t["error.name"]=o.error.name,t["error.message"]=o.error.message,o.error.stack)t["error.stack"]=o.error.stack}return t}};var i=process.env.LOG_FORMAT,v=!!i,n={debug:0,info:1,warn:2,error:3,fatal:4},k=/\x1b\[[0-9;]*m/g;function l(o){return o.replace(k,"")}var m=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",p=m==="deno"?new TextEncoder:null;function x(o,t){if(m==="node")(t?process.stderr??process.stdout:process.stdout).write(`${o}
2
- `);else if(m==="deno"&&p){let e=p.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":a,"victoria-logs":f},F=i&&y[i]||a,u=v,O=process.env.LOG_LEVEL||(u?"info":"debug"),d={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};class L{ctx;min;json;fmt;constructor(o,t){this.ctx=o||{},this.json=t?.json??u,this.fmt=t?.formatter??F;let e=t?.level??O;this.min=n[e]??n.info}child(o){let t=new L({...this.ctx,...o},{json:this.json,formatter:this.fmt});return t.min=this.min,t}log(o,t,e,r){if(n[o]<this.min)return;let s={level:o,message:this.json?l(t).trim():t,timestamp:new Date().toISOString(),context:e?{...this.ctx,...e}:this.ctx};if(r)s.error={name:r.name,message:r.message,stack:r.stack};if(this.json)x(JSON.stringify(this.fmt.format(s)),d[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"},r="\x1B[0m",s=e[o],h=new Date(t.timestamp).toLocaleTimeString("en-US",{hour12:!1}),R=o.toUpperCase().padEnd(5),c=t.context,w=Object.keys(c).length>0?` ${JSON.stringify(c)}`:"",b=t.error?` [${t.error.name}: ${t.error.message}]`:"",g=`${s}${h} ${R}\x1B[0m ${t.message}${w}${b}`;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,e){this.log("error",o,t,e)}fatal(o,t,e){this.log("fatal",o,t,e)}}var V=new L;export{l as stripAnsi,V as logger,n as logLevelValues,L as Logger};
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};
package/dist/intercept.js CHANGED
@@ -1,3 +1,7 @@
1
- var Q={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},W={messageKey:"message",format(R){let k={message:R.message,timestamp:R.timestamp,severity:Q[R.level],...R.context};if(R.error){if(k["error.name"]=R.error.name,k["error.message"]=R.error.message,R.error.stack)k["error.stack"]=R.error.stack}return k}};var X=process.env.LOG_FORMAT,L=!!X,T={debug:0,info:1,warn:2,error:3,fatal:4},Y=/\x1b\[[0-9;]*m/g;function q(R){return R.replace(Y,"")}var B=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",j=B==="deno"?new TextEncoder:null;function U(R,k){if(B==="node")(k?process.stderr??process.stdout:process.stdout).write(`${R}
2
- `);else if(B==="deno"&&j){let I=j.encode(`${R}
3
- `);if(k)Deno.stderr.writeSync(I);else Deno.stdout.writeSync(I)}else if(k)console.error(R);else console.log(R)}var S={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 Z(...R){let k="";for(let I=0;I<R.length;I++){if(I>0)k+=" ";let C=R[I];if(typeof C==="string")k+=q(C);else if(C instanceof Error)k+=C.message;else try{k+=JSON.stringify(C)}catch{k+=String(C)}}return k.trim()}function _(...R){let k;for(let I of R)if(typeof I==="object"&&I!==null&&!(I instanceof Error)&&!Array.isArray(I)){if(!k)k={};Object.assign(k,I)}return k}function $(...R){for(let k of R)if(k instanceof Error)return k}var w={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function y(R,k,I,C){let A=`"${k.messageKey}"`;return(...F)=>{if(F.length===1&&typeof F[0]==="string"&&F[0].charCodeAt(0)===123&&F[0].includes(A)){U(F[0],w[R]);return}if(T[R]<I)return;let G=Z(...F);if(C&&!C(R,G))return;let H=_(...F),N=$(...F),O={level:R,message:G,timestamp:new Date().toISOString(),context:H||{},error:N?{name:N.name,message:N.message,stack:N.stack}:void 0};U(JSON.stringify(k.format(O)),w[R])}}var z="__jsonlLoggerIntercepted";function V(R){if(globalThis[z])return;globalThis[z]=!0;let k=R?.formatter??W,I=T[R?.level??"debug"],C=R?.filter,A=[["log","info"],["info","info"],["warn","warn"],["error","error"],["debug","debug"]];for(let[F,G]of A)console[F]=y(G,k,I,C)}export{S as originalConsole,V as intercept};
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};
package/dist/preload.js CHANGED
@@ -1,3 +1,7 @@
1
- var V={debug:"DEBUG",info:"INFO",warn:"WARNING",error:"ERROR",fatal:"CRITICAL"},F={messageKey:"message",format(m){let n={message:m.message,timestamp:m.timestamp,severity:V[m.level],...m.context};if(m.error){if(n["error.name"]=m.error.name,n["error.message"]=m.error.message,m.error.stack)n["error.stack"]=m.error.stack}return n}};var _={messageKey:"_msg",format(m){let n={_msg:m.message,_time:m.timestamp,level:m.level,...m.context};if(m.error){if(n["error.name"]=m.error.name,n["error.message"]=m.error.message,m.error.stack)n["error.stack"]=m.error.stack}return n}};var p=process.env.LOG_FORMAT,Q=!!p,I={debug:0,info:1,warn:2,error:3,fatal:4},W=/\x1b\[[0-9;]*m/g;function C(m){return m.replace(W,"")}var G=typeof process<"u"&&process.stdout&&typeof process.stdout.write==="function"?"node":typeof Deno<"u"&&Deno.stdout?"deno":"browser",w=G==="deno"?new TextEncoder:null;function N(m,n){if(G==="node")(n?process.stderr??process.stdout:process.stdout).write(`${m}
2
- `);else if(G==="deno"&&w){let R=w.encode(`${m}
3
- `);if(n)Deno.stderr.writeSync(R);else Deno.stdout.writeSync(R)}else if(n)console.error(m);else console.log(m)}var $={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 c(...m){let n="";for(let R=0;R<m.length;R++){if(R>0)n+=" ";let o=m[R];if(typeof o==="string")n+=C(o);else if(o instanceof Error)n+=o.message;else try{n+=JSON.stringify(o)}catch{n+=String(o)}}return n.trim()}function j(...m){let n;for(let R of m)if(typeof R==="object"&&R!==null&&!(R instanceof Error)&&!Array.isArray(R)){if(!n)n={};Object.assign(n,R)}return n}function q(...m){for(let n of m)if(n instanceof Error)return n}var A={debug:!1,info:!1,warn:!1,error:!0,fatal:!0};function t(m,n,R,o){let y=`"${n.messageKey}"`;return(...k)=>{if(k.length===1&&typeof k[0]==="string"&&k[0].charCodeAt(0)===123&&k[0].includes(y)){N(k[0],A[m]);return}if(I[m]<R)return;let f=c(...k);if(o&&!o(m,f))return;let T=j(...k),L=q(...k),U={level:m,message:f,timestamp:new Date().toISOString(),context:T||{},error:L?{name:L.name,message:L.message,stack:L.stack}:void 0};N(JSON.stringify(n.format(U)),A[m])}}var B="__jsonlLoggerIntercepted";function O(m){if(globalThis[B])return;globalThis[B]=!0;let n=m?.formatter??F,R=I[m?.level??"debug"],o=m?.filter,y=[["log","info"],["info","info"],["warn","warn"],["error","error"],["debug","debug"]];for(let[k,f]of y)console[k]=t(f,n,R,o)}if(p){let n={"google-cloud-logging":F,"victoria-logs":_}[p]??F,R=process.env.LOG_LEVEL||"info";O({formatter:n,level:R})}
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})}
package/dist/types.d.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal';
2
2
  export type LogContext = Record<string, unknown>;
3
+ export type ErrorInfo = {
4
+ name: string;
5
+ message: string;
6
+ stack?: string;
7
+ cause?: ErrorInfo;
8
+ };
3
9
  export type LogRecord = {
4
10
  level: LogLevel;
5
11
  message: string;
6
12
  timestamp: string;
7
13
  context: LogContext;
8
- error?: {
9
- name: string;
10
- message: string;
11
- stack?: string;
12
- };
14
+ error?: ErrorInfo;
13
15
  };
14
16
  export type Formatter = {
15
17
  format: (record: LogRecord) => Record<string, unknown>;
@@ -30,4 +32,5 @@ export type InterceptOptions = {
30
32
  };
31
33
  export declare const logLevelValues: Record<LogLevel, number>;
32
34
  export declare function stripAnsi(str: string): string;
35
+ export declare function flattenError(entry: Record<string, unknown>, error: ErrorInfo, prefix?: string): void;
33
36
  export declare function write(data: string, isError: boolean): void;
@@ -1 +1 @@
1
- var t={messageKey:"_msg",format(e){let r={_msg:e.message,_time:e.timestamp,level:e.level,...e.context};if(e.error){if(r["error.name"]=e.error.name,r["error.message"]=e.error.message,e.error.stack)r["error.stack"]=e.error.stack}return r}};export{t as VictoriaLogs};
1
+ var o={messageKey:"_msg",format(t){return{_msg:t.message,_time:t.timestamp,level:t.level,...t.context}}};export{o as VictoriaLogs};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsonl-logger",
3
- "version": "0.2.3",
3
+ "version": "0.3.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",
@@ -17,11 +17,6 @@ export const GoogleCloudLogging: Formatter = {
17
17
  severity: severityMap[record.level],
18
18
  ...record.context,
19
19
  }
20
- if (record.error) {
21
- entry['error.name'] = record.error.name
22
- entry['error.message'] = record.error.message
23
- if (record.error.stack) entry['error.stack'] = record.error.stack
24
- }
25
20
  return entry
26
21
  },
27
22
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { GoogleCloudLogging } from './google-cloud-logging'
2
2
  import type {
3
+ ErrorInfo,
3
4
  Formatter,
4
5
  FormatterName,
5
6
  LogContext,
@@ -9,6 +10,7 @@ import type {
9
10
  } from './types'
10
11
  import {
11
12
  defaultFormat,
13
+ flattenError,
12
14
  isJsonMode,
13
15
  logLevelValues,
14
16
  stripAnsi,
@@ -17,6 +19,7 @@ import {
17
19
  import { VictoriaLogs } from './victoria-logs'
18
20
 
19
21
  export type {
22
+ ErrorInfo,
20
23
  Formatter,
21
24
  FormatterName,
22
25
  InterceptOptions,
@@ -47,6 +50,22 @@ const isErrorLevel: Record<LogLevel, boolean> = {
47
50
  fatal: true,
48
51
  }
49
52
 
53
+ function extractErrorInfo(err: Error, visited: WeakSet<Error>): ErrorInfo {
54
+ visited.add(err)
55
+ return {
56
+ name: err.name,
57
+ message: err.message,
58
+ stack: err.stack,
59
+ ...(err.cause instanceof Error && !visited.has(err.cause)
60
+ ? { cause: extractErrorInfo(err.cause, visited) }
61
+ : {}),
62
+ }
63
+ }
64
+
65
+ export function errorInfo(err: Error): ErrorInfo {
66
+ return extractErrorInfo(err, new WeakSet())
67
+ }
68
+
50
69
  export class Logger {
51
70
  private ctx: LogContext
52
71
  private min: number
@@ -89,11 +108,13 @@ export class Logger {
89
108
  }
90
109
 
91
110
  if (err) {
92
- record.error = { name: err.name, message: err.message, stack: err.stack }
111
+ record.error = errorInfo(err)
93
112
  }
94
113
 
95
114
  if (this.json) {
96
- write(JSON.stringify(this.fmt.format(record)), isErrorLevel[level])
115
+ const formatted = this.fmt.format(record)
116
+ if (record.error) flattenError(formatted, record.error)
117
+ write(JSON.stringify(formatted), isErrorLevel[level])
97
118
  } else {
98
119
  this.logPlain(level, record)
99
120
  }
@@ -118,9 +139,22 @@ export class Logger {
118
139
  const ctx = record.context
119
140
  const metaStr = Object.keys(ctx).length > 0 ? ` ${JSON.stringify(ctx)}` : ''
120
141
 
121
- const errStr = record.error
122
- ? ` [${record.error.name}: ${record.error.message}]`
123
- : ''
142
+ let errStr = ''
143
+ if (record.error) {
144
+ let current: ErrorInfo | undefined = record.error
145
+ let isRoot = true
146
+ while (current) {
147
+ if (current.stack) {
148
+ errStr += isRoot ? `\n${current.stack}` : `\nCaused by: ${current.stack}`
149
+ } else {
150
+ errStr += isRoot
151
+ ? `\n ${current.name}: ${current.message}`
152
+ : `\nCaused by: ${current.name}: ${current.message}`
153
+ }
154
+ current = current.cause
155
+ isRoot = false
156
+ }
157
+ }
124
158
 
125
159
  const output = `${color}${time} ${levelStr}${reset} ${record.message}${metaStr}${errStr}`
126
160
 
package/src/intercept.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { GoogleCloudLogging } from './google-cloud-logging'
2
+ import { errorInfo } from './index'
2
3
  import type { Formatter, InterceptOptions, LogLevel } from './types'
3
- import { logLevelValues, stripAnsi, write } from './types'
4
+ import { flattenError, logLevelValues, stripAnsi, write } from './types'
4
5
 
5
6
  type ConsoleMethods = {
6
7
  log: typeof console.log
@@ -103,12 +104,12 @@ function createOverride(
103
104
  message,
104
105
  timestamp: new Date().toISOString(),
105
106
  context: meta || {},
106
- error: error
107
- ? { name: error.name, message: error.message, stack: error.stack }
108
- : undefined,
107
+ error: error ? errorInfo(error) : undefined,
109
108
  }
110
109
 
111
- write(JSON.stringify(formatter.format(record)), isErrorLevel[level])
110
+ const formatted = formatter.format(record)
111
+ if (record.error) flattenError(formatted, record.error)
112
+ write(JSON.stringify(formatted), isErrorLevel[level])
112
113
  }
113
114
  }
114
115
 
package/src/types.ts CHANGED
@@ -2,12 +2,19 @@ export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'fatal'
2
2
 
3
3
  export type LogContext = Record<string, unknown>
4
4
 
5
+ export type ErrorInfo = {
6
+ name: string
7
+ message: string
8
+ stack?: string
9
+ cause?: ErrorInfo
10
+ }
11
+
5
12
  export type LogRecord = {
6
13
  level: LogLevel
7
14
  message: string
8
15
  timestamp: string
9
16
  context: LogContext
10
- error?: { name: string; message: string; stack?: string }
17
+ error?: ErrorInfo
11
18
  }
12
19
 
13
20
  export type Formatter = {
@@ -47,6 +54,17 @@ export function stripAnsi(str: string): string {
47
54
  return str.replace(ansiPattern, '')
48
55
  }
49
56
 
57
+ export function flattenError(
58
+ entry: Record<string, unknown>,
59
+ error: ErrorInfo,
60
+ prefix = 'error',
61
+ ): void {
62
+ entry[`${prefix}.name`] = error.name
63
+ entry[`${prefix}.message`] = error.message
64
+ if (error.stack) entry[`${prefix}.stack`] = error.stack
65
+ if (error.cause) flattenError(entry, error.cause, `${prefix}.cause`)
66
+ }
67
+
50
68
  /**
51
69
  * Detect runtime once, resolve streams at call time.
52
70
  * Stream lookup is deferred so tests/runtime can replace process.stdout.
@@ -9,11 +9,6 @@ export const VictoriaLogs: Formatter = {
9
9
  level: record.level,
10
10
  ...record.context,
11
11
  }
12
- if (record.error) {
13
- entry['error.name'] = record.error.name
14
- entry['error.message'] = record.error.message
15
- if (record.error.stack) entry['error.stack'] = record.error.stack
16
- }
17
12
  return entry
18
13
  },
19
14
  }