@stacksjs/logging 0.75.84 → 0.75.85

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
@@ -93,6 +93,17 @@ export declare function withLogContext<T>(context: LogContext, fn: () => T): T;
93
93
  * ambient one.
94
94
  */
95
95
  export declare function getLogContext(): LogContext | undefined;
96
+ /**
97
+ * Where logs go when config names no `logsPath`: the project's storage/logs,
98
+ * or, when the working directory is not a project at all, a per-user cache.
99
+ *
100
+ * Commands run outside a project are real: `buddy new my-app` (often through
101
+ * `panx buddy new`) runs from wherever the user happens to be. Resolving
102
+ * storage/logs there made every such run leave a stray `storage/logs/` beside
103
+ * the new app, in ~/Code or wherever it was started. A directory counts as a
104
+ * project if it has a package.json or a storage/ of its own.
105
+ */
106
+ export declare function defaultLogDirectory(projectRoot: string, exists: (path: string) => boolean, home: string): string;
96
107
  /*.ts` file,
97
108
  * or from a module one of them imports at evaluation time: it waits on the
98
109
  * very load those files are part of. The `log.*` methods have no such
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import{captureError,flushErrorReporters}from"@stacksjs/error-handling/reporters"
4
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
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
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}
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{const p=await import("@stacksjs/path"),{existsSync}=await import("node:fs"),{homedir}=await import("node:os");logDirectory=defaultLogDirectory(p.projectPath(),existsSync,homedir())}catch{logDirectory="storage/logs"}return{level,format,writeToFile,logDirectory}}export function defaultLogDirectory(projectRoot,exists,home){return exists(`${projectRoot}/package.json`)||exists(`${projectRoot}/storage`)?`${projectRoot}/storage/logs`:`${home}/.cache/stacks/logs`}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
8
  `)},syncError:(msg)=>{process.stderr.write(`${msg}
9
9
  `)},fatal:(msg,exitCode=EXIT_CODE_FATAL_ERROR)=>{process.stderr.write(`${msg}
10
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();await flushErrorReporters();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}captureError(error,{...context,...options.label?{label:options.label}:{}});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;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/logging",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.75.84",
5
+ "version": "0.75.85",
6
6
  "description": "The Stacks logging system.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -66,15 +66,15 @@
66
66
  "dependencies": {
67
67
  "@loghq/stacks": "^0.2.6",
68
68
  "@stacksjs/clarity": "^0.3.33",
69
- "@stacksjs/config": "0.75.84",
70
- "@stacksjs/error-handling": "0.75.84",
71
- "@stacksjs/path": "0.75.84",
72
- "@stacksjs/types": "0.75.84"
69
+ "@stacksjs/config": "0.75.85",
70
+ "@stacksjs/error-handling": "0.75.85",
71
+ "@stacksjs/path": "0.75.85",
72
+ "@stacksjs/types": "0.75.85"
73
73
  },
74
74
  "devDependencies": {
75
- "@stacksjs/cli": "0.75.84",
76
- "@stacksjs/storage": "0.75.84",
77
- "@stacksjs/validation": "0.75.84",
75
+ "@stacksjs/cli": "0.75.85",
76
+ "@stacksjs/storage": "0.75.85",
77
+ "@stacksjs/validation": "0.75.85",
78
78
  "better-dx": "^0.2.24"
79
79
  }
80
80
  }