@emasoft/svg-matrix 1.0.6 → 1.0.7
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 +85 -0
- package/bin/svg-matrix.js +1000 -0
- package/package.json +12 -2
- package/scripts/bootstrap_repo.sh +99 -0
- package/scripts/postinstall.js +252 -0
- package/src/clip-path-resolver.js +2 -1
- package/src/index.js +15 -1
- package/src/logger.js +302 -0
package/src/logger.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Configurable logging for @emasoft/svg-matrix
|
|
3
|
+
* Provides centralized logging control for all library modules.
|
|
4
|
+
*
|
|
5
|
+
* Why buffered writing: appendFileSync blocks the event loop on every log call.
|
|
6
|
+
* For high-volume logging, this creates significant performance impact.
|
|
7
|
+
* Instead, we buffer log messages and flush periodically or on demand.
|
|
8
|
+
* The tradeoff is that logs may be lost on crash - use flush() before
|
|
9
|
+
* critical operations if durability is needed.
|
|
10
|
+
*
|
|
11
|
+
* @module src/logger
|
|
12
|
+
* @license MIT
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import { Logger, setLogLevel, LogLevel } from '@emasoft/svg-matrix';
|
|
16
|
+
*
|
|
17
|
+
* // Suppress all logging
|
|
18
|
+
* setLogLevel(LogLevel.SILENT);
|
|
19
|
+
*
|
|
20
|
+
* // Enable only errors
|
|
21
|
+
* setLogLevel(LogLevel.ERROR);
|
|
22
|
+
*
|
|
23
|
+
* // Enable warnings and errors (default)
|
|
24
|
+
* setLogLevel(LogLevel.WARN);
|
|
25
|
+
*
|
|
26
|
+
* // Enable all logging including debug
|
|
27
|
+
* setLogLevel(LogLevel.DEBUG);
|
|
28
|
+
*
|
|
29
|
+
* // Or configure via Logger object
|
|
30
|
+
* Logger.level = LogLevel.WARN;
|
|
31
|
+
* Logger.logToFile = '/path/to/logfile.log';
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
// Why: Only appendFileSync is needed - we use sync writes in flush() for reliability
|
|
35
|
+
// during shutdown or before critical operations. Async writes were considered but
|
|
36
|
+
// the complexity of handling async flush during process exit wasn't worth it.
|
|
37
|
+
import { appendFileSync } from 'fs';
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// CONSTANTS
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Why: Centralize configuration values to make tuning easier
|
|
43
|
+
const LOG_BUFFER_SIZE = 100; // Flush after this many messages
|
|
44
|
+
const LOG_FLUSH_INTERVAL_MS = 5000; // Auto-flush every 5 seconds
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// LOG LEVELS
|
|
48
|
+
// ============================================================================
|
|
49
|
+
/**
|
|
50
|
+
* Log levels for controlling output verbosity.
|
|
51
|
+
* Why numeric values: Allows simple >= comparisons for level filtering.
|
|
52
|
+
* @enum {number}
|
|
53
|
+
*/
|
|
54
|
+
export const LogLevel = {
|
|
55
|
+
/** Suppress all logging - use when library is used in production */
|
|
56
|
+
SILENT: 0,
|
|
57
|
+
/** Log only errors - problems that prevent operation */
|
|
58
|
+
ERROR: 1,
|
|
59
|
+
/** Log errors and warnings - issues that may indicate problems */
|
|
60
|
+
WARN: 2,
|
|
61
|
+
/** Log errors, warnings, and info - normal operation status */
|
|
62
|
+
INFO: 3,
|
|
63
|
+
/** Log everything including debug - for development/troubleshooting */
|
|
64
|
+
DEBUG: 4,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// LOGGER IMPLEMENTATION
|
|
69
|
+
// ============================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Global logger configuration and methods.
|
|
72
|
+
* Why singleton pattern: Logging configuration should be consistent across
|
|
73
|
+
* all modules in the library. A single Logger object ensures this.
|
|
74
|
+
* @namespace
|
|
75
|
+
*/
|
|
76
|
+
export const Logger = {
|
|
77
|
+
/**
|
|
78
|
+
* Current log level. Messages below this level are suppressed.
|
|
79
|
+
* Why WARN default: Library users typically only want to see problems,
|
|
80
|
+
* not routine operation info. They can increase to INFO/DEBUG if needed.
|
|
81
|
+
* @type {number}
|
|
82
|
+
*/
|
|
83
|
+
level: LogLevel.WARN,
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Optional file path for logging output.
|
|
87
|
+
* Why null default: File logging must be explicitly enabled to avoid
|
|
88
|
+
* unexpected file creation in user's project directories.
|
|
89
|
+
* @type {string|null}
|
|
90
|
+
*/
|
|
91
|
+
logToFile: null,
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Whether to include timestamps in log output.
|
|
95
|
+
* Why false default: Timestamps add noise for casual use. Enable for
|
|
96
|
+
* debugging timing issues or when correlating with other logs.
|
|
97
|
+
* @type {boolean}
|
|
98
|
+
*/
|
|
99
|
+
timestamps: false,
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Internal buffer for batching file writes.
|
|
103
|
+
* Why buffering: Reduces I/O overhead by batching multiple log messages
|
|
104
|
+
* into single file operations. See module docstring for tradeoffs.
|
|
105
|
+
* @type {string[]}
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
_buffer: [],
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Timer reference for periodic flushing.
|
|
112
|
+
* Why: Ensures buffered messages are written even during idle periods.
|
|
113
|
+
* Without this, buffered messages might never be written if logging stops.
|
|
114
|
+
* @type {NodeJS.Timeout|null}
|
|
115
|
+
* @private
|
|
116
|
+
*/
|
|
117
|
+
_flushTimer: null,
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format a log message with optional timestamp.
|
|
121
|
+
* Why centralized formatting: Ensures consistent log format across all
|
|
122
|
+
* log levels. Makes parsing and grep'ing logs easier.
|
|
123
|
+
* @param {string} level - Log level name
|
|
124
|
+
* @param {string} message - Message to format
|
|
125
|
+
* @returns {string} Formatted message
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
_format(level, message) {
|
|
129
|
+
if (this.timestamps) {
|
|
130
|
+
const ts = new Date().toISOString();
|
|
131
|
+
return `[${ts}] [${level}] ${message}`;
|
|
132
|
+
}
|
|
133
|
+
return `[${level}] ${message}`;
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Add message to buffer and flush if needed.
|
|
138
|
+
* Why buffer + conditional flush: Balances write efficiency with
|
|
139
|
+
* message timeliness. Large bursts are batched, but messages aren't
|
|
140
|
+
* delayed indefinitely.
|
|
141
|
+
* @param {string} message - Message to buffer
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
_bufferWrite(message) {
|
|
145
|
+
if (!this.logToFile) return;
|
|
146
|
+
|
|
147
|
+
this._buffer.push(message);
|
|
148
|
+
|
|
149
|
+
// Why: Flush when buffer is full to prevent unbounded memory growth
|
|
150
|
+
if (this._buffer.length >= LOG_BUFFER_SIZE) {
|
|
151
|
+
this.flush();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Why: Start auto-flush timer if not already running
|
|
155
|
+
if (!this._flushTimer) {
|
|
156
|
+
this._flushTimer = setInterval(() => this.flush(), LOG_FLUSH_INTERVAL_MS);
|
|
157
|
+
// Why: unref() prevents timer from keeping process alive when done
|
|
158
|
+
this._flushTimer.unref();
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Flush buffered messages to file.
|
|
164
|
+
* Why public: Allows callers to force immediate write before critical
|
|
165
|
+
* operations or shutdown. Uses sync write during flush for reliability.
|
|
166
|
+
*/
|
|
167
|
+
flush() {
|
|
168
|
+
if (!this.logToFile || this._buffer.length === 0) return;
|
|
169
|
+
|
|
170
|
+
try {
|
|
171
|
+
// Why sync here: flush() is called when reliability matters more
|
|
172
|
+
// than performance (shutdown, before risky operation)
|
|
173
|
+
const content = this._buffer.join('\n') + '\n';
|
|
174
|
+
appendFileSync(this.logToFile, content);
|
|
175
|
+
this._buffer = [];
|
|
176
|
+
} catch {
|
|
177
|
+
// Why silent: Can't log a logging failure. Clear buffer to prevent
|
|
178
|
+
// infinite growth if file is consistently unwritable.
|
|
179
|
+
this._buffer = [];
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Log an error message. Always logged unless SILENT.
|
|
185
|
+
* Why always flush errors: Errors may precede crashes. Immediate
|
|
186
|
+
* write ensures the error is captured even if crash follows.
|
|
187
|
+
* @param {string} message - Error message
|
|
188
|
+
* @param {...any} args - Additional arguments
|
|
189
|
+
*/
|
|
190
|
+
error(message, ...args) {
|
|
191
|
+
if (this.level >= LogLevel.ERROR) {
|
|
192
|
+
const formatted = this._format('ERROR', message);
|
|
193
|
+
console.error(formatted, ...args);
|
|
194
|
+
this._bufferWrite(formatted + (args.length ? ' ' + args.join(' ') : ''));
|
|
195
|
+
// Why: Errors are important enough to flush immediately
|
|
196
|
+
this.flush();
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Log a warning message. Logged at WARN level and above.
|
|
202
|
+
* @param {string} message - Warning message
|
|
203
|
+
* @param {...any} args - Additional arguments
|
|
204
|
+
*/
|
|
205
|
+
warn(message, ...args) {
|
|
206
|
+
if (this.level >= LogLevel.WARN) {
|
|
207
|
+
const formatted = this._format('WARN', message);
|
|
208
|
+
console.warn(formatted, ...args);
|
|
209
|
+
this._bufferWrite(formatted + (args.length ? ' ' + args.join(' ') : ''));
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Log an info message. Logged at INFO level and above.
|
|
215
|
+
* @param {string} message - Info message
|
|
216
|
+
* @param {...any} args - Additional arguments
|
|
217
|
+
*/
|
|
218
|
+
info(message, ...args) {
|
|
219
|
+
if (this.level >= LogLevel.INFO) {
|
|
220
|
+
const formatted = this._format('INFO', message);
|
|
221
|
+
console.log(formatted, ...args);
|
|
222
|
+
this._bufferWrite(formatted + (args.length ? ' ' + args.join(' ') : ''));
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Log a debug message. Logged only at DEBUG level.
|
|
228
|
+
* @param {string} message - Debug message
|
|
229
|
+
* @param {...any} args - Additional arguments
|
|
230
|
+
*/
|
|
231
|
+
debug(message, ...args) {
|
|
232
|
+
if (this.level >= LogLevel.DEBUG) {
|
|
233
|
+
const formatted = this._format('DEBUG', message);
|
|
234
|
+
console.log(formatted, ...args);
|
|
235
|
+
this._bufferWrite(formatted + (args.length ? ' ' + args.join(' ') : ''));
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Clean up resources. Call before process exit.
|
|
241
|
+
* Why: Flushes any remaining buffered messages and clears the timer.
|
|
242
|
+
* Without this, messages in buffer would be lost on exit.
|
|
243
|
+
*/
|
|
244
|
+
shutdown() {
|
|
245
|
+
if (this._flushTimer) {
|
|
246
|
+
clearInterval(this._flushTimer);
|
|
247
|
+
this._flushTimer = null;
|
|
248
|
+
}
|
|
249
|
+
this.flush();
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Set the global log level.
|
|
255
|
+
* Why convenience function: Provides a more explicit API than directly
|
|
256
|
+
* modifying Logger.level. Also allows future validation or side effects.
|
|
257
|
+
* @param {number} level - Log level from LogLevel enum
|
|
258
|
+
*/
|
|
259
|
+
export function setLogLevel(level) {
|
|
260
|
+
// Why: Validate level is within valid range to catch typos
|
|
261
|
+
if (level < LogLevel.SILENT || level > LogLevel.DEBUG) {
|
|
262
|
+
throw new Error(`Invalid log level: ${level}. Use LogLevel.SILENT (0) through LogLevel.DEBUG (4)`);
|
|
263
|
+
}
|
|
264
|
+
Logger.level = level;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get the current log level.
|
|
269
|
+
* Why: Allows callers to save/restore log level around noisy operations.
|
|
270
|
+
* @returns {number} Current log level
|
|
271
|
+
*/
|
|
272
|
+
export function getLogLevel() {
|
|
273
|
+
return Logger.level;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Enable file logging.
|
|
278
|
+
* Why separate function: Encapsulates the setup of file logging including
|
|
279
|
+
* timestamp configuration. Clearer intent than setting multiple properties.
|
|
280
|
+
* @param {string} filePath - Path to log file
|
|
281
|
+
* @param {boolean} [withTimestamps=true] - Include timestamps
|
|
282
|
+
*/
|
|
283
|
+
export function enableFileLogging(filePath, withTimestamps = true) {
|
|
284
|
+
// Why: Don't accept empty/null paths - would cause confusing errors later
|
|
285
|
+
if (!filePath) {
|
|
286
|
+
throw new Error('File path required for enableFileLogging()');
|
|
287
|
+
}
|
|
288
|
+
Logger.logToFile = filePath;
|
|
289
|
+
Logger.timestamps = withTimestamps;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Disable file logging.
|
|
294
|
+
* Why: Clean shutdown of file logging. Flushes buffer to ensure no
|
|
295
|
+
* messages are lost, then clears the file path.
|
|
296
|
+
*/
|
|
297
|
+
export function disableFileLogging() {
|
|
298
|
+
Logger.flush(); // Why: Don't lose buffered messages
|
|
299
|
+
Logger.logToFile = null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export default Logger;
|