logroller 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/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vinau
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy...
package/README.md ADDED
@@ -0,0 +1,147 @@
1
+ # logroller
2
+
3
+ Zero-dependency rotating file stream for Node.js. Daily or interval rotation,
4
+ size limits, gzip archives, retention policies, timezone-aware naming and a
5
+ crash-safe resume — in one small Writable stream you can `pipe()` into or hand
6
+ to pino.
7
+
8
+ ```bash
9
+ npm install logroller
10
+ ```
11
+
12
+ ## Quick start
13
+
14
+ ### ESM
15
+
16
+ ```js
17
+ import { createStream } from "logroller";
18
+
19
+ const log = createStream({
20
+ filename: "logs/app-%DATE%.log",
21
+ datePattern: "YYYY-MM-DD",
22
+ zippedArchive: true,
23
+ maxSize: "20m",
24
+ maxFiles: "90d",
25
+ tz: "UTC",
26
+ frequency: "daily",
27
+ });
28
+
29
+ log.write("hello\n");
30
+ log.on("rotated", (r) => console.log("rotated:", r));
31
+ ```
32
+
33
+ ### CommonJS
34
+
35
+ ```js
36
+ const { createStream } = require("logroller");
37
+ ```
38
+
39
+ ### Pipe anything into it
40
+
41
+ ```js
42
+ source.pipe(createStream({ filename: "logs/data-%DATE%.log" }));
43
+ ```
44
+
45
+ ### With pino
46
+
47
+ ```js
48
+ import pino from "pino";
49
+ import { createStream } from "logroller";
50
+
51
+ const stream = createStream({
52
+ filename: "logs/general-%DATE%.log",
53
+ zippedArchive: true,
54
+ maxSize: "20m",
55
+ maxFiles: "90d",
56
+ tz: "Asia/Kolkata",
57
+ });
58
+
59
+ const logger = pino(pino.multistream([
60
+ { level: "info", stream: pino.transport({ target: "./pretty.js" }) },
61
+ { level: "info", stream },
62
+ ]));
63
+ ```
64
+
65
+ ## Options
66
+
67
+ | Option | Values | Default |
68
+ |---|---|---|
69
+ | `filename` | must contain `%DATE%` | required |
70
+ | `datePattern` | `YYYY YY MM DD HH mm ss` | `YYYY-MM-DD` |
71
+ | `zippedArchive` | boolean | `false` |
72
+ | `maxSize` | `20m`, `1k`, `5g`, number of bytes, `0` = off | off |
73
+ | `maxFiles` | `"90d"` (age), `500` (count), unset = never delete | off |
74
+ | `frequency` | `"daily"`, `"1s"`, `"5m"`, `"2h"`, ms number | `"daily"` |
75
+ | `tz` | any IANA zone (`Intl`-based) | `"UTC"` |
76
+ | `audit` | write `<stem>_audit.json` manifest | `true` |
77
+ | `auditFile` | custom manifest name | `<stem>_audit.json` |
78
+ | `unrefTimers` | don't hold the process open | `false` |
79
+ | `addHours` | static clock shift (`"-6h"`) — **test only** | off |
80
+ | `addHoursEveryMin` | hours added per step (`"24h"`) — **test only** | off |
81
+ | `clockStepIntervalMs` | step interval for `addHoursEveryMin` | `60000` |
82
+
83
+ ## File naming
84
+
85
+ ```
86
+ app-2026-08-27.log ← active
87
+ app-2026-08-27.log.gz ← hit maxSize → gzipped
88
+ app-2026-08-27.1.log ← next segment, writing…
89
+ app-2026-08-27.1.log.gz ← that one sealed too
90
+ app-2026-08-28.log ← new day (in tz), fresh series
91
+ ```
92
+
93
+ ## Restart behaviour
94
+
95
+ | Disk at crash | Resumes into |
96
+ |---|---|
97
+ | `app-D.log` | `app-D.log` (append) |
98
+ | `app-D.log.gz` + `app-D.1.log` | `app-D.1.log` (append) |
99
+ | `app-D.1.log.gz` only | `app-D.2.log` |
100
+ | crossed midnight while down | new day base file on first write |
101
+ | killed mid-gzip | boot finishes the interrupted gzip |
102
+
103
+ ## Events
104
+
105
+ | Event | Payload |
106
+ |---|---|
107
+ | `open` | `(file: string)` |
108
+ | `rotated` | `({ reason, oldFile, archive, newFile })` |
109
+ | `archive` | `(gzFile: string)` |
110
+ | `deleted` | `(file: string)` — retention |
111
+ | `period` | `(stamp: string)` — new period, nothing to archive |
112
+ | `clock` | `({ addHours, addHoursEveryMin, offsetMs })` — test clock active |
113
+ | `warn` | `(err: Error)` — non-fatal (gzip failure, concurrent writer, …) |
114
+
115
+ Always attach an `error` handler — this is a real Writable stream.
116
+
117
+ ## Audit manifest
118
+
119
+ Each stream family keeps a `*_audit.json` beside the logs (library-owned —
120
+ treat it as read-only). It records every file's lifecycle with a capped,
121
+ uuid-tagged event journal, restores the series index across restarts, repairs
122
+ gzip interrupted by a crash, and demotes records left behind by dead
123
+ processes. Writes are atomic (tmp + rename); a corrupt/foreign file is
124
+ quarantined as `*.corrupt` and logging continues via disk scan.
125
+
126
+ ## Testing rotation without waiting
127
+
128
+ ```js
129
+ const s = createStream({
130
+ filename: "logs/test-%DATE%.log",
131
+ zippedArchive: true,
132
+ addHoursEveryMin: "24h", // a day passes every minute
133
+ clockStepIntervalMs: 1000, // optional: step every second instead
134
+ tz: "UTC",
135
+ });
136
+ await s.advanceClock(); // or trigger one step manually
137
+ ```
138
+
139
+ ## Retention
140
+
141
+ - `maxFiles: "90d"` — delete family files older than 90 days (mtime)
142
+ - `maxFiles: 500` — keep newest 500 archives
143
+ - unset — never auto-delete; remove manually
144
+
145
+ ## License
146
+
147
+ MIT