jsonl-logger 0.2.2 → 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 +86 -16
- 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,6 +1,13 @@
|
|
|
1
|
-
|
|
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)
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
|
|
6
|
+
# JSONL Logger
|
|
7
|
+
|
|
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.
|
|
9
|
+
|
|
10
|
+
Next.js (and other hardcoded plain text logs) become JSON-only logging for systems where it is required.
|
|
4
11
|
|
|
5
12
|
## Install
|
|
6
13
|
|
|
@@ -29,20 +36,14 @@ Set `LOG_FORMAT` to enable JSON output with a specific formatter:
|
|
|
29
36
|
|
|
30
37
|
```bash
|
|
31
38
|
LOG_FORMAT=google-cloud-logging bun run server.ts
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
// Output: {"message":"...","timestamp":"...","severity":"INFO",...}
|
|
39
|
+
# Output: {"message":"...","timestamp":"...","severity":"INFO",...}
|
|
36
40
|
```
|
|
37
41
|
|
|
38
42
|
### VictoriaLogs
|
|
39
43
|
|
|
40
44
|
```bash
|
|
41
45
|
LOG_FORMAT=victoria-logs bun run server.ts
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
// Output: {"_msg":"...","_time":"...","level":"info",...}
|
|
46
|
+
# Output: {"_msg":"...","_time":"...","level":"info",...}
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
### Custom Formatter
|
|
@@ -82,15 +83,41 @@ console.log('plain text') // → structured JSON
|
|
|
82
83
|
originalConsole.log('bypass interception')
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
##
|
|
86
|
+
## Next.js Integration
|
|
86
87
|
|
|
87
|
-
|
|
88
|
+
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`.
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
### Instrumentation
|
|
91
|
+
|
|
92
|
+
Next.js auto-detects `instrumentation.ts` at the project root. Use it to load the preload module on the server:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
export async function register() {
|
|
96
|
+
if (process.env.NEXT_RUNTIME === 'nodejs' || typeof Bun !== 'undefined') {
|
|
97
|
+
await import('jsonl-logger/preload')
|
|
98
|
+
}
|
|
99
|
+
}
|
|
91
100
|
```
|
|
92
101
|
|
|
93
|
-
|
|
102
|
+
### Dockerfile (Standalone with Bun)
|
|
103
|
+
|
|
104
|
+
Next.js standalone output doesn't include all `node_modules`. Copy `jsonl-logger` explicitly from the build stage:
|
|
105
|
+
|
|
106
|
+
```dockerfile
|
|
107
|
+
COPY --from=build /app/node_modules/jsonl-logger ./node_modules/jsonl-logger
|
|
108
|
+
|
|
109
|
+
ENV LOG_FORMAT=victoria-logs
|
|
110
|
+
|
|
111
|
+
CMD ["bun", "--preload", "jsonl-logger/preload", "server.js"]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Node.js
|
|
115
|
+
|
|
116
|
+
For non-Bun deployments, use `--import` to preload:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
LOG_FORMAT=google-cloud-logging node --import jsonl-logger/preload server.js
|
|
120
|
+
```
|
|
94
121
|
|
|
95
122
|
## Child Loggers
|
|
96
123
|
|
|
@@ -100,6 +127,49 @@ requestLogger.info('Processing request')
|
|
|
100
127
|
// All entries include requestId and service
|
|
101
128
|
```
|
|
102
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
|
+
|
|
103
173
|
## Environment Variables
|
|
104
174
|
|
|
105
175
|
| Variable | Default | Description |
|
|
@@ -118,7 +188,7 @@ The logger auto-detects the runtime and uses the fastest available I/O:
|
|
|
118
188
|
|
|
119
189
|
| Subpath | Export |
|
|
120
190
|
|---------|--------|
|
|
121
|
-
| `jsonl-logger` | `Logger`, `logger`, types |
|
|
191
|
+
| `jsonl-logger` | `Logger`, `logger`, `errorInfo()`, types (`ErrorInfo`, `LogRecord`, etc.) |
|
|
122
192
|
| `jsonl-logger/google-cloud-logging` | `GoogleCloudLogging` formatter |
|
|
123
193
|
| `jsonl-logger/victoria-logs` | `VictoriaLogs` formatter |
|
|
124
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
|
}
|