@meadown/logger 1.8.6 → 1.8.8

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # @meadown/logger
4
4
 
5
- A **development-focused logger** for Node.js and TypeScript built to make your
5
+ A **development-focused logger** for Node.js and TypeScript. Built to make your
6
6
  development loop faster and your terminal actually readable.
7
7
 
8
8
  No dependencies. No config. Import it and you're done.
@@ -10,7 +10,7 @@ No dependencies. No config. Import it and you're done.
10
10
  ## Why this exists
11
11
 
12
12
  I kept writing the same `console.log` wrapper in every project. Every time.
13
- Copy, paste, rename. And I still shipped it to production by accident. And I
13
+ Copy, paste, rename. And I still shipped it to production by accident. Unconsciously I
14
14
  still spent ten minutes staring at logs trying to figure out which file they
15
15
  came from.
16
16
 
@@ -19,6 +19,10 @@ At some point I just built the thing I always wanted.
19
19
  One import. No config. No dependencies. It shows you exactly where every log
20
20
  came from, and it gets out of the way when you ship.
21
21
 
22
+ It's not trying to be Winston or Pino. No transports, no log levels, no config files.
23
+ Just a better `console.log` for the hours you spend in development. One that
24
+ tells you where things came from and disappears when you ship.
25
+
22
26
  > The full story — the problem, the research, every design decision, and
23
27
  > everything that got cut — is in [`docs/STORY.md`](docs/STORY.md).
24
28
 
@@ -27,7 +31,7 @@ came from, and it gets out of the way when you ship.
27
31
  - **Zero dependencies**
28
32
  - **Development-focused** — built for the dev experience, not production ops
29
33
  - **Clickable source link** — every log is a clickable link to the exact file it came from
30
- - **API response logging** — `tap` a fetch and get timing, status, size, and body automatically
34
+ - **Tap logging** — log any value or promise inline; fetch calls also get timing, status, size, and body
31
35
  - **Color-coded levels** — `[INFO]` cyan, `[WARN]` yellow, `[ERROR]` red
32
36
  - **Tree layout output** — clean, scannable structure in your terminal
33
37
  - **Collapsible messages** — cap long output with `logger.maxLines`
@@ -44,6 +48,9 @@ yarn add @meadown/logger
44
48
 
45
49
  ## Using it
46
50
 
51
+ Set `NODE_ENV=production` and all output is suppressed. Anything else and
52
+ logging is on. No config files, no init call, no options object.
53
+
47
54
  ```ts
48
55
  import logger from "@meadown/logger"
49
56
 
@@ -60,9 +67,41 @@ logger.error("Something went wrong")
60
67
  └── 05-30 04:00:00 PM - (server.ts:42)
61
68
  ```
62
69
 
70
+ ### Recommended: single shared import
71
+
72
+ Rather than importing directly from `@meadown/logger` in every file, create
73
+ one shared module in your project and re-export from there. This gives you a
74
+ single place to set options like `maxLines` and keeps any future changes to
75
+ one file.
76
+
77
+ ```ts
78
+ // lib/logger.ts
79
+ import logger from "@meadown/logger"
80
+
81
+ logger.maxLines = 10 // configure once, applies everywhere
82
+
83
+ export default logger
84
+ ```
85
+
86
+ ```ts
87
+ // anywhere else in your project
88
+ import logger from "@/lib/logger"
89
+ ```
90
+
91
+ When re-exporting, use a direct re-export, Not a wrapper function. A wrapper
92
+ breaks the caller location shown in every log line.
93
+
94
+ ```ts
95
+ // GOOD — location stays honest
96
+ export { default as logger } from "@meadown/logger"
97
+
98
+ // BAD — every log points at this file, not the real caller
99
+ export const logger = (...args) => log(...args)
100
+ ```
101
+
63
102
  ## API response logging
64
103
 
65
- Drop `tap` into any `await` chain you get timing, status, size, and the
104
+ Drop `tap` into any `await` chain. You get timing, status, size, and the
66
105
  actual response body. The promise flows through untouched. One line of code.
67
106
 
