@onezlinks/session-logger 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +648 -0
- package/package.json +52 -0
- package/src/cleanup.js +82 -0
- package/src/constants.js +74 -0
- package/src/context.js +343 -0
- package/src/file-logger.js +76 -0
- package/src/formatter.js +55 -0
- package/src/index.js +74 -0
- package/types/index.d.ts +208 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2026-02-07
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Hybrid API** — Wrapper pattern (`withSession`) + Manual pattern (`startSession`/`endSession`)
|
|
13
|
+
- **AsyncLocalStorage** context propagation — implicit session context without parameter drilling
|
|
14
|
+
- **Dual output** — simultaneous file + stdout logging
|
|
15
|
+
- **Category-based organization** — `logs/{category}/YYYY-MM-DD/{sessionId}_{timestamp}.log`
|
|
16
|
+
- **File logger factory** — `console.Console` with separate `.log` and `.err.log` streams
|
|
17
|
+
- **Cost/duration/metric formatters** — consistent structured output
|
|
18
|
+
- **Log rotation** — `cleanOldLogs()` with configurable retention period
|
|
19
|
+
- **Graceful fallback** — logs to console when outside session context or on file errors
|
|
20
|
+
- **Zero dependencies** — uses only Node.js built-in modules
|
|
21
|
+
- **TypeScript definitions** — full `.d.ts` type support
|
|
22
|
+
- **Environment variable configuration** — `SESSION_LOG_DIR`, `SESSION_LOG_RETENTION_DAYS`, etc.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Onez (onezlinks)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
# @onezlinks/session-logger
|
|
2
|
+
|
|
3
|
+
> 📁 Session-based file logger with **AsyncLocalStorage** context propagation for Node.js microservices.
|
|
4
|
+
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](package.json)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 📑 Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Features](#-features)
|
|
14
|
+
- [Installation](#-installation)
|
|
15
|
+
- [Quick Start](#-quick-start)
|
|
16
|
+
- [How It Works](#-how-it-works)
|
|
17
|
+
- [API Reference](#-api-reference)
|
|
18
|
+
- [Session Management](#session-management)
|
|
19
|
+
- [Logging Functions](#logging-functions)
|
|
20
|
+
- [Formatters](#formatters)
|
|
21
|
+
- [Utilities](#utilities)
|
|
22
|
+
- [Constants](#constants)
|
|
23
|
+
- [Log Output](#-log-output)
|
|
24
|
+
- [Directory Structure](#directory-structure)
|
|
25
|
+
- [File Naming Convention](#file-naming-convention)
|
|
26
|
+
- [Log File Content](#log-file-content)
|
|
27
|
+
- [Configuration](#%EF%B8%8F-configuration)
|
|
28
|
+
- [Log Rotation & Cleanup](#-log-rotation--cleanup)
|
|
29
|
+
- [Docker](#-docker)
|
|
30
|
+
- [TypeScript Support](#-typescript-support)
|
|
31
|
+
- [Testing](#-testing)
|
|
32
|
+
- [License](#-license)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## ✨ Features
|
|
37
|
+
|
|
38
|
+
| Feature | Description |
|
|
39
|
+
| ---------------------------- | -------------------------------------------------------------- |
|
|
40
|
+
| 🔄 **Hybrid API** | Wrapper (`withSession`) + Manual (`startSession`/`endSession`) |
|
|
41
|
+
| 🧵 **AsyncLocalStorage** | Implicit context propagation — no parameter drilling |
|
|
42
|
+
| 📝 **Dual Output** | Simultaneous file + stdout/stderr logging |
|
|
43
|
+
| 📂 **Category-based** | Logs organized into `{category}/YYYY-MM-DD/` subdirectories |
|
|
44
|
+
| 🧹 **Log Rotation** | `cleanOldLogs()` with configurable retention period |
|
|
45
|
+
| 💰 **Structured Formatters** | Cost, duration, and custom metric formatters |
|
|
46
|
+
| 🛡️ **Graceful Fallback** | Falls back to `console.*` when called outside a session |
|
|
47
|
+
| 📦 **Zero Dependencies** | Uses only Node.js built-in modules |
|
|
48
|
+
| 🔤 **TypeScript Support** | Full `.d.ts` type definitions included |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 📦 Installation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Published package (after npm publish)
|
|
56
|
+
npm install @onezlinks/session-logger
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For local development (monorepo or pre-publish):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Install from sibling directory
|
|
63
|
+
npm install ../session-logger
|
|
64
|
+
|
|
65
|
+
# Or use npm link
|
|
66
|
+
cd /path/to/session-logger && npm link
|
|
67
|
+
cd /path/to/your-service && npm link @onezlinks/session-logger
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## 🚀 Quick Start
|
|
73
|
+
|
|
74
|
+
### Wrapper Pattern (Recommended)
|
|
75
|
+
|
|
76
|
+
`withSession` creates a session context, runs your function, and automatically writes a summary footer and closes file streams when done.
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
const { withSession, log, logCost } = require("@onezlinks/session-logger");
|
|
80
|
+
|
|
81
|
+
async function processOrder(order) {
|
|
82
|
+
await withSession(
|
|
83
|
+
{
|
|
84
|
+
sessionId: order._id.toString(),
|
|
85
|
+
category: "order-processing",
|
|
86
|
+
metadata: { orderId: order._id, store: order.storeId },
|
|
87
|
+
},
|
|
88
|
+
async () => {
|
|
89
|
+
log("Pipeline", "🚀 Processing started");
|
|
90
|
+
|
|
91
|
+
// ... your business logic ...
|
|
92
|
+
// All log/error/warn calls within this async scope
|
|
93
|
+
// automatically write to the session log file.
|
|
94
|
+
|
|
95
|
+
logCost("Billing", { totalUSD: 0.001, totalTHB: 0.035 }, 100, 50);
|
|
96
|
+
log("Pipeline", "✅ Processing complete");
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Manual Pattern
|
|
103
|
+
|
|
104
|
+
Use `startSession` / `endSession` when you need finer control over the session lifecycle (e.g., event handlers, iterative flows). You **must** call `endSession()` in a `finally` block — otherwise the file streams will leak.
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const {
|
|
108
|
+
startSession,
|
|
109
|
+
endSession,
|
|
110
|
+
log,
|
|
111
|
+
error,
|
|
112
|
+
} = require("@onezlinks/session-logger");
|
|
113
|
+
|
|
114
|
+
async function handleWebhook(data) {
|
|
115
|
+
startSession({
|
|
116
|
+
sessionId: data.id,
|
|
117
|
+
category: "webhook",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
log("Webhook", "Received payload");
|
|
122
|
+
// ... processing ...
|
|
123
|
+
} catch (err) {
|
|
124
|
+
error("Webhook", `Failed: ${err.message}`);
|
|
125
|
+
throw err;
|
|
126
|
+
} finally {
|
|
127
|
+
await endSession(); // ⚠️ MUST be in finally block
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## 🔍 How It Works
|
|
135
|
+
|
|
136
|
+
```text
|
|
137
|
+
┌───────────────────────────────────────────────────────────┐
|
|
138
|
+
│ withSession(options, asyncFn) │
|
|
139
|
+
│ ┌─────────────────────────────────────────────────────┐ │
|
|
140
|
+
│ │ 1. Create date folder logs/{category}/YYYY-MM-DD │ │
|
|
141
|
+
│ │ 2. Open write streams .log + .err.log │ │
|
|
142
|
+
│ │ 3. Write session header │ │
|
|
143
|
+
│ │ 4. Store context in AsyncLocalStorage │ │
|
|
144
|
+
│ │ 5. Execute asyncFn() │ │
|
|
145
|
+
│ │ ┌──────────────────────────────────────────┐ │ │
|
|
146
|
+
│ │ │ log() → .log file + stdout │ │ │
|
|
147
|
+
│ │ │ warn() → .log file + stdout │ │ │
|
|
148
|
+
│ │ │ error() → .err.log file + stderr │ │ │
|
|
149
|
+
│ │ └──────────────────────────────────────────┘ │ │
|
|
150
|
+
│ │ 6. Write session summary (always "SUCCESS") │ │
|
|
151
|
+
│ │ 7. Close file streams │ │
|
|
152
|
+
│ └─────────────────────────────────────────────────────┘ │
|
|
153
|
+
└───────────────────────────────────────────────────────────┘
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**Key behaviors:**
|
|
157
|
+
|
|
158
|
+
- `log()` and `warn()` write to the `.log` file via `stdout` stream of `console.Console`.
|
|
159
|
+
- `error()` writes to a **separate** `.err.log` file via `stderr` stream.
|
|
160
|
+
- When called **outside** a session context, all functions fall back to the global `console` object (`console.log`, `console.error`, `console.warn`).
|
|
161
|
+
- The session summary always records status `SUCCESS`. Error tracking is available through `stats.errors` count.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 📖 API Reference
|
|
166
|
+
|
|
167
|
+
### Session Management
|
|
168
|
+
|
|
169
|
+
#### `withSession(options, asyncFn) → Promise<T>`
|
|
170
|
+
|
|
171
|
+
Run an async function within a session context. The session is automatically finalized in a `finally` block, ensuring streams are always closed — even if `asyncFn` throws.
|
|
172
|
+
|
|
173
|
+
| Parameter | Type | Required | Default | Description |
|
|
174
|
+
| ---------------------- | ------------------ | -------- | ---------------------- | -------------------------------------------- |
|
|
175
|
+
| `options.sessionId` | `string` | ✅ | — | Unique session identifier |
|
|
176
|
+
| `options.category` | `string` | ✅ | — | Log category — used as subdirectory name |
|
|
177
|
+
| `options.metadata` | `object` | ❌ | `{}` | Key-value pairs added to the session header |
|
|
178
|
+
| `options.logDir` | `string` | ❌ | `CONFIG.LOG_BASE_DIR` | Override base log directory |
|
|
179
|
+
| `options.enableFile` | `boolean` | ❌ | `CONFIG.ENABLE_FILE` | Enable/disable file logging |
|
|
180
|
+
| `options.enableStdout` | `boolean` | ❌ | `CONFIG.ENABLE_STDOUT` | Enable/disable stdout/stderr output |
|
|
181
|
+
| `asyncFn` | `() => Promise<T>` | ✅ | — | Async function to execute within the session |
|
|
182
|
+
|
|
183
|
+
**Returns:** The return value of `asyncFn`.
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
const result = await withSession(
|
|
187
|
+
{ sessionId: "abc123", category: "ai-pipeline" },
|
|
188
|
+
async () => {
|
|
189
|
+
log("Pipeline", "Starting...");
|
|
190
|
+
return { success: true };
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
// result === { success: true }
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
#### `startSession(options) → void`
|
|
199
|
+
|
|
200
|
+
Start a session context using the manual pattern. Accepts the **same options** as `withSession` (without `asyncFn`).
|
|
201
|
+
|
|
202
|
+
> ⚠️ You **must** call `endSession()` in a `finally` block. Failing to do so will leave file streams open (resource leak).
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
#### `endSession() → Promise<void>`
|
|
207
|
+
|
|
208
|
+
End the current session — writes the summary footer and closes file streams. If called outside a session context, this is a no-op.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
#### `getSessionContext() → SessionContext | null`
|
|
213
|
+
|
|
214
|
+
Returns the current session context object, or `null` if not inside a session. Useful for conditional logic based on session state.
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
const ctx = getSessionContext();
|
|
218
|
+
if (ctx) {
|
|
219
|
+
console.log(`Session ${ctx.sessionId} has ${ctx.stats.errors} errors`);
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**`SessionContext` properties:**
|
|
224
|
+
|
|
225
|
+
| Property | Type | Description |
|
|
226
|
+
| ------------ | ---------------- | ---------------------------------------------------- |
|
|
227
|
+
| `sessionId` | `string` | The session identifier |
|
|
228
|
+
| `category` | `string` | The log category |
|
|
229
|
+
| `metadata` | `object` | Metadata from options |
|
|
230
|
+
| `logPath` | `string \| null` | Absolute path to the `.log` file |
|
|
231
|
+
| `errLogPath` | `string \| null` | Absolute path to the `.err.log` file |
|
|
232
|
+
| `startTime` | `number` | `Date.now()` when the session started |
|
|
233
|
+
| `stats` | `object` | `{ logs: number, errors: number, warnings: number }` |
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
### Logging Functions
|
|
238
|
+
|
|
239
|
+
All logging functions follow this signature:
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
functionName(tag, message, ...args);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
- **`tag`** — A short label for the source module/component (padded to 12 characters in output).
|
|
246
|
+
- **`message`** — The log message string.
|
|
247
|
+
- **`...args`** — Additional values (objects, arrays, etc.) appended after the message.
|
|
248
|
+
|
|
249
|
+
**Output behavior:**
|
|
250
|
+
|
|
251
|
+
| Context | `enableStdout` | File output | Console output |
|
|
252
|
+
| --------------- | -------------- | ----------- | ---------------------------- |
|
|
253
|
+
| Inside session | `true` | ✅ Writes | ✅ Writes |
|
|
254
|
+
| Inside session | `false` | ✅ Writes | ❌ Suppressed |
|
|
255
|
+
| Outside session | — | ❌ No file | ✅ Falls back to `console.*` |
|
|
256
|
+
|
|
257
|
+
**Output format:**
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
[HH:mm:ss.SSS] [Tag ] message
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
#### `log(tag, message, ...args)`
|
|
266
|
+
|
|
267
|
+
Log an info-level message. Writes to the `.log` file and `console.log`.
|
|
268
|
+
|
|
269
|
+
```javascript
|
|
270
|
+
log("Pipeline", "🚀 Processing started");
|
|
271
|
+
log("OCR", "Result:", { confidence: 0.95 });
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
#### `info(tag, message, ...args)`
|
|
275
|
+
|
|
276
|
+
Alias for `log()`. Use whichever reads better in your code.
|
|
277
|
+
|
|
278
|
+
#### `warn(tag, message, ...args)`
|
|
279
|
+
|
|
280
|
+
Log a warning message. Writes to the `.log` file (same as `log`) and `console.warn`. Increments `stats.warnings`.
|
|
281
|
+
|
|
282
|
+
#### `error(tag, message, ...args)`
|
|
283
|
+
|
|
284
|
+
Log an error message. Writes to a **separate `.err.log` file** (not the main `.log`) and `console.error`. Increments `stats.errors`.
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
### Formatters
|
|
289
|
+
|
|
290
|
+
Convenience functions that format data into a consistent string and log it via `log()`.
|
|
291
|
+
|
|
292
|
+
#### `logCost(tag, costData, inputUnits?, outputUnits?, cachedUnits?)`
|
|
293
|
+
|
|
294
|
+
Log cost/billing data.
|
|
295
|
+
|
|
296
|
+
| Parameter | Type | Default | Description |
|
|
297
|
+
| ------------- | -------- | ------- | ---------------------------------------- |
|
|
298
|
+
| `tag` | `string` | — | Module/component tag |
|
|
299
|
+
| `costData` | `object` | — | `{ totalUSD: number, totalTHB: number }` |
|
|
300
|
+
| `inputUnits` | `number` | `0` | Input units (tokens, API calls) |
|
|
301
|
+
| `outputUnits` | `number` | `0` | Output units |
|
|
302
|
+
| `cachedUnits` | `number` | `0` | Cached units |
|
|
303
|
+
|
|
304
|
+
```javascript
|
|
305
|
+
logCost("Quality", { totalUSD: 0.001, totalTHB: 0.035 }, 100, 50);
|
|
306
|
+
// → [12:00:00.500] [Quality ] 💰 Cost: $0.001000 (~฿0.0350) | Units: 100+50
|
|
307
|
+
|
|
308
|
+
logCost("Quality", { totalUSD: 0.002, totalTHB: 0.07 }, 200, 80, 50);
|
|
309
|
+
// → [12:00:00.500] [Quality ] 💰 Cost: $0.002000 (~฿0.0700) | Units: 200+80 (cached: 50)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
#### `logDuration(tag, durationMs, label?)`
|
|
313
|
+
|
|
314
|
+
Log a duration metric.
|
|
315
|
+
|
|
316
|
+
| Parameter | Type | Default | Description |
|
|
317
|
+
| ------------ | -------- | ------------ | ------------------------ |
|
|
318
|
+
| `tag` | `string` | — | Module/component tag |
|
|
319
|
+
| `durationMs` | `number` | — | Duration in milliseconds |
|
|
320
|
+
| `label` | `string` | `'Duration'` | Metric label |
|
|
321
|
+
|
|
322
|
+
```javascript
|
|
323
|
+
logDuration("Pipeline", 500);
|
|
324
|
+
// → [12:00:01.200] [Pipeline ] ⏱️ Duration: 500ms
|
|
325
|
+
|
|
326
|
+
logDuration("Pipeline", 2500);
|
|
327
|
+
// → [12:00:01.200] [Pipeline ] ⏱️ Duration: 2500ms (2.50s)
|
|
328
|
+
|
|
329
|
+
logDuration("OCR", 3200, "OCR Time");
|
|
330
|
+
// → [12:00:01.200] [OCR ] ⏱️ OCR Time: 3200ms (3.20s)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
> **Note:** The seconds conversion `(X.XXs)` only appears when the duration is ≥ 1000ms.
|
|
334
|
+
|
|
335
|
+
#### `logMetric(tag, metricName, value, unit?)`
|
|
336
|
+
|
|
337
|
+
Log a custom metric.
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
logMetric("OCR", "Confidence", 95.5, "%");
|
|
341
|
+
// → [12:00:01.200] [OCR ] Confidence: 95.5 %
|
|
342
|
+
|
|
343
|
+
logMetric("Pipeline", "Items Processed", 42);
|
|
344
|
+
// → [12:00:01.200] [Pipeline ] Items Processed: 42
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
### Utilities
|
|
350
|
+
|
|
351
|
+
#### `cleanOldLogs(category?, daysToKeep?) → Promise<CleanupResult>`
|
|
352
|
+
|
|
353
|
+
Remove date-based log folders older than the retention period. Scans `{LOG_BASE_DIR}/{category}/YYYY-MM-DD/` and removes folders where the date is older than `daysToKeep`.
|
|
354
|
+
|
|
355
|
+
| Parameter | Type | Default | Description |
|
|
356
|
+
| ------------ | -------- | --------------------------- | -------------------------- |
|
|
357
|
+
| `category` | `string` | all categories | Specific category to clean |
|
|
358
|
+
| `daysToKeep` | `number` | `CONFIG.LOG_RETENTION_DAYS` | Number of days to retain |
|
|
359
|
+
|
|
360
|
+
**Returns:** `{ deleted: string[], errors: string[] }`
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
const { cleanOldLogs } = require("@onezlinks/session-logger");
|
|
364
|
+
|
|
365
|
+
// Clean all categories using default retention (30 days)
|
|
366
|
+
const result = await cleanOldLogs();
|
|
367
|
+
console.log(`Deleted ${result.deleted.length} folders`);
|
|
368
|
+
// deleted: ['ai-pipeline/2026-01-01', 'webhook/2026-01-02', ...]
|
|
369
|
+
|
|
370
|
+
// Clean a specific category, keep only last 7 days
|
|
371
|
+
const result = await cleanOldLogs("ai-pipeline", 7);
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
If the base log directory does not exist, returns `{ deleted: [], errors: [] }` without throwing.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
### Constants
|
|
379
|
+
|
|
380
|
+
#### `CONFIG`
|
|
381
|
+
|
|
382
|
+
Configuration object with **lazy evaluation** — environment variables are read at access time (not at import time), making them testable and overridable at runtime.
|
|
383
|
+
|
|
384
|
+
| Property | Env Variable | Default | Description |
|
|
385
|
+
| -------------------- | ---------------------------- | -------- | ------------------------------------------------ |
|
|
386
|
+
| `LOG_BASE_DIR` | `SESSION_LOG_DIR` | `'logs'` | Base directory for log files (relative to `cwd`) |
|
|
387
|
+
| `LOG_RETENTION_DAYS` | `SESSION_LOG_RETENTION_DAYS` | `30` | Days to retain logs before cleanup |
|
|
388
|
+
| `ENABLE_FILE` | `SESSION_LOG_TO_FILE` | `true` | Set to `'false'` to disable file logging |
|
|
389
|
+
| `ENABLE_STDOUT` | `SESSION_LOG_TO_STDOUT` | `true` | Set to `'false'` to disable stdout/stderr output |
|
|
390
|
+
|
|
391
|
+
> **Note:** `ENABLE_FILE` and `ENABLE_STDOUT` are only disabled when the env var is **exactly** `'false'` (string). Any other value (including unset) keeps them enabled.
|
|
392
|
+
|
|
393
|
+
#### `COMMON_TAGS`
|
|
394
|
+
|
|
395
|
+
Predefined tag string constants for consistency across services. These are suggestions — you can use any string as a tag.
|
|
396
|
+
|
|
397
|
+
| Group | Tags |
|
|
398
|
+
| --------------- | ---------------------------------------------------- |
|
|
399
|
+
| **AI Pipeline** | `PIPELINE`, `QUALITY`, `OCR`, `OPENAI`, `CLIENT` |
|
|
400
|
+
| **Rewards** | `REDEMPTION`, `POINTS`, `VALIDATION`, `NOTIFICATION` |
|
|
401
|
+
| **Survey** | `SURVEY`, `STORAGE`, `ANALYTICS` |
|
|
402
|
+
| **Order** | `ORDER`, `PAYMENT`, `INVENTORY`, `SHIPPING` |
|
|
403
|
+
| **Messaging** | `MESSAGE`, `WEBHOOK`, `RESPONSE`, `BROADCAST` |
|
|
404
|
+
| **Generic** | `DATABASE`, `CACHE`, `WORKER` |
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
const { COMMON_TAGS, log } = require("@onezlinks/session-logger");
|
|
408
|
+
|
|
409
|
+
log(COMMON_TAGS.PIPELINE, "Processing started"); // tag = 'Pipeline'
|
|
410
|
+
log(COMMON_TAGS.OCR, "Recognition complete"); // tag = 'OCR'
|
|
411
|
+
log(COMMON_TAGS.DATABASE, "Query executed"); // tag = 'Database'
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## 📂 Log Output
|
|
417
|
+
|
|
418
|
+
### Directory Structure
|
|
419
|
+
|
|
420
|
+
```text
|
|
421
|
+
logs/ ← CONFIG.LOG_BASE_DIR
|
|
422
|
+
├── ai-pipeline/ ← category
|
|
423
|
+
│ └── 2026-02-07/ ← date folder (YYYY-MM-DD)
|
|
424
|
+
│ ├── 6789abc_20260207T120000_001.log
|
|
425
|
+
│ └── 6789abc_20260207T120000_001.err.log
|
|
426
|
+
└── order-processing/
|
|
427
|
+
└── 2026-02-07/
|
|
428
|
+
├── def5678_20260207T130000_042.log
|
|
429
|
+
└── def5678_20260207T130000_042.err.log
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### File Naming Convention
|
|
433
|
+
|
|
434
|
+
Each session creates a unique filename:
|
|
435
|
+
|
|
436
|
+
```text
|
|
437
|
+
{sessionId7}_{timestamp}_{random}.log
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
| Component | Description | Example |
|
|
441
|
+
| ------------ | ---------------------------------------------- | ----------------- |
|
|
442
|
+
| `sessionId7` | First 7 characters of `sessionId` | `6789abc` |
|
|
443
|
+
| `timestamp` | ISO 8601 compact format (no dashes, no colons) | `20260207T120000` |
|
|
444
|
+
| `random` | 3-digit random number (avoids collisions) | `001` |
|
|
445
|
+
|
|
446
|
+
This produces filenames like `6789abc_20260207T120000_001.log` and a matching `.err.log`.
|
|
447
|
+
|
|
448
|
+
### Log File Content
|
|
449
|
+
|
|
450
|
+
```text
|
|
451
|
+
═══════════════════════════════════════════════════════════════
|
|
452
|
+
Ai Pipeline Session
|
|
453
|
+
═══════════════════════════════════════════════════════════════
|
|
454
|
+
Session ID : 6789abc...
|
|
455
|
+
Category : ai-pipeline
|
|
456
|
+
Order Id : order-001
|
|
457
|
+
Started : 2026-02-07T12:00:00.000Z
|
|
458
|
+
═══════════════════════════════════════════════════════════════
|
|
459
|
+
|
|
460
|
+
[12:00:00.001] [Pipeline ] 🚀 Processing started
|
|
461
|
+
[12:00:00.500] [Quality ] 💰 Cost: $0.001000 (~฿0.0350) | Units: 100+50
|
|
462
|
+
[12:00:01.200] [Pipeline ] ✅ Processing complete
|
|
463
|
+
|
|
464
|
+
═══════════════════════════════════════════════════════════════
|
|
465
|
+
Session Summary
|
|
466
|
+
═══════════════════════════════════════════════════════════════
|
|
467
|
+
Status : SUCCESS
|
|
468
|
+
Duration : 1200ms (1.20s)
|
|
469
|
+
Log Stats : 3 info, 0 errors, 0 warnings
|
|
470
|
+
═══════════════════════════════════════════════════════════════
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Notes:**
|
|
474
|
+
|
|
475
|
+
- The **session title** is derived from the `category` by converting `kebab-case` to `Title Case` (e.g., `ai-pipeline` → `Ai Pipeline Session`).
|
|
476
|
+
- **Metadata keys** are formatted from `camelCase` to `Title Case` (e.g., `orderId` → `Order Id`).
|
|
477
|
+
- The **summary status** is always `SUCCESS`. To detect errors, check the `stats.errors` count via `getSessionContext()` or inspect the `.err.log` file.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## ⚙️ Configuration
|
|
482
|
+
|
|
483
|
+
All configuration is through **environment variables**. No config files needed.
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
# Change log directory (default: 'logs')
|
|
487
|
+
SESSION_LOG_DIR=./output/logs
|
|
488
|
+
|
|
489
|
+
# Change retention period (default: 30 days)
|
|
490
|
+
SESSION_LOG_RETENTION_DAYS=14
|
|
491
|
+
|
|
492
|
+
# Disable file logging (only stdout)
|
|
493
|
+
SESSION_LOG_TO_FILE=false
|
|
494
|
+
|
|
495
|
+
# Disable stdout (only file logging — useful in production)
|
|
496
|
+
SESSION_LOG_TO_STDOUT=false
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
You can also override per-session:
|
|
500
|
+
|
|
501
|
+
```javascript
|
|
502
|
+
await withSession(
|
|
503
|
+
{
|
|
504
|
+
sessionId: "test-123",
|
|
505
|
+
category: "debug",
|
|
506
|
+
logDir: "./debug-logs", // Override LOG_BASE_DIR for this session
|
|
507
|
+
enableFile: false, // Skip file logging for this session
|
|
508
|
+
enableStdout: true, // Still print to console
|
|
509
|
+
},
|
|
510
|
+
async () => {
|
|
511
|
+
log("Debug", "This only prints to console, no file created");
|
|
512
|
+
},
|
|
513
|
+
);
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
---
|
|
517
|
+
|
|
518
|
+
## 🧹 Log Rotation & Cleanup
|
|
519
|
+
|
|
520
|
+
The library provides a `cleanOldLogs()` function that removes date folders older than the retention period. **Scheduling is your responsibility** — the library intentionally does not include a built-in scheduler to remain zero-dependency.
|
|
521
|
+
|
|
522
|
+
### Startup Cleanup (Simplest)
|
|
523
|
+
|
|
524
|
+
```javascript
|
|
525
|
+
const { cleanOldLogs } = require("@onezlinks/session-logger");
|
|
526
|
+
|
|
527
|
+
// Run once at application startup
|
|
528
|
+
cleanOldLogs().then(({ deleted, errors }) => {
|
|
529
|
+
if (deleted.length)
|
|
530
|
+
console.log(`[Cleanup] Removed ${deleted.length} old log folders`);
|
|
531
|
+
if (errors.length) console.error("[Cleanup] Errors:", errors);
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Scheduled Cleanup with `node-cron`
|
|
536
|
+
|
|
537
|
+
```bash
|
|
538
|
+
npm install node-cron
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
const cron = require("node-cron");
|
|
543
|
+
const { cleanOldLogs } = require("@onezlinks/session-logger");
|
|
544
|
+
|
|
545
|
+
// Run daily at 3:00 AM
|
|
546
|
+
cron.schedule("0 3 * * *", async () => {
|
|
547
|
+
const { deleted, errors } = await cleanOldLogs();
|
|
548
|
+
if (deleted.length)
|
|
549
|
+
console.log(`[Cron] Deleted ${deleted.length} old log folders`);
|
|
550
|
+
if (errors.length) console.error("[Cron] Cleanup errors:", errors);
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Scheduled Cleanup with `setInterval` (No Extra Dependencies)
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
const { cleanOldLogs } = require("@onezlinks/session-logger");
|
|
558
|
+
|
|
559
|
+
// Run every 24 hours
|
|
560
|
+
setInterval(
|
|
561
|
+
async () => {
|
|
562
|
+
const { deleted } = await cleanOldLogs();
|
|
563
|
+
if (deleted.length)
|
|
564
|
+
console.log(`[Cleanup] Deleted ${deleted.length} folders`);
|
|
565
|
+
},
|
|
566
|
+
24 * 60 * 60 * 1000,
|
|
567
|
+
);
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Cluster-Safe Pattern (PM2 / Docker Swarm)
|
|
571
|
+
|
|
572
|
+
When running multiple instances, schedule cleanup on **one instance only**:
|
|
573
|
+
|
|
574
|
+
```javascript
|
|
575
|
+
const cluster = require("cluster");
|
|
576
|
+
const cron = require("node-cron");
|
|
577
|
+
const { cleanOldLogs } = require("@onezlinks/session-logger");
|
|
578
|
+
|
|
579
|
+
if (cluster.isPrimary || process.env.NODE_APP_INSTANCE === "0") {
|
|
580
|
+
cron.schedule("0 3 * * *", () => cleanOldLogs());
|
|
581
|
+
}
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Cron Schedule Reference
|
|
585
|
+
|
|
586
|
+
| Pattern | Description |
|
|
587
|
+
| ------------- | ----------------------------- |
|
|
588
|
+
| `0 3 * * *` | Every day at 3:00 AM |
|
|
589
|
+
| `0 */6 * * *` | Every 6 hours |
|
|
590
|
+
| `0 0 * * 0` | Every Sunday at midnight |
|
|
591
|
+
| `0 2 1 * *` | First day of month at 2:00 AM |
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
## 🐳 Docker
|
|
596
|
+
|
|
597
|
+
Add a volume mount to persist logs outside the container:
|
|
598
|
+
|
|
599
|
+
```yaml
|
|
600
|
+
services:
|
|
601
|
+
your-service:
|
|
602
|
+
volumes:
|
|
603
|
+
- ./logs:/app/logs
|
|
604
|
+
environment:
|
|
605
|
+
- SESSION_LOG_DIR=logs # relative to /app (working dir)
|
|
606
|
+
- SESSION_LOG_RETENTION_DAYS=14
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
> Ensure the `SESSION_LOG_DIR` path matches the volume mount target. The default value `logs` works with `/app/logs` when the working directory is `/app`.
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## 🔤 TypeScript Support
|
|
614
|
+
|
|
615
|
+
Full type definitions are included at `types/index.d.ts`. No additional `@types/` package is needed.
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
import {
|
|
619
|
+
withSession,
|
|
620
|
+
log,
|
|
621
|
+
error,
|
|
622
|
+
logCost,
|
|
623
|
+
cleanOldLogs,
|
|
624
|
+
CONFIG,
|
|
625
|
+
COMMON_TAGS,
|
|
626
|
+
type SessionOptions,
|
|
627
|
+
type SessionContext,
|
|
628
|
+
type CostData,
|
|
629
|
+
type CleanupResult,
|
|
630
|
+
} from "@onezlinks/session-logger";
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## 🧪 Testing
|
|
636
|
+
|
|
637
|
+
```bash
|
|
638
|
+
npm test # Run all tests with coverage
|
|
639
|
+
npm run test:watch # Watch mode for development
|
|
640
|
+
npm run lint # ESLint check
|
|
641
|
+
npm run lint:fix # ESLint auto-fix
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## 📄 License
|
|
647
|
+
|
|
648
|
+
[MIT](LICENSE) © Onez (onezlinks)
|