diff --git a/package-lock.json b/package-lock.json index 155c228..b99d9a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "chalk": "^4.1.2", + "date-fns": "^2.30.0", "fs-extra": "^10.0.0", "parse-ms": "^2.1.0", "safe-json-stringify": "^1.2.0", @@ -580,6 +581,17 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", @@ -1873,6 +1885,21 @@ "node": ">= 8" } }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -3842,6 +3869,11 @@ "node": ">=8.10.0" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -4933,6 +4965,14 @@ "integrity": "sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw==", "dev": true }, + "@babel/runtime": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "requires": { + "regenerator-runtime": "^0.14.0" + } + }, "@babel/template": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", @@ -5919,6 +5959,14 @@ "which": "^2.0.1" } }, + "date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "requires": { + "@babel/runtime": "^7.21.0" + } + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -7383,6 +7431,11 @@ "picomatch": "^2.2.1" } }, + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", diff --git a/package.json b/package.json index bd48b4a..f2130a2 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ }, "dependencies": { "chalk": "^4.1.2", + "date-fns": "^2.30.0", "fs-extra": "^10.0.0", "parse-ms": "^2.1.0", "safe-json-stringify": "^1.2.0", diff --git a/src/Logger.spec.ts b/src/Logger.spec.ts index aa27910..40df4ed 100644 --- a/src/Logger.spec.ts +++ b/src/Logger.spec.ts @@ -255,6 +255,49 @@ describe('Logger', () => { const stamp = logger.formatTimestamp(new Date()); expect(/\d\d:\d\d:\d\d\.\d\d\d/.exec(stamp)).to.exist; }); + + it('uses from original options', () => { + logger = new Logger({ timestampFormat: 'HH' }); + expect( + logger.formatTimestamp(now) + ).to.eql('04'); + }); + + it('uses from parent options', () => { + logger = new Logger({ timestampFormat: 'HH' }); + logger = logger.createLogger(); + expect( + logger.formatTimestamp(now) + ).to.eql('04'); + }); + + it('uses the default when missing from options', () => { + logger = new Logger(); + expect( + logger.formatTimestamp(now) + ).to.eql(timestamp); + }); + + it('uses the default when deleted from options', () => { + logger = new Logger({ timestampFormat: 'HH' }); + logger['options'].timestampFormat = undefined; + expect( + logger.formatTimestamp(now) + ).to.eql(timestamp); + }); + + it('uses the default when deleted from logger itself', () => { + logger = new Logger({ timestampFormat: 'HH' }); + logger.timestampFormat = undefined; + expect( + logger.formatTimestamp(now) + ).to.eql(timestamp); + }); + + it('supports the brighterscript log format', () => { + logger.timestampFormat = 'hh:mm:ss:SSS aa'; + expect(logger.formatTimestamp(now)).to.eql('04:05:06:789 AM'); + }); }); describe('emit', () => { diff --git a/src/Logger.ts b/src/Logger.ts index f7f69e5..a523cfc 100644 --- a/src/Logger.ts +++ b/src/Logger.ts @@ -1,5 +1,6 @@ import * as safeJsonStringify from 'safe-json-stringify'; import { serializeError } from 'serialize-error'; +import { format } from 'date-fns'; import type { ChalkFunction } from 'chalk'; // eslint-disable-next-line @typescript-eslint/no-require-imports import Chalk = require('chalk'); @@ -20,6 +21,21 @@ export class Logger { */ private options: LoggerOptions; + /** + * The timestamp format string. Defaults to 'HH:mm:ss.SSS' (24-hour time with milliseconds) + * + * https://date-fns.org/v2.30.0/docs/format + */ + public get timestampFormat(): string { + return this.options.timestampFormat ?? this.options.parent?.timestampFormat ?? 'HH:mm:ss.SSS'; + } + public set timestampFormat(value: string | undefined) { + this.options.timestampFormat = value; + } + + /** + * The log level of this logger. If a log level is not specified, it will inherit from the parent logger or default to 'log' + */ public get logLevel(): LogLevel | LogLevelNumeric { return this.options.logLevel ?? this.options.parent?.logLevel ?? 'log'; } @@ -175,12 +191,7 @@ export class Logger { } public formatTimestamp(date: Date) { - return date.getHours().toString().padStart(2, '0') + - ':' + - date.getMinutes().toString().padStart(2, '0') + - ':' + - date.getSeconds().toString().padStart(2, '0') + - '.' + date.getMilliseconds().toString().padEnd(3, '0').substring(0, 3); + return format(date, this.timestampFormat); } /** @@ -466,6 +477,12 @@ export enum LogLevelNumeric { export type LogLevel = 'off' | 'error' | 'warn' | 'log' | 'info' | 'debug' | 'trace'; export interface LoggerOptions { + /** + * The timestamp format string. Defaults to 'HH:mm:ss.SSS' (24-hour time with milliseconds) + * + * https://date-fns.org/v2.30.0/docs/format + */ + timestampFormat?: string; /** * A prefix applied to every log entry. Appears directly after the logLevel */