@stacksjs/logging 0.74.45 → 0.74.47

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/dist/index.d.ts CHANGED
@@ -102,68 +102,7 @@ export declare function logger(): Promise<Logger>;
102
102
  // Helper function to format message for logging, including request context.
103
103
  // Exported for direct unit testing of arg handling (stacksjs/stacks#2047).
104
104
  export declare function formatMessage(...args: unknown[]): string;
105
- // Export convenience functions
106
- export declare function dump(...args: any[]): Promise<void>;
107
- export declare function dd(...args: any[]): Promise<never>;
108
- export declare function echo(...args: any[]): Promise<void>;
109
- /**
110
- * Single error→log chokepoint (stacksjs/stacks#1933) — Laravel's
111
- * `report()`. Every automatic error-logging path (router action catch,
112
- * request catch, process-level handlers) funnels through here so the
113
- * policy lives in one place:
114
- *
115
- * - **4xx** (client errors — a thrown `HttpError(404)` / `422`) are
116
- * NOT reported at error level; they're expected control flow, not
117
- * server faults. Logged at debug so they stay traceable without
118
- * spamming the error stream.
119
- * - **5xx** and any non-HTTP throw are always reported at `error`
120
- * with the full normalized stack + cause chain + request context.
121
- *
122
- * Fire-and-forget by design (callers are on a response / exit path);
123
- * the write is queued through the shared logger so a flush-on-exit
124
- * (stacksjs/stacks#1934) drains it.
125
- */
126
- export declare function report(error: unknown, options?: ReportOptions): void;
127
- declare function emit(level: 'debug' | 'info' | 'warn' | 'error', event: string, fields: StructuredFields): void;
128
105
  export declare const log: Log;
