@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.
Files changed (3) hide show
  1. package/README.md +35 -0
  2. package/package.json +1 -1
  3. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stonyx/logs",
3
- "version": "1.0.1-beta.8",
3
+ "version": "1.0.1-beta.9",
4
4
  "description": "Simplified logging for node applications",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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 targetLog = `${path}${type}.log`;
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