logan-logger 2.5.1 → 2.5.2
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/browser.cjs +1 -1
- package/dist/browser.mjs +20 -20
- package/dist/bun.cjs +1 -1
- package/dist/bun.mjs +4 -4
- package/dist/chunks/{config-file-z0J0xfos.mjs → config-file-B2bCuTsh.mjs} +158 -149
- package/dist/chunks/config-file-Bi7lhPHV.cjs +1 -0
- package/dist/chunks/{file-transport-DF_YtZ1J.mjs → file-transport-DtEHuVUw.mjs} +2 -2
- package/dist/chunks/file-transport-jelqRjCs.cjs +1 -0
- package/dist/chunks/{config-DsFnr-rq.mjs → formatting-DTrgGC8g.mjs} +62 -56
- package/dist/chunks/{config-BoVokyIX.cjs → formatting-pzIQGL_7.cjs} +1 -1
- package/dist/core/types.d.cts +12 -0
- package/dist/core/types.d.mts +12 -0
- package/dist/core/types.d.ts +12 -0
- package/dist/deno.cjs +1 -1
- package/dist/deno.mjs +3 -3
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +8 -8
- package/dist/node.cjs +1 -1
- package/dist/node.mjs +2 -2
- package/dist/utils/config.d.cts +87 -4
- package/dist/utils/config.d.mts +87 -4
- package/dist/utils/config.d.ts +87 -4
- package/package.json +1 -1
- package/dist/chunks/config-file-C_hoT1V-.cjs +0 -1
- package/dist/chunks/file-transport-93M9zaPy.cjs +0 -1
package/dist/utils/config.d.ts
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
import { LoggerConfig, LogLevel } from '../core/types';
|
|
2
2
|
/**
|
|
3
|
-
* Whether colored output is appropriate
|
|
3
|
+
* Whether colored output is appropriate by default.
|
|
4
4
|
*
|
|
5
5
|
* `capabilities.colorSupport` answers whether the runtime *can* colorize.
|
|
6
6
|
* This answers whether it *should*: writing ANSI escapes into a redirected
|
|
7
7
|
* file or a log shipper is worse than writing none, so a non-TTY stdout
|
|
8
|
-
* disables color unless the caller forces it.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* disables color unless the caller forces it.
|
|
9
|
+
*
|
|
10
|
+
* This resolves the **default** only — every source in SPEC §6.2 outranks it.
|
|
11
|
+
* `NO_COLOR` is deliberately not consulted here: §6.4.1 makes it an override
|
|
12
|
+
* that beats every configuration source, so checking it at this layer would
|
|
13
|
+
* let `LOG_COLOR=true` or an explicit `colorize: true` defeat it. It is applied
|
|
14
|
+
* after the merge instead — see `noColorRequested`, `applyNoColorOverride`, and
|
|
15
|
+
* `createTransports`, which applies it again to per-transport options.
|
|
16
|
+
* @returns True when the level token should carry ANSI color by default
|
|
11
17
|
*/
|
|
12
18
|
export declare function shouldColorize(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the user has asked every program in this session not to emit color.
|
|
21
|
+
*
|
|
22
|
+
* SPEC §6.4.1. Presence is the entire signal and the value is meaningless, so
|
|
23
|
+
* `NO_COLOR=0` disables color exactly as `NO_COLOR=1` does. The value is read
|
|
24
|
+
* raw — neither trimmed nor case-folded — so a single space is a non-empty
|
|
25
|
+
* value and disables color.
|
|
26
|
+
*
|
|
27
|
+
* An empty string counts as **unset**, per no-color.org's own wording:
|
|
28
|
+
* "present and not an empty string". That is the exact opposite of the rule
|
|
29
|
+
* §6.3 imposes on `LOG_LEVEL`, `LOG_FORMAT`, `LOG_TIMESTAMP` and `LOG_COLOR`,
|
|
30
|
+
* where an empty value is *set*, matches nothing, and must warn — which is why
|
|
31
|
+
* `loadConfigFromEnvironment` below guards those with `!== undefined` rather
|
|
32
|
+
* than truthiness (#84).
|
|
33
|
+
*
|
|
34
|
+
* The two rules are opposite deliberately and **must not be reconciled**: §6.3
|
|
35
|
+
* governs variables in this library's namespace, where an empty value is a
|
|
36
|
+
* mistake worth reporting, while `NO_COLOR` is defined by a standard this
|
|
37
|
+
* library does not own and cannot revise. A truthiness test is correct here
|
|
38
|
+
* and a bug ninety lines further down.
|
|
39
|
+
* @returns True when `NO_COLOR` is set to a non-empty value
|
|
40
|
+
*/
|
|
41
|
+
export declare function noColorRequested(): boolean;
|
|
13
42
|
export declare function getDefaultConfig(): LoggerConfig;
|
|
14
43
|
/**
|
|
15
44
|
* Reset the warn-once state, config file warnings included. Exposed for tests;
|
|
@@ -31,7 +60,61 @@ export declare function tryParseLogLevel(level: string): LogLevel | undefined;
|
|
|
31
60
|
*
|
|
32
61
|
* In a browser these values exist only if the bundler inlined them at build
|
|
33
62
|
* time; otherwise this returns an empty object.
|
|
63
|
+
*
|
|
64
|
+
* **This reads the environment; it does not apply SPEC §6.4.1's `NO_COLOR`
|
|
65
|
+
* veto.** Called on its own under `NO_COLOR=1 LOG_COLOR=true` it emits the
|
|
66
|
+
* disagreement diagnostic and still returns `{ colorize: true }`: the message
|
|
67
|
+
* describes what the *resolved* configuration will do, not what this function
|
|
68
|
+
* did. `applyNoColorOverride` is what actually forces `colorize` off, and
|
|
69
|
+
* `createTransports` enforces it per transport. A caller assembling a
|
|
70
|
+
* configuration by hand from this function must apply the former, or let
|
|
71
|
+
* `createLogger()` do both.
|
|
34
72
|
* @returns The subset of configuration the environment specifies
|
|
35
73
|
*/
|
|
36
74
|
export declare function loadConfigFromEnvironment(): Partial<LoggerConfig>;
|
|
37
75
|
export declare function mergeConfigs(...configs: Partial<LoggerConfig>[]): LoggerConfig;
|
|
76
|
+
/**
|
|
77
|
+
* Apply SPEC §6.4.1's `NO_COLOR` override to an already-merged configuration.
|
|
78
|
+
*
|
|
79
|
+
* `NO_COLOR` is not another tier of §6.2's chain — it outranks all of it — so
|
|
80
|
+
* it cannot be expressed as one more argument to `mergeConfigs`, where anything
|
|
81
|
+
* later would beat it. That is precisely the bug this replaces: `shouldColorize()`
|
|
82
|
+
* consulted `NO_COLOR` at the defaults layer, so `LOG_COLOR=true` and an explicit
|
|
83
|
+
* `colorize: true` both silently defeated it.
|
|
84
|
+
*
|
|
85
|
+
* Precedence is the whole reason it is a separate step, and the only one. It is
|
|
86
|
+
* *not* that `mergeConfigs` must be kept clear of the environment: that function
|
|
87
|
+
* seeds itself with `getDefaultConfig()`, which calls `shouldColorize()`, which
|
|
88
|
+
* reads `FORCE_COLOR` and `process.stdout.isTTY`. It has always read the
|
|
89
|
+
* environment, and a purity argument here would be false.
|
|
90
|
+
*
|
|
91
|
+
* **`ignoreEnvironment` does not suppress this**, deliberately. That flag is
|
|
92
|
+
* documented as opting out of `LOG_LEVEL`, `LOG_FORMAT`, `LOG_TIMESTAMP` and
|
|
93
|
+
* `LOG_COLOR` by name, and it exists so a library is not hijacked by the *host
|
|
94
|
+
* application's* operational settings. `NO_COLOR` is a different kind of thing:
|
|
95
|
+
* the *end user's* preference, expressed to every program in their session. A
|
|
96
|
+
* library author who sets the flag for the documented reason must not silently
|
|
97
|
+
* also acquire "and ignore the user's `NO_COLOR`" — least of all because
|
|
98
|
+
* `ignoreEnvironment` is settable from a config file, which would let a file
|
|
99
|
+
* checked into a repository defeat every one of that project's users.
|
|
100
|
+
*
|
|
101
|
+
* **This is not the only place the veto is applied, and it must not be.** It
|
|
102
|
+
* rewrites the top-level `colorize` and nothing else, so a per-transport
|
|
103
|
+
* `options: { colorize: true }` sails straight past it — the console transport
|
|
104
|
+
* factory resolves `options.colorize ?? context.colorize`. `createTransports`
|
|
105
|
+
* therefore resolves `NO_COLOR` again when it builds each transport, which also
|
|
106
|
+
* covers a directly constructed `new NodeLogger(...)` that never reached this
|
|
107
|
+
* function. Between the two, no configuration source defeats `NO_COLOR`:
|
|
108
|
+
* neither `LOG_COLOR`, nor an explicit `colorize: true`, nor a config file, nor
|
|
109
|
+
* a per-transport option, nor `ignoreEnvironment`.
|
|
110
|
+
*
|
|
111
|
+
* What still does: bypassing configuration resolution entirely. `new
|
|
112
|
+
* BrowserLogger({ colorize: true })` reads `config.colorize` and consults no
|
|
113
|
+
* environment at all, so it honors neither `NO_COLOR` nor `LOG_COLOR` nor a
|
|
114
|
+
* config file. That is a property of constructing an adapter by hand rather
|
|
115
|
+
* than a hole in the veto, and `docs/configuration.md` says so where a reader
|
|
116
|
+
* meets `NO_COLOR`.
|
|
117
|
+
* @param config - The configuration after the whole precedence chain
|
|
118
|
+
* @returns The configuration with color forced off when `NO_COLOR` applies
|
|
119
|
+
*/
|
|
120
|
+
export declare function applyNoColorOverride(config: LoggerConfig): LoggerConfig;
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=require("./config-BoVokyIX.cjs");var t=new Map;function n(e,n){t.set(e,n)}function r(e){return t.get(e)}var i=class{constructor(e={}){this.type=`console`,this.level=e.level,this.format=e.format===`json`?`json`:`text`,this.formatOptions={timestamp:e.timestamp!==!1,colorize:e.colorize===!0}}write(t){let n=e.l(t,this.format,this.formatOptions);switch(t.level){case e._.DEBUG:console.debug(n);break;case e._.WARN:console.warn(n);break;case e._.ERROR:console.error(n);break;default:console.info(n)}}};n(`console`,(e,t)=>{let n=e.options??{};return new i({format:n.format??t.format,timestamp:n.timestamp??t.timestamp,colorize:n.colorize??t.colorize,level:e.level})}),n(`custom`,(e,t)=>{let n=e.options?.transport,r=typeof n==`function`?n(t):n;if(!r||typeof r.write!=`function`)throw Error(`custom transport requires options.transport to be an object with a write(entry) method, or a function returning one; ${typeof n==`function`?`the function it was given returned no write(entry) method`:`it was given no object with a write(entry) method`}`);return e.level===void 0?r:{type:r.type??`custom`,level:e.level,write:e=>r.write(e),close:r.close?()=>r.close?.():void 0}});function a(e){let t={format:e.format??`text`,timestamp:e.timestamp??!0,colorize:e.colorize??!1};if(e.transports===void 0)return[new i({format:t.format,timestamp:t.timestamp,colorize:t.colorize})];let n=[];for(let i of e.transports){let e=r(i.type);if(!e){console.warn(`[logan-logger] ${o(i.type)}`);continue}try{n.push(e(i,t))}catch(e){console.warn(`[logan-logger] transport '${i.type}' failed to initialize:`,e)}}return n}function o(e){return e===`file`?`the 'file' transport is not registered; import from 'logan-logger/node' (or 'logan-logger/bun') rather than the main entry point to use file logging`:e===`http`?`the 'http' transport is not built in; supply one with { type: 'custom', options: { transport } }`:`unknown transport type '${e}'; skipping`}var s=class t extends e.g{constructor(e={},t){super(e),this.transports=t??a(e)}getTransports(){return this.transports}close(){for(let e of this.transports)e.close?.()}writeLog(e){for(let t of this.transports)if(!(t.level!==void 0&&e.level<t.level))try{t.write(e)}catch(e){console.warn(`[logan-logger] transport '${t.type}' failed to write:`,e)}}createChild(){return new t(this.config,this.transports)}};function c(e){return{write:t=>{e.info(t.trim())}}}var l=class t{static create(n={}){let r=e.v(),i=t.mergeConfig(n);switch(r.name){case`node`:return new s(i);case`deno`:return new e.u(i);case`bun`:return new s(i);case`browser`:case`webworker`:return new e.u(i);default:return new e.u(i)}}static createChild(e,t){return e.child(t)}static mergeConfig(t){let n=t.ignoreEnvironment?{}:e.n();return e.r(t,n)}};function u(e){return l.create(e)}function d(){let t=f();return u({level:p(t),colorize:t!==`production`&&e.a(),timestamp:!0,format:t===`production`?`json`:`text`})}function f(){return typeof process<`u`&&process.env?process.env.NODE_ENV||process.env.NEXT_PUBLIC_APP_ENV||process.env.ENVIRONMENT||`development`:typeof window<`u`&&globalThis.__ENV__||`development`}function p(t){switch(t){case`production`:return e._.ERROR;case`staging`:case`test`:return e._.WARN;case`development`:case`dev`:return e._.DEBUG;default:return e._.INFO}}function m(t){return e.o(t)??e._.INFO}function h(t){switch(t){case e._.DEBUG:return`debug`;case e._.INFO:return`info`;case e._.WARN:return`warn`;case e._.ERROR:return`error`;case e._.SILENT:return`silent`;default:return`info`}}var g=[{path:`logan.config.json`},{path:`.loganrc`},{path:`package.json`,key:`logan`}],_=new Set([`level`,`format`,`timestamp`,`colorize`,`ignoreEnvironment`,`metadata`,`transports`,`$schema`]),v=new Set([`EACCES`,`EPERM`,`NotCapable`,`PermissionDenied`]);function y(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function b(e){return e instanceof Error?e.message:String(e)}function x(e){let t=/at position \d+(?: \(line \d+ column \d+\))?/.exec(b(e));return t?`not valid JSON (${t[0]})`:`not valid JSON`}function S(e){return e.startsWith(`/`)||e.startsWith(`\\`)||/^[A-Za-z]:/.test(e)}function C(e,t){return t===void 0||t===``||S(e)?e:`${t.replace(/[\\/]+$/,``)}/${e}`}async function w(e,t){if(e===`deno`)return e=>C(e,t);let{resolve:n}=await import(`node:path`);return t===void 0?e=>n(e):e=>n(t,e)}function T(e){let t=e.split(/[\\/]/);return t[t.length-1]||e}function E(e){if(e===`deno`)try{return globalThis.Deno?.cwd?.()}catch{return}return typeof process<`u`&&typeof process.cwd==`function`?process.cwd():void 0}function D(e){let t=e?.code??``,n=e?.name??``;return t===`ENOENT`||n===`NotFound`?{kind:`absent`}:v.has(t)||v.has(n)?{kind:`denied`,reason:b(e)}:{kind:`unreadable`,reason:b(e)}}async function O(e,t){let n=t===`deno`?()=>globalThis.Deno.readTextFile(e):async()=>(await import(`node:fs/promises`)).readFile(e,`utf-8`);try{return{kind:`content`,content:await n()}}catch(e){return D(e)}}function k(t){if(typeof t==`string`)return e.o(t);if(typeof t==`number`&&e._[t]!==void 0)return t}function A(t,n,r){let i=t=>{e.s(`[logan-logger] ${r}: ignoring transports[${n}]: ${t}.`)};if(!y(t)){i(`expected an object, got ${JSON.stringify(t)??typeof t}`);return}if(typeof t.type!=`string`){i(`"type" is required and must be a string`);return}if(t.options!==void 0&&!y(t.options)){i(`"options" must be an object`);return}let a={type:t.type,options:t.options??{}};if(t.level!==void 0){let i=k(t.level);i===void 0?e.s(`[logan-logger] ${r}: ignoring transports[${n}].level=${JSON.stringify(t.level)}. Accepted: debug, info, warn, error, silent.`):a.level=i}return a}function j(t,n){let r={},i=(t,r,i)=>{e.s(`[logan-logger] ${n}: ignoring ${t}=${JSON.stringify(r)}. Accepted: ${i}.`)};for(let r of Object.keys(t))_.has(r)||e.s(`[logan-logger] ${n}: ignoring unknown field "${r}". Known fields: level, format, timestamp, colorize, ignoreEnvironment, metadata, transports.`);if(t.level!==void 0){let e=k(t.level);e===void 0?i(`level`,t.level,`debug, info, warn, error, silent`):r.level=e}t.format!==void 0&&(t.format===`json`||t.format===`text`||t.format===`custom`?r.format=t.format:i(`format`,t.format,`json, text, custom`));for(let e of[`timestamp`,`colorize`,`ignoreEnvironment`])t[e]!==void 0&&(typeof t[e]==`boolean`?r[e]=t[e]:i(e,t[e],`true, false`));if(t.metadata!==void 0&&(y(t.metadata)?r.metadata=t.metadata:i(`metadata`,t.metadata,`an object`)),Array.isArray(t.transports)){let e=t.transports.map((e,t)=>A(e,t,n)).filter(e=>e!==void 0);(e.length>0||t.transports.length===0)&&(r.transports=e)}else t.transports!==void 0&&i(`transports`,t.transports,`an array of transport configs`);return r}async function M(e,t){let n=await O(e.path,t);if(n.kind===`absent`)return n;if(n.kind!==`content`)return{kind:n.kind,path:e.path,reason:n.reason};let r;try{r=JSON.parse(n.content)}catch(t){return{kind:`invalid`,path:e.path,reason:x(t)}}let i=r;if(e.key){if(!y(r))return{kind:`invalid`,path:e.path,reason:`expected a JSON object`};if(!Object.hasOwn(r,e.key))return{kind:`keyless`,path:e.path,key:e.key};i=r[e.key]}if(!y(i)){let t=e.key?`the "${e.key}" key`:`the file`;return{kind:`invalid`,path:e.path,reason:`expected ${t} to be an object`}}return{kind:`found`,config:j(i,e.path)}}function N(e){let t=g.find(t=>t.path===T(e));return t?{...t,path:e}:{path:e}}function P(t,n){e.s(`[logan-logger] cannot read config at ${t} — no permission; using defaults instead: ${n}`)}async function F(t,n={}){let r=e.v();if(!r.capabilities.fileSystem)return{};let i=n.cwd||E(r.name),a=await w(r.name,i);if(t!==void 0){let e=a(t),n=await M(N(e),r.name);if(n.kind===`found`)return n.config;if(n.kind===`absent`)throw Error(`[logan-logger] config file not found: ${e}`);if(n.kind===`keyless`)throw Error(`[logan-logger] no "${n.key}" key in ${n.path}`);if(n.kind===`denied`)return P(n.path,n.reason),{};throw n.kind===`unreadable`?Error(`[logan-logger] config at ${n.path} is not a readable file: ${n.reason}`):Error(`[logan-logger] config at ${n.path} is invalid: ${n.reason}`)}for(let t of g){let n=await M({...t,path:a(t.path)},r.name);if(n.kind===`found`)return n.config;if(n.kind===`invalid`)return e.s(`[logan-logger] config at ${n.path} is invalid: ${n.reason}`),{};if(n.kind===`denied`)return P(n.path,n.reason),{};n.kind===`unreadable`&&e.s(`[logan-logger] skipping ${n.path}: not a readable file (${n.reason})`)}return{}}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return c}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return n}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return a}});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const e=require("./config-BoVokyIX.cjs"),t=require("./config-file-C_hoT1V-.cjs");let n=require("node:fs"),r=require("node:path");var i=5242880,a=5,o=class{constructor(e){if(this.type=`file`,this.size=0,this.warned=!1,!e?.filename)throw Error(`file transport requires options.filename`);this.path=(0,r.resolve)(e.filename),this.maxsize=e.maxsize??i,this.maxFiles=e.maxFiles??a,this.format=e.format===`text`?`text`:`json`,this.timestamp=e.timestamp!==!1,this.level=e.level}get filename(){return this.path}write(t){let r=`${e.l(t,this.format,{timestamp:this.timestamp,colorize:!1})}\n`;try{this.open(),this.maxsize>0&&this.size>=this.maxsize&&(this.rotate(),this.open()),this.size+=(0,n.writeSync)(this.fd,r)}catch(e){this.warnOnce(e)}}close(){if(this.fd!==void 0)try{(0,n.closeSync)(this.fd)}finally{this.fd=void 0}}open(){if(this.fd!==void 0)return;let e=(0,r.dirname)(this.path);try{(0,n.mkdirSync)(e,{recursive:!0})}catch(t){throw s(`create log directory`,e,t)}try{this.fd=(0,n.openSync)(this.path,`a`)}catch(e){throw s(`open log file`,this.path,e)}this.size=(0,n.fstatSync)(this.fd).size}rotate(){if(this.close(),this.maxFiles<=0){(0,n.existsSync)(this.path)&&(0,n.unlinkSync)(this.path),this.size=0;return}let e=`${this.path}.${this.maxFiles}`;(0,n.existsSync)(e)&&(0,n.unlinkSync)(e);for(let e=this.maxFiles-1;e>=1;e--){let t=`${this.path}.${e}`;(0,n.existsSync)(t)&&(0,n.renameSync)(t,`${this.path}.${e+1}`)}(0,n.existsSync)(this.path)&&(0,n.renameSync)(this.path,`${this.path}.1`),this.size=0}warnOnce(e){this.close(),!this.warned&&(this.warned=!0,console.warn(`[logan-logger] file transport for '${this.path}' failed and will keep retrying quietly:`,e instanceof Error?e.message:e))}};function s(e,t,n){let r=n,i=r?.code?` [${r.code}]`:``,a=r?.syscall?` during ${r.syscall}`:``,o=Error(`could not ${e} '${t}'${i}${a}: ${r?.message??n}`);return o.cause=n,o}t.f(`file`,(e,t)=>{let n=e.options??{};return new o({filename:n.filename,maxsize:n.maxsize,maxFiles:n.maxFiles,format:n.format,timestamp:n.timestamp??t.timestamp,level:e.level})}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return o}});
|