@stonyx/logs 1.0.1-beta.8 → 1.0.1-beta.9
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 +35 -0
- package/package.json +1 -1
- package/src/index.js +30 -1
package/README.md
CHANGED
|
@@ -201,6 +201,7 @@ const chronicle = new Chronicle({
|
|
|
201
201
|
path: 'logs/',
|
|
202
202
|
prefix: '',
|
|
203
203
|
suffix: '',
|
|
204
|
+
filename: '',
|
|
204
205
|
additionalLogs: {},
|
|
205
206
|
systemLogs: {
|
|
206
207
|
info: 'cyan',
|
|
@@ -217,6 +218,7 @@ const chronicle = new Chronicle({
|
|
|
217
218
|
| `path` | **String** | *'logs/'* | Path in which to store log files. This setting is relative to your project's root directory. |
|
|
218
219
|
| `prefix` | **String** | *''* | Prefix string to prepend all log messages for all log types with the exception of *debug*. |
|
|
219
220
|
| `suffix` | **String** | *''* | Suffix string to tack on to all log messages for all log types with the exception of *debug*. |
|
|
221
|
+
| `filename` | **String** | *''* | Template for log file names with variable support. Defaults to `{type}.log` when empty. See [dynamic file names](#dynamic-file-names). |
|
|
220
222
|
| `additionalLogs` | **Object** | | Key value pair object containing log type to color setting for logs that will be merged with `systemLogs` |
|
|
221
223
|
| `systemLogs` | **Object** | | Key value pair object containing log type to color setting for main **Chronicle** logs available in application |
|
|
222
224
|
|
|
@@ -270,6 +272,39 @@ chronicle.notice('This new log is the hex "#c0c0c0" share of gray');
|
|
|
270
272
|
|
|
271
273
|
`defineType()` can also be used as an alternative to populating the `additionalLogs` setting in the constructor, as if the setting doesn't already exist, it will then be created.
|
|
272
274
|
|
|
275
|
+
### Dynamic File Names
|
|
276
|
+
|
|
277
|
+
The `filename` option supports template variables that are resolved at write-time, allowing each log type to produce uniquely named files.
|
|
278
|
+
|
|
279
|
+
#### Supported Variables
|
|
280
|
+
|
|
281
|
+
| Variable | Resolves To | Example Output |
|
|
282
|
+
| :---: | :--- | :--- |
|
|
283
|
+
| `{date}` | Current date in YYYY-MM-DD format | `2026-04-04` |
|
|
284
|
+
| `{type}` | Log type name | `error` |
|
|
285
|
+
| `{pid}` | Current process ID | `12345` |
|
|
286
|
+
| `{hostname}` | Machine hostname | `my-server` |
|
|
287
|
+
|
|
288
|
+
#### Examples
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
// Per-type filename via defineType
|
|
292
|
+
chronicle.defineType('error', 'red', { filename: 'error-{date}.log' });
|
|
293
|
+
// writes to: logs/error-2026-04-04.log
|
|
294
|
+
|
|
295
|
+
// Per-type filename with multiple variables
|
|
296
|
+
chronicle.defineType('info', 'cyan', { filename: '{type}-{hostname}-{date}.log' });
|
|
297
|
+
// writes to: logs/info-my-server-2026-04-04.log
|
|
298
|
+
|
|
299
|
+
// Global filename template via constructor
|
|
300
|
+
const chronicle = new Chronicle({ filename: '{type}-{date}.log' });
|
|
301
|
+
// all types write to: logs/<type>-2026-04-04.log
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
When no `filename` is configured, the default behavior of `{type}.log` is preserved for full backward compatibility.
|
|
305
|
+
|
|
306
|
+
Path traversal characters (`..`, `/`, `\`) are automatically stripped from resolved file names for security.
|
|
307
|
+
|
|
273
308
|
## Origin
|
|
274
309
|
|
|
275
310
|
As a team of developers who are constantly working on side projects, we often litter our codebase with TODOs to refactor convenience utils such as **chronicle** into classes of their own, or projects of their own. This usually turns into internal tech debt that never gets addressed. Furthermore, we also often find ourselves going the *copy -> paste -> modify* route of previously written useful logic, which saves us time in new projects, but not as much as it would if all we had to do was run an `npm install` instead.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, promises as fsp } from 'fs';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
|
+
import { hostname } from 'os';
|
|
3
4
|
import projectPath from 'path';
|
|
4
5
|
import Color from './color.js';
|
|
5
6
|
|
|
@@ -9,6 +10,7 @@ const defaultOptions = {
|
|
|
9
10
|
path: 'logs/', // default log directory (relative to main project root directory)
|
|
10
11
|
prefix: '',
|
|
11
12
|
suffix: '',
|
|
13
|
+
filename: '', // template for log file name (e.g. 'error-{date}.log'), defaults to '{type}.log'
|
|
12
14
|
|
|
13
15
|
// log types with corresponding color settings
|
|
14
16
|
additionalLogs: {},
|
|
@@ -129,7 +131,9 @@ export default class Chronicle {
|
|
|
129
131
|
|
|
130
132
|
async writeToFile(type, content, overwrite) {
|
|
131
133
|
const path = this.getOptionForType(type, 'path');
|
|
132
|
-
const
|
|
134
|
+
const filenameTemplate = this.getOptionForType(type, 'filename');
|
|
135
|
+
const resolvedName = this.resolveFilename(filenameTemplate, type);
|
|
136
|
+
const targetLog = `${path}${resolvedName}`;
|
|
133
137
|
await this.validateFileAndDirectory(path, targetLog);
|
|
134
138
|
|
|
135
139
|
const fileAction = overwrite ? fsp.writeFile : fsp.appendFile;
|
|
@@ -137,6 +141,31 @@ export default class Chronicle {
|
|
|
137
141
|
return fileAction(targetLog, content);
|
|
138
142
|
}
|
|
139
143
|
|
|
144
|
+
// resolves template variables in a filename string
|
|
145
|
+
resolveFilename(template, type) {
|
|
146
|
+
// default to '{type}.log' when no template is configured
|
|
147
|
+
if (!template) return `${type}.log`;
|
|
148
|
+
|
|
149
|
+
const now = new Date();
|
|
150
|
+
const yyyy = now.getFullYear();
|
|
151
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
152
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
153
|
+
|
|
154
|
+
const variables = {
|
|
155
|
+
date: `${yyyy}-${mm}-${dd}`,
|
|
156
|
+
type,
|
|
157
|
+
pid: process.pid,
|
|
158
|
+
hostname: hostname(),
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const resolved = template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
162
|
+
return variables[key] !== undefined ? variables[key] : match;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// sanitize: prevent path traversal and disallow directory separators
|
|
166
|
+
return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
|
|
167
|
+
}
|
|
168
|
+
|
|
140
169
|
// attempts to create file and/or directory if they don't already exist
|
|
141
170
|
async validateFileAndDirectory(path, targetLog) {
|
|
142
171
|
const errorMethod = this.error || console.error; // prefer native method unless removed by user
|