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 +49 -1
- package/dist/google-cloud-logging.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +7 -3
- package/dist/intercept.js +7 -3
- package/dist/preload.js +7 -3
- package/dist/types.d.ts +8 -5
- package/dist/victoria-logs.js +1 -1
- package/package.json +1 -1
- package/src/google-cloud-logging.ts +0 -5
- package/src/index.ts +39 -5
- package/src/intercept.ts +6 -5
- package/src/types.ts +19 -1
- package/src/victoria-logs.ts +0 -5
package/README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/jsonl-logger)
|
|
2
|
+
[](https://www.npmjs.com/package/jsonl-logger)
|
|
3
|
+
[](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"},
|
|
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
|
|
2
|
-
`);else if(
|
|
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":
|
|
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
|
|
2
|
-
`);else if(
|
|
3
|
-
`);if(
|
|
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
|
|
2
|
-
`);else if(
|
|
3
|
-
`);if(
|
|
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;
|
package/dist/victoria-logs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
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
|
@@ -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 =
|
|
111
|
+
record.error = errorInfo(err)
|
|
93
112
|
}
|
|
94
113
|
|
|
95
114
|
if (this.json) {
|
|
96
|
-
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
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?:
|
|
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.
|
package/src/victoria-logs.ts
CHANGED
|
@@ -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
|
}
|