Configurable logger outputting to the console and optionally to a file, with cleaned error stack traces, and timestamps in the YYYY-MM-DD HH:MM:SS[.mmm] format (RFC3339, or ISO8601).
-
prefixes each line with the local time in RFC3339
YYYY-MM-DD HH:MM:SSformat (which is ISO8601 with a more readablebetween the date and the time instead of theT)[2025-04-23 17:00:00] It's tea time -
outputs
Errorobjects to file (via serialize-error) -
cleans up
Errorstack traces (via clean-stack) -
makes absolute error paths relative to the home directory
-
uses the native Node
consolewith colorization, plus yellow forWARNs and red forERRORs- the downside is that objects beyond 3 levels deep will be displayed as
[Object]. Refer to the same timestamp in the log file to see the full JSON dump.
- the downside is that objects beyond 3 levels deep will be displayed as
-
exposes a lazily-created writable
.streamfor integration with other libraries -
uses four standard log levels:
debug,info,WARN,ERROR. -
option to prefix messages with the same unique id per Logger instance, to distinguish them in parallel processing contexts
-
you can use the familiar variable-arity
consoleformat, with arguments of any type:logger.warn('Got', results.length, 'results, but also an error:', results, new Error('oops'));
-
arrays are logged in JSON format, with newlines for readability
logger.error([error1, error2]); // smart indented display
Overall, the package aims to format messages logged to a file as close as possible to the console having been redirected to that file (e.g. by adding newlines for readability), while including more information than what was logged to the console (e.g. by fully dumping objects beyond the first 3 levels of nesting).
This is a JSR package.
# Deno (optional if you don't prefix the import with 'jsr:'), current pnpm or yarn
deno add jsr:@dandv/timestamp-logger
pnpm add jsr:@dandv/timestamp-logger
yarn add jsr:@dandv/timestamp-logger
# NPM, bun, and older versions of yarn or pnpm
npx jsr add @dandv/timestamp-logger
bunx jsr add @dandv/timestamp-logger
yarn dlx jsr add @dandv/timestamp-logger
pnpm dlx jsr add @dandv/timestamp-loggerimport { Logger } from '@dandv/timestamp-logger';
const logger = new Logger({ filename: 'file.log' });
// Timestamped log messages in the YYYY-MM-DDTHH:MM:SS format and the local timezone
logger.debug('Greyed out timestamp to de-emphasize');
logger.info('Variable number of arguments, not just', 1);
logger.warn('Yellow for warnings');
logger.error('Error with clean stack trace', new Error('Oops'));
// No need to close โ log methods append atomically, no file handle is held openIf you need a persistent WriteStream (e.g. to hand off to Mongoose or another library), access the .stream getter. This lazily opens a file handle, so you must close the Logger when done โ via await using, using, or an explicit .close() call:
import { Logger } from '@dandv/timestamp-logger';
import mongoose from 'mongoose';
await using logger = new Logger({ filename: 'file.log' });
mongoose.set('debug', logger.stream); // creates a persistent WriteStream
// ... logger.stream is automatically closed at end of scope by `await using`Or with an explicit .close():
const logger = new Logger({ filename: 'file.log' });
externalLib.setOutput(logger.stream);
// ... when done:
await logger.close();For more examples, see examples.ts.
If you're using Deno, you may need to run Deno with the following access flags:
-
Permission to write to the log file if you initialize the constructor with a filename option (
new Logger({ filename: 'example.log' })). Run with:deno run --allow-write examples.ts # or to be strict: --allow-write=example.log -
Permission to determine (not read) the home directory if you log any error objects with
cleanStack: true. This is required byclean-stack. Run with:deno run --allow-sys=homedir examples.ts
Normal log methods (.debug(), .info(), .warn(), .error()) append to the file atomically โ no persistent file handle is held open, so the Logger can simply go out of scope without leaking resources.
You only need to close the Logger if you accessed the .stream getter (e.g. to pass it to another library). Three options, in order of preference:
await usingโ automatically flushes and closes when the variable goes out of scope:{ await using logger = new Logger({ filename: 'app.log' }); externalLib.setOutput(logger.stream); // ... } // stream is closed here
usingโ synchronous disposal; the stream may not be fully flushed before you can read the file:{ using logger = new Logger({ filename: 'app.log' }); externalLib.setOutput(logger.stream); }
await logger.close()โ explicit async close:const logger = new Logger({ filename: 'app.log' }); externalLib.setOutput(logger.stream); // ... await logger.close();
A FinalizationRegistry safety net will close a forgotten stream when the Logger is garbage-collected, but you should not rely on it โ GC timing is non-deterministic.
-
Logging something right before calling
Deno/process.exit()won't flush the output to the file. This is a problem with all loggers (e.g. Winston, Bristol). As a workaround, try delaying the exit:setTimeout(() => Deno.exit(1), 1);
-
Stack traces don't produce proper URLs. This is an issue with
clean-stack. -
Somewhat ironically,
Dateobjects logged to the console will be output in UTC, while in the log file they're output in the local timezone (i.e. passed through.localISOdt). This is done to preserve console colorization, and may be improved in a future version. In the meantime, you can pass Date objects to.localISOdtif desired.
MIT