129
- /**
130
- * @defaultValue
131
- * ```ts
132
- * {
133
- * request: (method: string, path: string, status: number, durationMs: number, fields?: StructuredFields) => void,
134
- * query: (sql: string, durationMs: number, fields?: StructuredFields) => void,
135
- * slowQuery: (sql: string, durationMs: number, fields?: StructuredFields) => void,
136
- * job: (name: string, phase: 'started' | 'succeeded' | 'failed' | 'released', fields?: StructuredFields) => void,
137
- * cache: (op: 'hit' | 'miss' | 'set' | 'del', key: string, fields?: StructuredFields) => void
138
- * }
139
- * ```
140
- */
141
- export declare const struct: {
142
- /**
143
- * HTTP request completed. `status` is the response status code,
144
- * `durationMs` the wall time from request start to response sent.
145
- */
146
- request: (method: string, path: string, status: number, durationMs: number, fields?: StructuredFields) => void;
147
- /**
148
- * Database query completed. `durationMs` is the wall time at the
149
- * driver boundary.
150
- */
151
- query: (sql: string, durationMs: number, fields?: StructuredFields) => void;
152
- /**
153
- * A slow query (over the slow-threshold) — emits at warn so it
154
- * surfaces in the default log filter.
155
- */
156
- slowQuery: (sql: string, durationMs: number, fields?: StructuredFields) => void;
157
- /**
158
- * Queue job lifecycle event. `phase` is `'started' | 'succeeded' |
159
- * 'failed' | 'released'`.
160
- */
161
- job: (name: string, phase: 'started' | 'succeeded' | 'failed' | 'released', fields?: StructuredFields) => void;
162
- /**
163
- * Cache hit/miss event for warm-path debugging.
164
- */
165
- cache: (op: 'hit' | 'miss' | 'set' | 'del', key: string, fields?: StructuredFields) => void
166
- };
167
106
  export declare interface ResolvedLogSettings {
168
107
  level: LogLevel
169
108
  format: LogFormat
@@ -213,30 +152,5 @@ export declare interface LogErrorOptions {
213
152
  silent?: boolean
214
153
  message?: ErrorMessage
215
154
  }
216
- export declare interface ReportOptions {
217
- status?: number
218
- context?: LogContext
219
- label?: string
220
- }
221
- /**
222
- * Structured logging shorthands for common framework events.
223
- *
224
- * The bare `log.info("…")` form is good for ad-hoc messages, but the
225
- * framework emits a predictable set of events (HTTP requests, DB
226
- * queries, queued jobs, cache operations) that benefit from a stable
227
- * shape so downstream log shippers can index on consistent field
228
- * names.
229
- *
230
- * Each helper:
231
- * 1. Attaches the current trace id (if any) automatically
232
- * 2. Picks the appropriate severity based on outcome
233
- * 3. Emits a consistent JSON shape in production (`event`, `level`,
234
- * `traceId`, …) while keeping the human-readable form in dev
235
- *
236
- * Helpers are batched onto `log.struct` so they don't pollute the
237
- * top-level `log` namespace, and so users can opt out by routing
238
- * `log.struct` to a custom transport in tests.
239
- */
240
- declare interface StructuredFields { [key: string]: unknown }
241
155
  export type LogFormat = 'json' | 'text';
242
156
  export type ErrorMessage = string;
package/dist/index.js CHANGED
@@ -1,11 +1,10 @@
1
- // @bun
2
- import{AsyncLocalStorage as Y}from"async_hooks";import s from"process";var _=0,S=1,f=null,d=null,x=new Set;function R(e){return x.add(e),e.finally(()=>x.delete(e)).catch(()=>{}),e}var j=!1;function B(){if(j)return;j=!0,s.on("beforeExit",()=>{c.flush()})}var g=[],D=new Set,h={debug:0,info:1,success:1,warning:2,error:3};function I(e){return h[e]<h[W()]}function V(e){let n=e;if(!n||typeof n!=="object"||typeof n.log!=="function")return s.stderr.write(`[logging] Ignoring a transport without a log() function.
3
- `),null;if(typeof n.name!=="string"||!n.name)return s.stderr.write(`[logging] Ignoring a transport without a name.
4
- `),null;return g.push(n),()=>{let t=g.indexOf(n);if(t!==-1)g.splice(t,1)}}function se(e){let n=V(e);if(!n)return()=>{};return b(),n}function ae(){return[...g]}function l(e,n,t){if(g.length===0)return;let r;try{r={level:e,message:n,args:t,context:F(),timestamp:new Date().toISOString()}}catch{return}for(let o of g){if(o.level&&h[e]<h[o.level])continue;try{o.log(r)}catch(i){if(!D.has(o.name)){D.add(o.name);let u=i instanceof Error?i.message:String(i);s.stderr.write(`[logging] Transport "${o.name}" threw: ${u}. Further throws from it are silent.
5
- `)}}}}async function K(){let e=g.filter((n)=>typeof n.flush==="function").map((n)=>n.flush().catch(()=>{}));if(e.length>0)await Promise.allSettled(e)}var N=new Set(["debug","info","success","warning","error"]);function p(e,n="info"){if(!e)return n;let t=e.toLowerCase();if(t==="warn")return"warning";if(N.has(t))return t;return s.stderr.write(`[logging] Ignoring invalid LOG_LEVEL="${e}" (expected: ${[...N].join(", ")}); using "${n}".
6
- `),n}var T,A,q;function W(){let e=s.env.LOG_LEVEL;if(e){if(e!==A)A=e,q=p(e);return q}return T??"info"}function U(e){if(e==="json"||e==="text")return e;if(e)s.stderr.write(`[logging] Ignoring invalid LOG_FORMAT="${e}" (expected "json" or "text").
7
- `);return s.env.NODE_ENV==="production"?"json":"text"}function X(e){let n=e.envLevel?p(e.envLevel):e.cfgLevel?p(e.cfgLevel):"info",t=e.envFormat?U(e.envFormat):e.cfgFormat==="json"||e.cfgFormat==="text"?e.cfgFormat:e.isProduction?"json":"text";return{level:n,format:t,writeToFile:e.cfgWriteToFile??!0}}function w(e,n=0){if(n>8)return{name:"Error",message:"[cause chain truncated]"};if(e instanceof Error)return{name:e.name,message:e.message,stack:e.stack,cause:e.cause!=null?w(e.cause,n+1):void 0};if(typeof e==="string")return{name:"Error",message:e};if(e==null)return{name:"Error",message:String(e)};try{return{name:"Error",message:JSON.stringify(y(e))}}catch{return{name:"Error",message:String(e)}}}function k(e){let n=e.stack||`${e.name}: ${e.message}`,t=e.cause;while(t)n+=`
8
- caused by: ${t.stack||`${t.name}: ${t.message}`}`,t=t.cause;return n}function y(e,n=0){if(e instanceof Error)return w(e);if(n>=4||e===null||typeof e!=="object")return e;if(Array.isArray(e))return e.map((o)=>y(o,n+1));let t=Object.getPrototypeOf(e);if(t!==Object.prototype&&t!==null)return e;let r={};for(let[o,i]of Object.entries(e))r[o]=y(i,n+1);return r}function z(e){return y(e)}var C=new Y;function ge(e,n){return C.run(e,n)}function F(){let e=C.getStore(),n=H();if(!n)return e;return{trace_id:n,...e}}function H(){try{let n=globalThis[Symbol.for("stacks.router.traceStorage")]?.getStore?.();if(n)return n;return globalThis[Symbol.for("stacks.router.requestStorage")]?.getStore?.()?._requestId}catch{return}}var Q=Symbol.for("@stacksjs/config:overridesReady"),P=null;async function Z(){try{let e=await import("@stacksjs/config");return e.logging??null}catch{return null}}function G(){let e=globalThis[Q];if(!e||typeof e.then!=="function")return null;return e}async function M(e,n){let{level:t,format:r,writeToFile:o}=X({envLevel:s.env.LOG_LEVEL,envFormat:s.env.LOG_FORMAT,cfgLevel:e?.level,cfgFormat:e?.format,cfgWriteToFile:typeof e?.writeToFile==="boolean"?e.writeToFile:void 0,isProduction:s.env.NODE_ENV==="production"}),i;if(e?.logsPath){let u=await import("path");i=u.dirname(e.logsPath)}if(!i)i=n;if(!i)try{let u=await import("@stacksjs/path");i=u.projectPath("storage/logs")}catch{i="storage/logs"}return{level:t,format:r,writeToFile:o,logDirectory:i}}function J(e){if(!Array.isArray(e?.transports))return;for(let n of e.transports)if(!g.includes(n))V(n)}var v=null;function O(){if(v)return;let e=G();if(!e)return;v=e.then(async(n)=>{let t=n?.logging;if(!t)return;if(J(t),t.level)T=p(t.level);let r=P;if(!r||!f)return;let o=await M(t,r.logDirectory);if(r.level===o.level&&r.format===o.format&&r.writeToFile===o.writeToFile&&r.logDirectory===o.logDirectory)return;let i=f,{Logger:u}=await import("@stacksjs/clarity");f=new u("stacks",{level:o.level,logDirectory:o.logDirectory,showTags:!1,fancy:o.format!=="json",format:o.format,writeToFile:o.writeToFile}),P=o;try{await i.flush?.(),await i.destroy?.()}catch{}}).catch(()=>{})}async function b(){if(f)return;if(d)return d;return B(),d=(async()=>{let e=await Z();if(J(e),e?.level)T=p(e.level);let n=await M(e),{Logger:t}=await import("@stacksjs/clarity");f=new t("stacks",{level:n.level,logDirectory:n.logDirectory,showTags:!1,fancy:n.format!=="json",format:n.format,writeToFile:n.writeToFile}),P=n})(),d.then(O),d}async function a(){return await b(),f}async function ce(){if(await b(),O(),v)await v;return f}function m(...e){let n=e.filter((r)=>r!==void 0).map((r)=>{if(r instanceof Error)return k(w(r));if(typeof r==="object"&&r!==null)return JSON.stringify(y(r),null,2);return String(r)}).join(" "),t=C.getStore();if(t?.requestId)return`[${t.requestId}] ${n}`;return n}var ee=new Set(["shouldExit","silent","message"]);function ne(e){if(!e||typeof e!=="object"||e instanceof Error)return!1;if(!(("shouldExit"in e)||("silent"in e)))return!1;return Object.keys(e).every((n)=>ee.has(n))}var c={info:async(...e)=>{let n=I("info");if(n&&g.length===0)return;let t=m(...e);if(n){l("info",t,e);return}let r=await a();l("info",t,e),await r.info(t)},success:async(e)=>{let n=I("success");if(n&&g.length===0)return;if(n){l("success",e,[e]);return}let t=await a();l("success",e,[e]),await t.success(e)},warn:async(e,n)=>{let t=await a();if(l("warning",e,n===void 0?[e]:[e,n]),n==null){await t.warn(e);return}let r=y(n);await t.warn(e,r)},warning:async(e)=>{let n=await a();l("warning",e,[e]),await n.warn(e)},error:async(e,n,t)=>{let r=ne(n)?n:void 0,o;if(typeof e==="string")o=e;else o=k(w(e));if(n!==void 0&&!r)o=`${o} ${k(w(n))}`;let i={...F(),...t};if(Object.keys(i).length>0)try{o=`${o} ${JSON.stringify(z(i))}`}catch{}let u=await a();if(l("error",o,r?[e]:[e,n,t].filter((E)=>E!==void 0)),await u.error(o),r?.shouldExit){let{handleError:E}=await import("@stacksjs/error-handling/handler");E(e,r)}},debug:async(...e)=>{let n=W()!=="debug";if(n&&g.length===0){if(G()){if(!f&&!d)b();else if(!v)O()}return}let t=m(...e);if(n){l("debug",t,e);return}let r=await a();l("debug",t,e),await r.debug(t)},dump:async(...e)=>{let n=m(...e);await(await a()).debug(`DUMP: ${n}`)},dd:async(...e)=>{let n=m(...e);await(await a()).error(n),s.exit(S)},echo:async(...e)=>{let n=m(...e);await(await a()).info(`ECHO: ${n}`)},time:(e)=>{let n=performance.now();return async(t)=>{let r=performance.now()-n,o=await a(),i=t?` ${JSON.stringify(z(t))}`:"";await o.info(`${e}: ${r.toFixed(2)}ms${i}`)}},syncWarn:(e)=>{s.stderr.write(`${e}
9
- `)},syncError:(e)=>{s.stderr.write(`${e}
10
- `)},fatal:(e,n=S)=>{s.stderr.write(`${e}
11
- `),s.exit(n)},exit:async(e,n=_)=>{if(e)await(n===_?c.success(e):c.error(e));await c.flush(),s.exit(n)},flush:async()=>{if(x.size>0)await Promise.allSettled([...x]);if(await K(),!f&&!d)return;try{let e=await a(),n=e.flush;if(typeof n==="function")await n.call(e)}catch{}}};async function ue(...e){for(let n of e)await c.debug(n)}async function le(...e){let n=m(...e);console.log(n),s.exit(S)}async function fe(...e){await c.debug(...e)}function te(e){if(e&&typeof e==="object"){let n=e;if(typeof n.status==="number")return n.status;if(typeof n.statusCode==="number")return n.statusCode}return}function de(e,n={}){let t=n.status??te(e),r=typeof t==="number"&&t>=400&&t<500,o=n.label??"Unhandled error",i={...n.context,...t!=null?{status:t}:{}};if(r){c.debug(`${o} (client error ${t}): ${w(e).message}`);return}c.error(o,e,i)}function L(e,n,t){let r=F(),o={event:n,traceId:r?.requestId,...t};if(e==="warn")R(c.warn(`[${n}]`,o));else R(c[e](o))}var re={request(e,n,t,r,o={}){let i=t>=500?"error":t>=400?"warn":"info";L(i,"http.request",{method:e,path:n,status:t,durationMs:r,...o})},query(e,n,t={}){L("debug","db.query",{sql:e,durationMs:n,...t})},slowQuery(e,n,t={}){L("warn","db.slow_query",{sql:e,durationMs:n,...t})},job(e,n,t={}){L(n==="failed"?"error":"info",`job.${n}`,{jobName:e,...t})},cache(e,n,t={}){L("debug",`cache.${e}`,{key:n,...t})}};c.struct=re;export{W as currentLevel,le as dd,ue as dump,fe as echo,m as formatMessage,F as getLogContext,c as log,ce as logger,z as normalizeContext,w as normalizeError,U as parseLogFormat,p as parseLogLevel,se as registerTransport,k as renderNormalizedError,de as report,X as resolveLogSettings,re as struct,I as suppressedAtCurrentLevel,ae as transports,ge as withLogContext};
1
+ import{AsyncLocalStorage}from"node:async_hooks";import process from"node:process";const EXIT_CODE_SUCCESS=0,EXIT_CODE_FATAL_ERROR=1;let _logger=null,_loggerInitPromise=null;const pendingWrites=new Set;function track(p){pendingWrites.add(p);p.finally(()=>pendingWrites.delete(p)).catch(()=>{});return p}let _flushOnExitRegistered=!1;function registerFlushOnExit(){if(_flushOnExitRegistered)return;_flushOnExitRegistered=!0;process.on("beforeExit",()=>{log.flush()})}const _transports=[],_brokenTransports=new Set,LEVEL_RANK={debug:0,info:1,success:1,warning:2,error:3};export function suppressedAtCurrentLevel(level){return LEVEL_RANK[level]<LEVEL_RANK[currentLevel()]}function addTransport(candidate){const t=candidate;if(!t||typeof t!=="object"||typeof t.log!=="function"){process.stderr.write(`[logging] Ignoring a transport without a log() function.
2
+ `);return null}if(typeof t.name!=="string"||!t.name){process.stderr.write(`[logging] Ignoring a transport without a name.
3
+ `);return null}_transports.push(t);return()=>{const at=_transports.indexOf(t);if(at!==-1)_transports.splice(at,1)}}export function registerTransport(transport){const detach=addTransport(transport);if(!detach)return()=>{};initLogger();return detach}export function transports(){return[..._transports]}function dispatch(level,message,args){if(_transports.length===0)return;let record;try{record={level,message,args,context:getLogContext(),timestamp:new Date().toISOString()}}catch{return}for(const transport of _transports){if(transport.level&&LEVEL_RANK[level]<LEVEL_RANK[transport.level])continue;try{transport.log(record)}catch(err){if(!_brokenTransports.has(transport.name)){_brokenTransports.add(transport.name);const reason=err instanceof Error?err.message:String(err);process.stderr.write(`[logging] Transport "${transport.name}" threw: ${reason}. Further throws from it are silent.
4
+ `)}}}}async function flushTransports(){const pending=_transports.filter((t)=>typeof t.flush==="function").map((t)=>t.flush().catch(()=>{}));if(pending.length>0)await Promise.allSettled(pending)}const VALID_LEVELS=new Set(["debug","info","success","warning","error"]);export function parseLogLevel(raw,fallback="info"){if(!raw)return fallback;const v=raw.toLowerCase();if(v==="warn")return"warning";if(VALID_LEVELS.has(v))return v;process.stderr.write(`[logging] Ignoring invalid LOG_LEVEL="${raw}" (expected: ${[...VALID_LEVELS].join(", ")}); using "${fallback}".
5
+ `);return fallback}let _configLevel,_envLevelRaw,_envLevelParsed;export function currentLevel(){const raw=process.env.LOG_LEVEL;if(raw){if(raw!==_envLevelRaw){_envLevelRaw=raw;_envLevelParsed=parseLogLevel(raw)}return _envLevelParsed}return _configLevel??"info"}export function parseLogFormat(raw){if(raw==="json"||raw==="text")return raw;if(raw)process.stderr.write(`[logging] Ignoring invalid LOG_FORMAT="${raw}" (expected "json" or "text").
6
+ `);return process.env.NODE_ENV==="production"?"json":"text"}export function resolveLogSettings(input){const level=input.envLevel?parseLogLevel(input.envLevel):input.cfgLevel?parseLogLevel(input.cfgLevel):"info",format=input.envFormat?parseLogFormat(input.envFormat):input.cfgFormat==="json"||input.cfgFormat==="text"?input.cfgFormat:input.isProduction?"json":"text";return{level,format,writeToFile:input.cfgWriteToFile??!0}}export function normalizeError(err,depth=0){if(depth>8)return{name:"Error",message:"[cause chain truncated]"};if(err instanceof Error)return{name:err.name,message:err.message,stack:err.stack,cause:err.cause!=null?normalizeError(err.cause,depth+1):void 0};if(typeof err==="string")return{name:"Error",message:err};if(err==null)return{name:"Error",message:String(err)};try{return{name:"Error",message:JSON.stringify(normalizeContextValue(err))}}catch{return{name:"Error",message:String(err)}}}export function renderNormalizedError(n){let out=n.stack||`${n.name}: ${n.message}`,cause=n.cause;while(cause){out+=`
7
+ caused by: ${cause.stack||`${cause.name}: ${cause.message}`}`;cause=cause.cause}return out}function normalizeContextValue(value,depth=0){if(value instanceof Error)return normalizeError(value);if(depth>=4||value===null||typeof value!=="object")return value;if(Array.isArray(value))return value.map((v)=>normalizeContextValue(v,depth+1));const proto=Object.getPrototypeOf(value);if(proto!==Object.prototype&&proto!==null)return value;const out={};for(const[k,v]of Object.entries(value))out[k]=normalizeContextValue(v,depth+1);return out}export function normalizeContext(ctx){return normalizeContextValue(ctx)}const logContextStorage=new AsyncLocalStorage;export function withLogContext(context,fn){return logContextStorage.run(context,fn)}export function getLogContext(){const store=logContextStorage.getStore(),trace=activeTraceId();if(!trace)return store;return{trace_id:trace,...store}}function activeTraceId(){try{const explicit=globalThis[Symbol.for("stacks.router.traceStorage")]?.getStore?.();if(explicit)return explicit;return globalThis[Symbol.for("stacks.router.requestStorage")]?.getStore?.()?._requestId}catch{return}}const CONFIG_READY_KEY=Symbol.for("@stacksjs/config:overridesReady");let _builtWith=null;async function readLoggingConfigNow(){try{return(await import("@stacksjs/config")).logging??null}catch{return null}}function configReadySignal(){const anchored=globalThis[CONFIG_READY_KEY];if(!anchored||typeof anchored.then!=="function")return null;return anchored}async function resolveSettings(logging,fallbackDirectory){const{level,format,writeToFile}=resolveLogSettings({envLevel:process.env.LOG_LEVEL,envFormat:process.env.LOG_FORMAT,cfgLevel:logging?.level,cfgFormat:logging?.format,cfgWriteToFile:typeof logging?.writeToFile==="boolean"?logging.writeToFile:void 0,isProduction:process.env.NODE_ENV==="production"});let logDirectory;if(logging?.logsPath)logDirectory=(await import("node:path")).dirname(logging.logsPath);if(!logDirectory)logDirectory=fallbackDirectory;if(!logDirectory)try{logDirectory=(await import("@stacksjs/path")).projectPath("storage/logs")}catch{logDirectory="storage/logs"}return{level,format,writeToFile,logDirectory}}function attachDeclaredTransports(logging){if(!Array.isArray(logging?.transports))return;for(const transport of logging.transports)if(!_transports.includes(transport))addTransport(transport)}let _configApplied=null;function applyConfigWhenReady(){if(_configApplied)return;const ready=configReadySignal();if(!ready)return;_configApplied=ready.then(async(resolved)=>{const logging=resolved?.logging;if(!logging)return;attachDeclaredTransports(logging);if(logging.level)_configLevel=parseLogLevel(logging.level);const built=_builtWith;if(!built||!_logger)return;const next=await resolveSettings(logging,built.logDirectory);if(built.level===next.level&&built.format===next.format&&built.writeToFile===next.writeToFile&&built.logDirectory===next.logDirectory)return;const previous=_logger,{Logger}=await import("@stacksjs/clarity");_logger=new Logger("stacks",{level:next.level,logDirectory:next.logDirectory,showTags:!1,fancy:next.format!=="json",format:next.format,writeToFile:next.writeToFile});_builtWith=next;try{await previous.flush?.();await previous.destroy?.()}catch{}}).catch(()=>{})}async function initLogger(){if(_logger)return;if(_loggerInitPromise)return _loggerInitPromise;registerFlushOnExit();_loggerInitPromise=(async()=>{const logging=await readLoggingConfigNow();attachDeclaredTransports(logging);if(logging?.level)_configLevel=parseLogLevel(logging.level);const settings=await resolveSettings(logging),{Logger}=await import("@stacksjs/clarity");_logger=new Logger("stacks",{level:settings.level,logDirectory:settings.logDirectory,showTags:!1,fancy:settings.format!=="json",format:settings.format,writeToFile:settings.writeToFile});_builtWith=settings})();_loggerInitPromise.then(applyConfigWhenReady);return _loggerInitPromise}async function getLogger(){await initLogger();return _logger}export async function logger(){await initLogger();applyConfigWhenReady();if(_configApplied)await _configApplied;return _logger}export function formatMessage(...args){const base=args.filter((arg)=>arg!==void 0).map((arg)=>{if(arg instanceof Error)return renderNormalizedError(normalizeError(arg));if(typeof arg==="object"&&arg!==null)return JSON.stringify(normalizeContextValue(arg),null,2);return String(arg)}).join(" "),ctx=logContextStorage.getStore();if(ctx?.requestId)return`[${ctx.requestId}] ${base}`;return base}const LEGACY_ERROR_OPTION_KEYS=new Set(["shouldExit","silent","message"]);function isLegacyErrorOptions(v){if(!v||typeof v!=="object"||v instanceof Error)return!1;if(!(("shouldExit"in v)||("silent"in v)))return!1;return Object.keys(v).every((k)=>LEGACY_ERROR_OPTION_KEYS.has(k))}export const log={info:async(...args)=>{const suppressed=suppressedAtCurrentLevel("info");if(suppressed&&_transports.length===0)return;const message=formatMessage(...args);if(suppressed){dispatch("info",message,args);return}const logger=await getLogger();dispatch("info",message,args);await logger.info(message)},success:async(message)=>{const suppressed=suppressedAtCurrentLevel("success");if(suppressed&&_transports.length===0)return;if(suppressed){dispatch("success",message,[message]);return}const logger=await getLogger();dispatch("success",message,[message]);await logger.success(message)},warn:async(message,context)=>{const logger=await getLogger();dispatch("warning",message,context===void 0?[message]:[message,context]);if(context==null){await logger.warn(message);return}const normalized=normalizeContextValue(context);await logger.warn(message,normalized)},warning:async(message)=>{const logger=await getLogger();dispatch("warning",message,[message]);await logger.warn(message)},error:async(message,error,context)=>{const legacyOptions=isLegacyErrorOptions(error)?error:void 0;let line;if(typeof message==="string")line=message;else line=renderNormalizedError(normalizeError(message));if(error!==void 0&&!legacyOptions)line=`${line} ${renderNormalizedError(normalizeError(error))}`;const mergedCtx={...getLogContext(),...context};if(Object.keys(mergedCtx).length>0)try{line=`${line} ${JSON.stringify(normalizeContext(mergedCtx))}`}catch{}const logger=await getLogger();dispatch("error",line,legacyOptions?[message]:[message,error,context].filter((a)=>a!==void 0));await logger.error(line);if(legacyOptions?.shouldExit){const{handleError}=await import("@stacksjs/error-handling/handler");handleError(message,legacyOptions)}},debug:async(...args)=>{const suppressed=currentLevel()!=="debug";if(suppressed&&_transports.length===0){if(configReadySignal()){if(!_logger&&!_loggerInitPromise)initLogger();else if(!_configApplied)applyConfigWhenReady()}return}const message=formatMessage(...args);if(suppressed){dispatch("debug",message,args);return}const logger=await getLogger();dispatch("debug",message,args);await logger.debug(message)},dump:async(...args)=>{const message=formatMessage(...args);await(await getLogger()).debug(`DUMP: ${message}`)},dd:async(...args)=>{const message=formatMessage(...args);await(await getLogger()).error(message);process.exit(EXIT_CODE_FATAL_ERROR)},echo:async(...args)=>{const message=formatMessage(...args);await(await getLogger()).info(`ECHO: ${message}`)},time:(label)=>{const start=performance.now();return async(metadata)=>{const duration=performance.now()-start,logger=await getLogger(),meta=metadata?` ${JSON.stringify(normalizeContext(metadata))}`:"";await logger.info(`${label}: ${duration.toFixed(2)}ms${meta}`)}},syncWarn:(msg)=>{process.stderr.write(`${msg}
8
+ `)},syncError:(msg)=>{process.stderr.write(`${msg}
9
+ `)},fatal:(msg,exitCode=EXIT_CODE_FATAL_ERROR)=>{process.stderr.write(`${msg}
10
+ `);process.exit(exitCode)},exit:async(msg,exitCode=EXIT_CODE_SUCCESS)=>{if(msg)await(exitCode===EXIT_CODE_SUCCESS?log.success(msg):log.error(msg));await log.flush();process.exit(exitCode)},flush:async()=>{if(pendingWrites.size>0)await Promise.allSettled([...pendingWrites]);await flushTransports();if(!_logger&&!_loggerInitPromise)return;try{const logger=await getLogger(),maybeFlush=logger.flush;if(typeof maybeFlush==="function")await maybeFlush.call(logger)}catch{}}};globalThis[Symbol.for("@stacksjs/logging:implementation-loaded")]=!0;export async function dump(...args){for(const arg of args)await log.debug(arg)}export async function dd(...args){const message=formatMessage(...args);console.log(message);process.exit(EXIT_CODE_FATAL_ERROR)}export async function echo(...args){await log.debug(...args)}function statusOf(error){if(error&&typeof error==="object"){const e=error;if(typeof e.status==="number")return e.status;if(typeof e.statusCode==="number")return e.statusCode}return}export function report(error,options={}){const status=options.status??statusOf(error),isClientError=typeof status==="number"&&status>=400&&status<500,message=options.label??"Unhandled error",context={...options.context,...status!=null?{status}:{}};if(isClientError){log.debug(`${message} (client error ${status}): ${normalizeError(error).message}`);return}log.error(message,error,context)}function emit(level,event,fields){const ctx=getLogContext(),payload={event,traceId:ctx?.requestId,...fields};if(level==="warn")track(log.warn(`[${event}]`,payload));else track(log[level](payload))}export const struct={request(method,path,status,durationMs,fields={}){const level=status>=500?"error":status>=400?"warn":"info";emit(level,"http.request",{method,path,status,durationMs,...fields})},query(sql,durationMs,fields={}){emit("debug","db.query",{sql,durationMs,...fields})},slowQuery(sql,durationMs,fields={}){emit("warn","db.slow_query",{sql,durationMs,...fields})},job(name,phase,fields={}){emit(phase==="failed"?"error":"info",`job.${phase}`,{jobName:name,...fields})},cache(op,key,fields={}){emit("debug",`cache.${op}`,{key,...fields})}};log.struct=struct;
@@ -0,0 +1,11 @@
1
+ import type { Log, ReportOptions } from './index';
2
+ export declare function report(error: unknown, options?: ReportOptions): void;
3
+ /**
4
+ * Quiet server processes only need the logging facade when a record is
5
+ * emitted. Keep router imports small, then hand every real record to the
6
+ * complete logger so formatting, transports, context, and file output retain
7
+ * one implementation.
8
+ */
9
+ export declare const log: RuntimeLog;
10
+ declare type RuntimeLog = Pick<Log, 'info' | 'warn' | 'error' | 'debug'>;
11
+ declare type LoggingModule = typeof import('./index');
@@ -0,0 +1 @@
1
+ import process from"node:process";const CONFIG_READY_KEY=Symbol.for("@stacksjs/config:overridesReady"),IMPLEMENTATION_LOADED_KEY=Symbol.for("@stacksjs/logging:implementation-loaded"),SUPPRESSED_LEVELS=new Set(["info","success","warn","warning","error"]);let implementationLoad;function loadImplementation(){return implementationLoad??=import("./index")}function implementationIsLoaded(){return globalThis[IMPLEMENTATION_LOADED_KEY]===!0}function configurationNeedsDebug(configuration,envLevel){const logging=configuration?.logging;if(Array.isArray(logging?.transports)&&logging.transports.length>0)return!0;if(envLevel)return envLevel==="debug";const level=logging?.level?.toLowerCase();return level==="debug"||level!==void 0&&!SUPPRESSED_LEVELS.has(level)}function delegateDebug(args){if(implementationIsLoaded())return loadImplementation().then((module)=>module.log.debug(...args));const envLevel=process.env.LOG_LEVEL?.toLowerCase();if(envLevel&&!SUPPRESSED_LEVELS.has(envLevel))return loadImplementation().then((module)=>module.log.debug(...args));const ready=globalThis[CONFIG_READY_KEY];if(!ready||typeof ready.then!=="function")return Promise.resolve();const signal=ready,current=Bun.peek(signal);if(current!==signal)return configurationNeedsDebug(current,envLevel)?loadImplementation().then((module)=>module.log.debug(...args)):Promise.resolve();return signal.then((configuration)=>{if(configurationNeedsDebug(configuration,envLevel))return loadImplementation().then((module)=>module.log.debug(...args))})}export const log={info:(...args)=>loadImplementation().then((module)=>module.log.info(...args)),warn:(...args)=>loadImplementation().then((module)=>module.log.warn(...args)),error:(...args)=>loadImplementation().then((module)=>module.log.error(...args)),debug:(...args)=>delegateDebug(args)};export function report(error,options={}){loadImplementation().then((module)=>module.report(error,options))}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/logging",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.74.45",
5
+ "version": "0.74.47",
6
6
  "description": "The Stacks logging system.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -33,6 +33,12 @@
33
33
  "import": "./dist/index.js",
34
34
  "default": "./dist/index.js"
35
35
  },