68
107
  ```ts
@@ -93,25 +132,45 @@ const user = await logger.tap(
93
132
  You can immediately see: was it successful? How long did it take? What came
94
133
  back? Without opening DevTools.
95
134
 
96
- ![API response logging tap a fetch and see timing, status, size, and body](media/tap-api-demo.png)
135
+ ![API response logging: tap a fetch and see timing, status, size, and body](media/tap-api-demo.png)
136
+
137
+ ### Tap any value
138
+
139
+ `tap` works on anything, not just fetch. Pass in any value or expression and
140
+ get it back exactly as it was. The only thing that happens is a log.
141
+
142
+ ```ts
143
+ // numbers, strings, objects — logged and returned as-is
144
+ logger.tap(port, "port")
145
+ logger.tap(process.env.NODE_ENV, "env")
146
+ logger.tap(config, "loaded config")
147
+ ```
97
148
 
98
- Works with plain values too — logs it, returns it, nothing changes:
149
+ ```ts
150
+ // async functions — promise flows through, timing logged when it settles
151
+ const user = await logger.tap(getUser(), "getUser")
152
+ const config = await logger.tap(loadConfig(), "loadConfig")
153
+ ```
99
154
 
100
155
  ```ts
101
- const port = logger.tap(5000, "port") // port is still 5000
102
- const user = logger.tap(await getUser(), "user") // same as without tap
156
+ // inline no temp variable needed
157
+ server.listen(logger.tap(port, "port"))
103
158
  ```
104
159
 
160
+ If it's a promise, `tap` logs elapsed time once it settles. If it resolves
161
+ to a `Response` (any fetch like call), you also get status and size, same
162
+ as the fetch example above.
163
+
105
164
  ## Clickable source link
106
165
 
107
- That `(server.ts:42)` is a **clickable link** open it and you land on the exact
108
- line that wrote the log. Works in VS Code, iTerm2, WezTerm, Kitty, and Windows
109
- Terminal. Degrades to plain text everywhere else.
166
+ That `(server.ts:42)` is a **clickable link**. Click it and the file opens.
167
+ The line number is right there so you can jump straight to it. Works in VS Code,
168
+ iTerm2, WezTerm, Kitty, and Windows Terminal. Degrades to plain text everywhere else.
110
169
 
111
170
  ## Color-coded levels
112
171
 
113
- `[INFO]` cyan · `[WARN]` yellow · `[ERROR]` red. Timestamp and location tinted teal.
114
- Auto-disabled when output is piped no escape codes in your log files.
172
+ `[INFO]` , `[TAP]` cyan · `[WARN]` yellow · `[ERROR]` red. Timestamp and location tinted teal.
173
+ Auto-disabled when output is piped no escape codes in your log files.
115
174
 
116
175
  ## Tree layout output
117
176
 
@@ -121,7 +180,7 @@ Auto-disabled when output is piped — no escape codes in your log files.
121
180
  └── 05-30 04:00:00 PM - (server.ts:42)
122
181
  ```
123
182
 
124
- Level tag, message, timestamp, and location all in a clean tree. Easy to scan,
183
+ Level tag, message, timestamp, and location all in a clean tree. Easy to scan,
125
184
  even in a busy terminal.
126
185
 
127
186
  ## Collapsible messages
@@ -131,34 +190,25 @@ logger.maxLines = 5 // show 5 lines, then "... N more lines"
131
190
  logger.maxLines = 0 // default — show everything
132
191
  ```
133
192
 
134
- ### One thing if you re-export it
135
-
136
- ```ts
137
- // GOOD — location stays honest
138
- export { default as logger } from "@meadown/logger"
139
-
140
- // BAD — every log points at this file, not the real caller
141
- export const logger = (...args) => log(...args)
142
- ```
143
-
144
193
  ## NODE_ENV
145
194
 
146
- This logger reads `NODE_ENV` to decide when to log. By default it logs everywhere
147
- except `production`. This is a deliberate dev-focused default a production-grade
148
- logger with transport, persistence, and log levels is a separate concern and a
149
- future direction.
195
+ You shouldn't have to think about whether your logs will leak into production.
196
+ This package reads `NODE_ENV` and handles it for you. Set it to `production`
197
+ and all output is handled for you. You never have to remember to wrap a log call,
198
+ remove a debug line, or grep the codebase before a release.
150
199
 
151
- | `NODE_ENV` | Logs? |
152
- | ---------------------------------------- | ------ |
153
- | not set, `development`, or anything else | shown |
154
- | `production` | silent |
200
+ | `NODE_ENV` | Logs? |
201
+ | ---------------------------------------- | ---------- |
202
+ | not set, `development`, or anything else | shown |
203
+ | `production` | suppressed |
155
204
 
156
205
  ## Security
157
206
 
158
207
  Zero dependencies, no file or network access, nothing persisted.
159
- See [SECURITY.md](https://github.com/meadown/meadown-logger/blob/main/SECURITY.md)
160
- for the full security model.
208
+ See [SECURITY.md](https://github.com/meadown/meadown-logger/blob/main/SECURITY.md) for the full security model.
161
209
 
162
210
  ## License
163
211
 
164
- MIT © meadown
212
+ Architected and developed by [Dewan Mobashirul](https://linkedin.com/in/dewan-meadown)
213
+
214
+ MIT © [meadown](https://github.com/meadown)
@@ -23,10 +23,10 @@ export default function getCaller() {
23
23
  // Split off the trailing `:line:column`, keeping the (possibly colon-bearing)
24
24
  // path intact (e.g. Windows `C:\…` or `file://…`).