36
+ "./runtime": {
37
+ "types": "./dist/runtime.d.ts",
38
+ "bun": "./dist/runtime.js",
39
+ "import": "./dist/runtime.js",
40
+ "default": "./dist/runtime.js"
41
+ },
36
42
  "./*": {
37
43
  "types": "./dist/*.d.ts",
38
44
  "bun": "./dist/*.js",
@@ -60,15 +66,15 @@
60
66
  "dependencies": {
61
67
  "@loghq/stacks": "^0.2.3",
62
68
  "@stacksjs/clarity": "^0.3.33",
63
- "@stacksjs/config": "0.74.45",
64
- "@stacksjs/error-handling": "0.74.45",
65
- "@stacksjs/path": "0.74.45",
66
- "@stacksjs/types": "0.74.45"
69
+ "@stacksjs/config": "0.74.47",
70
+ "@stacksjs/error-handling": "0.74.47",
71
+ "@stacksjs/path": "0.74.47",
72
+ "@stacksjs/types": "0.74.47"
67
73
  },
68
74
  "devDependencies": {
69
- "@stacksjs/cli": "0.74.45",
70
- "@stacksjs/storage": "0.74.45",
71
- "@stacksjs/validation": "0.74.45",
75
+ "@stacksjs/cli": "0.74.47",
76
+ "@stacksjs/storage": "0.74.47",
77
+ "@stacksjs/validation": "0.74.47",
72
78
  "better-dx": "^0.2.24"
73
79
  }
74
80
  }