25
25
  const match = inner.match(/^(.*):(\d+):\d+$/);
26
- const file = match?.[1];
27
- if (file === undefined)
26
+ if (match === null)
28
27
  return UNKNOWN;
29
- const line = Number(match?.[2]);
28
+ const file = match[1] ?? "";
29
+ const line = Number(match[2]);
30
30
  const base = file.split(/[/\\]/).pop() ?? "";
31
31
  if (!SOURCE_FILE.test(base))
32
32
  return UNKNOWN;
@@ -26,10 +26,10 @@ function getCaller() {
26
26
  // Split off the trailing `:line:column`, keeping the (possibly colon-bearing)
27
27
  // path intact (e.g. Windows `C:\…` or `file://…`).
28
28
  const match = inner.match(/^(.*):(\d+):\d+$/);
29
- const file = match?.[1];
30
- if (file === undefined)
29
+ if (match === null)
31
30
  return UNKNOWN;
32
- const line = Number(match?.[2]);
31
+ const file = match[1] ?? "";
32
+ const line = Number(match[2]);
33
33
  const base = file.split(/[/\\]/).pop() ?? "";
34
34
  if (!SOURCE_FILE.test(base))
35
35
  return UNKNOWN;
@@ -9,6 +9,6 @@ export declare const BRANCH_END = "\u2514\u2500\u2500";
9
9
  export declare const SEPARATOR = "-";
10
10
  /** Hang-indent for message continuation lines, so they align under the message
11
11
  * text (the `├── ` branch is 4 columns wide). */
12
- export declare const MESSAGE_INDENT = "|\t";
12
+ export declare const MESSAGE_INDENT = "\u2502 ";
13
13
  /** Default for the collapse setting: 0 = show every line. */
14
14
  export declare const DEFAULT_MAX_LINES = 0;
@@ -22,6 +22,6 @@ exports.BRANCH_END = "└──"; // the last (metadata) branch
22
22
  exports.SEPARATOR = "-"; // between the timestamp and the location
23
23
  /** Hang-indent for message continuation lines, so they align under the message
24
24
  * text (the `├── ` branch is 4 columns wide). */
25
- exports.MESSAGE_INDENT = "|\t";
25
+ exports.MESSAGE_INDENT = "";
26
26
  /** Default for the collapse setting: 0 = show every line. */
27
27
  exports.DEFAULT_MAX_LINES = 0;
@@ -76,17 +76,14 @@ function writeLog(opts) {
76
76
  // One terminal check drives both color and clickable links — `isTTY` is the
77
77
  // single source of truth (DRY). Off when output is piped/redirected.
78
78
  const useColor = (0, isTTY_js_1.isTTY)(streamName);
79
+ const paint = (s, c) => useColor ? (0, color_js_1.colorize)(s, c) : s;
79
80
  const location = formatLocation(caller, useColor);
80
- // Colors (terminal only): tag by level, timestamp teal, location dim teal,
81
- // branch and separator gray.
82
- const tagOut = useColor ? (0, color_js_1.colorize)(tag, constants_js_1.TAG_COLOR[channel]) : tag;
83
- const timeStamp = useColor ? (0, color_js_1.colorize)((0, getTimeStamp_js_1.default)(), "teal") : (0, getTimeStamp_js_1.default)();
84
- const locOut = useColor
85
- ? (0, color_js_1.colorize)(`(${location})`, "dimTeal")
86
- : `(${location})`;
87
- const connector = useColor ? (0, color_js_1.colorize)(constants_js_1.BRANCH, "gray") : constants_js_1.BRANCH;
88
- const connectorBottom = useColor ? (0, color_js_1.colorize)(constants_js_1.BRANCH_END, "gray") : constants_js_1.BRANCH_END;
89
- const separator = useColor ? (0, color_js_1.colorize)(constants_js_1.SEPARATOR, "gray") : constants_js_1.SEPARATOR;
81
+ const tagOut = paint(tag, constants_js_1.TAG_COLOR[channel]);
82
+ const timeStamp = paint((0, getTimeStamp_js_1.default)(), "teal");
83
+ const locOut = paint(`(${location})`, "dimTeal");
84
+ const connector = paint(constants_js_1.BRANCH, "gray");
85
+ const connectorBottom = paint(constants_js_1.BRANCH_END, "gray");
86
+ const separator = paint(constants_js_1.SEPARATOR, "gray");
90
87
  // Layout: the tag, the message hanging off a `├──` branch, then the timestamp
91
88
  // and location on a `└──` branch below. Leading `\n` spaces entries apart.
92
89
  const message = renderMessage(args, useColor);
@@ -106,23 +106,18 @@ async function readBody(res) {
106
106
  * │ └── name: Leanne Graham
107
107
  */
108
108
  function buildBlock(label, ms, res, body, useColor) {
109
- const pipe = useColor ? (0, color_js_1.colorize)("│", "gray") : "│";
110
- const branch = useColor ? (0, color_js_1.colorize)("├──", "gray") : "├──";
111
- const last = useColor ? (0, color_js_1.colorize)("└──", "gray") : "└──";
109
+ const paint = (s, c) => useColor ? (0, color_js_1.colorize)(s, c) : s;
110
+ const pipe = paint("", "gray");
111
+ const branch = paint("├──", "gray");
112
+ const last = paint("└──", "gray");
112
113
  const indent = `${pipe} `;
113
- const statusLine = `${indent}${branch} status: ${formatStatus(res, useColor)}`;
114
114
  const timeLine = `${indent}${branch} time: ${formatDuration(ms, useColor)}`;
115
+ const statusLine = `${indent}${branch} status: ${formatStatus(res, useColor)}`;
115
116
  const sizeLine = `${indent}${last} size: ${body.size}`;
116
- const responseBlock = [
117
- `${pipe}`,
118
- `${indent}response:`,
119
- timeLine,
120
- statusLine,
121
- sizeLine,
122
- ].join("\n");
117
+ const responseLines = [`${pipe}`, `${indent}response:`, timeLine, statusLine, sizeLine];
118
+ const head = label === undefined ? "" : `${label}\n`;
123
119
  if (body.data === undefined) {
124
- const head = label === undefined ? "" : `${label}\n`;
125
- return [`${head}${responseBlock}`];
120
+ return [`${head}${responseLines.join("\n")}`];
126
121
  }
127
122
  // Render the body using util.formatWithOptions so objects/arrays look like
128
123
  // console.log output — colors, proper nesting, no JSON.stringify quirkiness.
@@ -132,17 +127,7 @@ function buildBlock(label, ms, res, body, useColor) {
132
127
  const bodyBlock = bodyLines
133
128
  .map((line, i) => `${indent}${i === lastIdx ? last : branch} ${line}`)
134
129
  .join("\n");
135
- const full = [
136
- `${pipe}`,
137
- `${indent}response:`,
138
- timeLine,
139
- statusLine,
140
- sizeLine,
141
- `${pipe}`,
142
- `${indent}body:`,
143
- bodyBlock,
144
- ].join("\n");
145
- const head = label === undefined ? "" : `${label}\n`;
130
+ const full = [...responseLines, `${pipe}`, `${indent}body:`, bodyBlock].join("\n");
146
131
  return [`${head}${full}`];
147
132
  }
148
133
  /**
@@ -9,6 +9,6 @@ export declare const BRANCH_END = "\u2514\u2500\u2500";
9
9
  export declare const SEPARATOR = "-";
10
10
  /** Hang-indent for message continuation lines, so they align under the message
11
11
  * text (the `├── ` branch is 4 columns wide). */
12
- export declare const MESSAGE_INDENT = "|\t";
12
+ export declare const MESSAGE_INDENT = "\u2502 ";
13
13
  /** Default for the collapse setting: 0 = show every line. */
14
14
  export declare const DEFAULT_MAX_LINES = 0;
package/dist/constants.js CHANGED
@@ -19,6 +19,6 @@ export const BRANCH_END = "└──"; // the last (metadata) branch
19
19
  export const SEPARATOR = "-"; // between the timestamp and the location
20
20
  /** Hang-indent for message continuation lines, so they align under the message
21
21
  * text (the `├── ` branch is 4 columns wide). */
22
- export const MESSAGE_INDENT = "|\t";
22
+ export const MESSAGE_INDENT = "";
23
23
  /** Default for the collapse setting: 0 = show every line. */
24
24
  export const DEFAULT_MAX_LINES = 0;
@@ -68,17 +68,14 @@ export function writeLog(opts) {
68
68
  // One terminal check drives both color and clickable links — `isTTY` is the
69
69
  // single source of truth (DRY). Off when output is piped/redirected.
70
70
  const useColor = isTTY(streamName);
71
+ const paint = (s, c) => useColor ? colorize(s, c) : s;
71
72
  const location = formatLocation(caller, useColor);
72
- // Colors (terminal only): tag by level, timestamp teal, location dim teal,
73
- // branch and separator gray.
74
- const tagOut = useColor ? colorize(tag, TAG_COLOR[channel]) : tag;
75
- const timeStamp = useColor ? colorize(getTimeStamp(), "teal") : getTimeStamp();
76
- const locOut = useColor
77
- ? colorize(`(${location})`, "dimTeal")
78
- : `(${location})`;
79
- const connector = useColor ? colorize(BRANCH, "gray") : BRANCH;
80
- const connectorBottom = useColor ? colorize(BRANCH_END, "gray") : BRANCH_END;
81
- const separator = useColor ? colorize(SEPARATOR, "gray") : SEPARATOR;
73
+ const tagOut = paint(tag, TAG_COLOR[channel]);
74
+ const timeStamp = paint(getTimeStamp(), "teal");
75
+ const locOut = paint(`(${location})`, "dimTeal");
76
+ const connector = paint(BRANCH, "gray");
77
+ const connectorBottom = paint(BRANCH_END, "gray");
78
+ const separator = paint(SEPARATOR, "gray");
82
79
  // Layout: the tag, the message hanging off a `├──` branch, then the timestamp
83
80
  // and location on a `└──` branch below. Leading `\n` spaces entries apart.
84
81
  const message = renderMessage(args, useColor);
@@ -102,23 +102,18 @@ async function readBody(res) {
102
102
  * │ └── name: Leanne Graham
103
103
  */
104
104
  function buildBlock(label, ms, res, body, useColor) {
105
- const pipe = useColor ? colorize("│", "gray") : "│";
106
- const branch = useColor ? colorize("├──", "gray") : "├──";
107
- const last = useColor ? colorize("└──", "gray") : "└──";
105
+ const paint = (s, c) => useColor ? colorize(s, c) : s;
106
+ const pipe = paint("", "gray");
107
+ const branch = paint("├──", "gray");
108
+ const last = paint("└──", "gray");
108
109
  const indent = `${pipe} `;
109
- const statusLine = `${indent}${branch} status: ${formatStatus(res, useColor)}`;
110
110
  const timeLine = `${indent}${branch} time: ${formatDuration(ms, useColor)}`;
111
+ const statusLine = `${indent}${branch} status: ${formatStatus(res, useColor)}`;
111
112
  const sizeLine = `${indent}${last} size: ${body.size}`;
112
- const responseBlock = [
113
- `${pipe}`,
114
- `${indent}response:`,
115
- timeLine,
116
- statusLine,
117
- sizeLine,
118
- ].join("\n");
113
+ const responseLines = [`${pipe}`, `${indent}response:`, timeLine, statusLine, sizeLine];
114
+ const head = label === undefined ? "" : `${label}\n`;
119
115
  if (body.data === undefined) {
120
- const head = label === undefined ? "" : `${label}\n`;
121
- return [`${head}${responseBlock}`];
116
+ return [`${head}${responseLines.join("\n")}`];
122
117
  }
123
118
  // Render the body using util.formatWithOptions so objects/arrays look like
124
119
  // console.log output — colors, proper nesting, no JSON.stringify quirkiness.
@@ -128,17 +123,7 @@ function buildBlock(label, ms, res, body, useColor) {
128
123
  const bodyBlock = bodyLines
129
124
  .map((line, i) => `${indent}${i === lastIdx ? last : branch} ${line}`)
130
125
  .join("\n");
131
- const full = [
132
- `${pipe}`,
133
- `${indent}response:`,
134
- timeLine,
135
- statusLine,
136
- sizeLine,
137
- `${pipe}`,
138
- `${indent}body:`,
139
- bodyBlock,
140
- ].join("\n");
141
- const head = label === undefined ? "" : `${label}\n`;
126
+ const full = [...responseLines, `${pipe}`, `${indent}body:`, bodyBlock].join("\n");
142
127
  return [`${head}${full}`];
143
128
  }
144
129
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meadown/logger",
3
- "version": "1.8.6",
3
+ "version": "1.8.8",
4
4
  "description": "A development-focused logger for Node.js and TypeScript — zero dependencies, clickable source links, and API response logging built in.",
5
5
  "keywords": [
6
6
  "logger",