diff --git a/lib/console.js b/lib/console.js new file mode 100644 index 0000000..702f8ee --- /dev/null +++ b/lib/console.js @@ -0,0 +1,116 @@ +'use strict'; + +const readline = require('readline'); +const util = require('util'); + +const INDENT = 2; + +class Console { + #logger; + #groupIndent; + #counts; + #times; + + constructor(logger) { + this.#logger = logger; + this.#groupIndent = 0; + this.#counts = new Map(); + this.#times = new Map(); + } + + assert(assertion, ...args) { + try { + console.assert(assertion, ...args); + } catch (err) { + this.#logger.write('error', this.#groupIndent, err.stack); + } + } + + clear() { + readline.cursorTo(process.stdout, 0, 0); + readline.clearScreenDown(process.stdout); + } + + count(label = 'default') { + let cnt = this.#counts.get(label) || 0; + cnt++; + this.#counts.set(label, cnt); + this.#logger.write('debug', this.#groupIndent, `${label}: ${cnt}`); + } + + countReset(label = 'default') { + this.#counts.delete(label); + } + + debug(...args) { + this.#logger.write('debug', this.#groupIndent, ...args); + } + + dir(...args) { + this.#logger.write('debug', this.#groupIndent, ...args); + } + + trace(...args) { + const msg = util.format(...args); + const err = new Error(msg); + this.#logger.write('debug', this.#groupIndent, `Trace${err.stack}`); + } + + info(...args) { + this.#logger.write('info', this.#groupIndent, ...args); + } + + log(...args) { + this.#logger.write('log', this.#groupIndent, ...args); + } + + warn(...args) { + this.#logger.write('warn', this.#groupIndent, ...args); + } + + error(...args) { + this.#logger.write('error', this.#groupIndent, ...args); + } + + group(...args) { + if (args.length !== 0) this.log(...args); + this.#groupIndent += INDENT; + } + + groupCollapsed(...args) { + this.group(...args); + } + + groupEnd() { + if (this.#groupIndent.length === 0) return; + this.#groupIndent -= INDENT; + } + + table(tabularData) { + this.#logger.write('log', 0, JSON.stringify(tabularData)); + } + + time(label = 'default') { + this.#times.set(label, process.hrtime()); + } + + timeEnd(label = 'default') { + const startTime = this.#times.get(label); + const totalTime = process.hrtime(startTime); + const totalTimeMs = totalTime[0] * 1e3 + totalTime[1] / 1e6; + this.timeLog(label, `${label}: ${totalTimeMs}ms`); + this.#times.delete(label); + } + + timeLog(label, ...args) { + const startTime = this.#times.get(label); + if (startTime === undefined) { + const msg = `Warning: No such label '${label}'`; + this.#logger.write('warn', this.#groupIndent, msg); + return; + } + this.#logger.write('debug', this.#groupIndent, ...args); + } +} + +module.exports = { Console }; diff --git a/lib/formatter.js b/lib/formatter.js new file mode 100644 index 0000000..0f4400b --- /dev/null +++ b/lib/formatter.js @@ -0,0 +1,98 @@ +'use strict'; + +const util = require('util'); +const concolor = require('concolor'); +const metautil = require('metautil'); + +const STACK_AT = ' at '; +const TYPE_LENGTH = 6; +const LINE_SEPARATOR = ';'; +const DATE_LEN = 'YYYY-MM-DD'.length; +const TIME_START = DATE_LEN + 1; +const TIME_END = TIME_START + 'HH:MM:SS'.length; + +const TYPE_COLOR = concolor({ + log: 'b,black/white', + info: 'b,white/blue', + warn: 'b,black/yellow', + debug: 'b,white/green', + error: 'b,yellow/red', +}); + +const TEXT_COLOR = concolor({ + log: 'white', + info: 'white', + warn: 'b,yellow', + debug: 'b,green', + error: 'red', +}); + +class Formatter { + #logger; + + constructor(logger) { + this.#logger = logger; + } + + format(type, indent, ...args) { + const normalize = type === 'error' || type === 'debug'; + const s = `${' '.repeat(indent)}${util.format(...args)}`; + return normalize ? this.#logger.normalizeStack(s) : s; + } + + pretty(type, indent, ...args) { + const dateTime = new Date().toISOString(); + const message = this.format(type, indent, ...args); + const normalColor = TEXT_COLOR[type]; + const markColor = TYPE_COLOR[type]; + const time = normalColor(dateTime.substring(TIME_START, TIME_END)); + const id = normalColor(this.#logger.workerId); + const mark = markColor(' ' + type.padEnd(TYPE_LENGTH)); + const msg = normalColor(message); + return `${time} ${id} ${mark} ${msg}`; + } + + file(type, indent, ...args) { + const dateTime = new Date().toISOString(); + const message = this.format(type, indent, ...args); + const msg = metautil.replace(message, '\n', LINE_SEPARATOR); + return `${dateTime} [${type}] ${msg}`; + } + + json(type, indent, ...args) { + const log = { + timestamp: new Date().toISOString(), + workerId: this.#logger.workerId, + level: type, + message: null, + }; + if (metautil.isError(args[0])) { + log.err = this.expandError(args[0]); + args = args.slice(1); + } else if (typeof args[0] === 'object') { + Object.assign(log, args[0]); + if (metautil.isError(log.err)) log.err = this.expandError(log.err); + if (metautil.isError(log.error)) log.error = this.expandError(log.error); + args = args.slice(1); + } + log.message = util.format(...args); + return JSON.stringify(log); + } + + normalizeStack(stack) { + if (!stack) return 'no data to log'; + let res = metautil.replace(stack, STACK_AT, ''); + if (this.#logger.home) res = metautil.replace(res, this.#logger.home, ''); + return res; + } + + expandError(err) { + return { + message: err.message, + stack: this.normalizeStack(err.stack), + ...err, + }; + } +} + +module.exports = { Formatter }; diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..103d5f7 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,105 @@ +'use strict'; + +const events = require('node:events'); +const metautil = require('metautil'); + +const { Console } = require('./console.js'); +const { Formatter } = require('./formatter.js'); +const { FsTarget } = require('./target.js'); + +const DAY_MILLISECONDS = metautil.duration('1d'); + +const LOG_TYPES = ['log', 'info', 'warn', 'debug', 'error']; + +const DEFAULT_FLAGS = { + log: false, + info: false, + warn: false, + debug: false, + error: false, +}; + +const logTypes = (types = LOG_TYPES) => { + const flags = { ...DEFAULT_FLAGS }; + for (const type of types) { + flags[type] = true; + } + return flags; +}; + +const getNextReopen = () => { + const now = new Date(); + const curTime = now.getTime(); + const nextDate = now.setUTCHours(0, 0, 0, 0); + return nextDate - curTime + DAY_MILLISECONDS; +}; + +class Logger extends events.EventEmitter { + constructor(options) { + super(); + const { workerId = 0, home, json } = options; + const { toFile = [], toStdout = [] } = options; + this.options = options; + this.active = false; + this.workerId = `W${workerId}`; + this.home = home; + this.json = Boolean(json); + this.reopenTimer = null; + this.toFile = logTypes(toFile); + this.toStdout = logTypes(toStdout); + this.console = new Console(this); + this.formatter = new Formatter(this); + this.fsEnabled = toFile.length !== 0; + this.target = null; + return this.open(); + } + + async open() { + if (this.active) return this; + if (!this.fsEnabled) { + this.active = true; + process.nextTick(() => this.emit('open')); + return this; + } + const nextReopen = getNextReopen(); + this.reopenTimer = setTimeout(() => { + this.once('close', () => { + this.open(); + }); + this.close().catch((err) => { + process.stdout.write(`${err.stack}\n`); + this.emit('error', err); + }); + }, nextReopen); + this.target = await new FsTarget(this); + this.active = true; + return this; + } + + async close() { + if (!this.active) return; + if (this.target) await this.target.close(); + this.active = false; + this.emit('close'); + } + + write(type, indent, ...args) { + const { formatter } = this; + if (this.toStdout[type]) { + const line = this.json + ? formatter.json(type, indent, ...args) + : formatter.pretty(type, indent, ...args); + process.stdout.write(line + '\n'); + } + if (this.toFile[type]) { + const line = this.json + ? formatter.json(type, indent, ...args) + : formatter.file(type, indent, ...args); + this.target.write(line + '\n'); + } + } +} + +const openLog = async (args) => new Logger(args); + +module.exports = { Logger, openLog }; diff --git a/lib/target.js b/lib/target.js new file mode 100644 index 0000000..c0fe84f --- /dev/null +++ b/lib/target.js @@ -0,0 +1,161 @@ +'use strict'; + +const fs = require('node:fs'); +const fsp = fs.promises; +const path = require('node:path'); +const events = require('node:events'); +const metautil = require('metautil'); + +const DAY_MILLISECONDS = metautil.duration('1d'); +const DATE_LEN = 'YYYY-MM-DD'.length; +const DEFAULT_WRITE_INTERVAL = metautil.duration('3s'); +const DEFAULT_BUFFER_SIZE = 64 * 1024; +const DEFAULT_KEEP_DAYS = 1; + +const createLogDir = (dir) => { + return new Promise((resolve, reject) => { + fs.access(dir, (err) => { + if (!err) resolve(); + fs.mkdir(dir, (err) => { + if (!err || err.code === 'EEXIST') { + resolve(); + return; + } + const error = new Error(`Can not create directory: ${dir}\n`); + reject(error); + }); + }); + }); +}; + +const nowDays = () => { + const now = new Date(); + const year = now.getUTCFullYear(); + const month = now.getUTCMonth(); + const day = now.getUTCDate(); + const date = new Date(year, month, day, 0, 0, 0, 0); + return Math.floor(date.getTime() / DAY_MILLISECONDS); +}; + +const nameToDays = (fileName) => { + const date = fileName.substring(0, DATE_LEN); + const fileTime = new Date(date).getTime(); + return Math.floor(fileTime / DAY_MILLISECONDS); +}; + +class FsTarget { + #logger; + + constructor(logger) { + this.#logger = logger; + const { writeInterval, writeBuffer, keepDays } = logger.options; + this.path = logger.options.path || fs.createWriteStream; + this.createStream = logger.options.createStream || fs.createWriteStream; + this.writeInterval = writeInterval || DEFAULT_WRITE_INTERVAL; + this.writeBuffer = writeBuffer || DEFAULT_BUFFER_SIZE; + this.keepDays = keepDays || DEFAULT_KEEP_DAYS; + this.stream = null; + this.flushTimer = null; + this.lock = []; + this.buffer = []; + this.file = ''; + return this.open(); + } + + async open() { + await createLogDir(this.path); + const { workerId } = this.#logger; + const now = metautil.nowDate(); + this.file = path.join(this.path, `${now}-${workerId}.log`); + if (this.keepDays) await this.rotate(); + const options = { flags: 'a', bufferSize: this.writeBuffer }; + this.stream = this.createStream(this.file, options); + this.flushTimer = setInterval(() => { + this.flush(); + }, this.writeInterval); + try { + await events.once(this.stream, 'open'); + } catch { + throw new Error(`Can't open log file: ${this.file}`); + } + return this; + } + + async close() { + const { stream } = this; + if (!stream || stream.destroyed || stream.closed) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + this.flush((err) => { + if (err) { + reject(err); + return; + } + stream.end(() => { + clearInterval(this.flushTimer); + this.flushTimer = null; + const fileName = this.file; + fs.stat(fileName, (err, stats) => { + if (err || stats.size > 0) { + resolve(); + return; + } + //fsp.unlink(fileName).then(resolve, reject); + }); + }); + }); + }); + } + + async rotate() { + if (!this.keepDays) return; + const now = nowDays(); + const finish = []; + try { + const files = await fsp.readdir(this.path); + for (const fileName of files) { + if (metautil.fileExt(fileName) !== 'log') continue; + const fileAge = now - nameToDays(fileName); + if (fileAge < this.keepDays) continue; + //finish.push(fsp.unlink(path.join(this.path, fileName))); + } + await Promise.all(finish); + } catch (err) { + process.stdout.write(`${err.stack}\n`); + this.#logger.emit('error', err); + } + } + + write(line) { + const buffer = Buffer.from(line); + this.buffer.push(buffer); + } + + flush(callback) { + if (this.lock.length > 0) { + if (callback) this.lock.push(callback); + return; + } + if (this.buffer.length === 0) { + if (callback) callback(); + return; + } + if (!this.#logger.active) { + const err = new Error('Cannot flush log buffer: logger is not opened'); + this.#logger.emit('error', err); + if (callback) callback(err); + return; + } + this.lock.push(callback); + const buffer = Buffer.concat(this.buffer); + this.buffer.length = 0; + this.stream.write(buffer, () => { + const callbacks = this.lock; + this.lock = []; + for (const callback of callbacks) callback(); + }); + } +} + +module.exports = { FsTarget }; diff --git a/metalog.js b/metalog.js index 9a380ad..cbe3aaa 100644 --- a/metalog.js +++ b/metalog.js @@ -1,426 +1,3 @@ 'use strict'; -const fs = require('node:fs'); -const fsp = fs.promises; -const path = require('node:path'); -const util = require('node:util'); -const events = require('node:events'); -const readline = require('node:readline'); -const metautil = require('metautil'); -const concolor = require('concolor'); - -const DAY_MILLISECONDS = metautil.duration('1d'); -const DEFAULT_WRITE_INTERVAL = metautil.duration('3s'); -const DEFAULT_BUFFER_SIZE = 64 * 1024; -const DEFAULT_KEEP_DAYS = 1; -const STACK_AT = ' at '; -const TYPE_LENGTH = 6; -const LINE_SEPARATOR = ';'; -const INDENT = 2; -const DATE_LEN = 'YYYY-MM-DD'.length; -const TIME_START = DATE_LEN + 1; -const TIME_END = TIME_START + 'HH:MM:SS'.length; - -const LOG_TYPES = ['log', 'info', 'warn', 'debug', 'error']; - -const TYPE_COLOR = concolor({ - log: 'b,black/white', - info: 'b,white/blue', - warn: 'b,black/yellow', - debug: 'b,white/green', - error: 'b,yellow/red', -}); - -const TEXT_COLOR = concolor({ - log: 'white', - info: 'white', - warn: 'b,yellow', - debug: 'b,green', - error: 'red', -}); - -const DEFAULT_FLAGS = { - log: false, - info: false, - warn: false, - debug: false, - error: false, -}; - -const logTypes = (types = LOG_TYPES) => { - const flags = { ...DEFAULT_FLAGS }; - for (const type of types) { - flags[type] = true; - } - return flags; -}; - -const nowDays = () => { - const now = new Date(); - const year = now.getUTCFullYear(); - const month = now.getUTCMonth(); - const day = now.getUTCDate(); - const date = new Date(year, month, day, 0, 0, 0, 0); - return Math.floor(date.getTime() / DAY_MILLISECONDS); -}; - -const nameToDays = (fileName) => { - const date = fileName.substring(0, DATE_LEN); - const fileTime = new Date(date).getTime(); - return Math.floor(fileTime / DAY_MILLISECONDS); -}; - -const getNextReopen = () => { - const now = new Date(); - const curTime = now.getTime(); - const nextDate = now.setUTCHours(0, 0, 0, 0); - return nextDate - curTime + DAY_MILLISECONDS; -}; - -const isError = (val) => - Object.prototype.toString.call(val) === '[object Error]'; - -class Console { - constructor(write) { - this._write = write; - this._groupIndent = 0; - this._counts = new Map(); - this._times = new Map(); - } - - assert(assertion, ...args) { - try { - console.assert(assertion, ...args); - } catch (err) { - this._write('error', this._groupIndent, err.stack); - } - } - - clear() { - readline.cursorTo(process.stdout, 0, 0); - readline.clearScreenDown(process.stdout); - } - - count(label = 'default') { - let cnt = this._counts.get(label) || 0; - cnt++; - this._counts.set(label, cnt); - this._write('debug', this._groupIndent, `${label}: ${cnt}`); - } - - countReset(label = 'default') { - this._counts.delete(label); - } - - debug(...args) { - this._write('debug', this._groupIndent, ...args); - } - - dir(...args) { - this._write('debug', this._groupIndent, ...args); - } - - trace(...args) { - const msg = util.format(...args); - const err = new Error(msg); - this._write('debug', this._groupIndent, `Trace${err.stack}`); - } - - info(...args) { - this._write('info', this._groupIndent, ...args); - } - - log(...args) { - this._write('log', this._groupIndent, ...args); - } - - warn(...args) { - this._write('warn', this._groupIndent, ...args); - } - - error(...args) { - this._write('error', this._groupIndent, ...args); - } - - group(...args) { - if (args.length !== 0) this.log(...args); - this._groupIndent += INDENT; - } - - groupCollapsed(...args) { - this.group(...args); - } - - groupEnd() { - if (this._groupIndent.length === 0) return; - this._groupIndent -= INDENT; - } - - table(tabularData) { - this._write('log', 0, JSON.stringify(tabularData)); - } - - time(label = 'default') { - this._times.set(label, process.hrtime()); - } - - timeEnd(label = 'default') { - const startTime = this._times.get(label); - const totalTime = process.hrtime(startTime); - const totalTimeMs = totalTime[0] * 1e3 + totalTime[1] / 1e6; - this.timeLog(label, `${label}: ${totalTimeMs}ms`); - this._times.delete(label); - } - - timeLog(label, ...args) { - const startTime = this._times.get(label); - if (startTime === undefined) { - const msg = `Warning: No such label '${label}'`; - this._write('warn', this._groupIndent, msg); - return; - } - this._write('debug', this._groupIndent, ...args); - } -} - -class Logger extends events.EventEmitter { - constructor(args) { - super(); - const { workerId = 0, createStream = fs.createWriteStream } = args; - const { writeInterval, writeBuffer, keepDays, home, json } = args; - const { toFile = [], toStdout = [] } = args; - this.active = false; - this.path = args.path; - this.workerId = `W${workerId}`; - this.createStream = createStream; - this.writeInterval = writeInterval || DEFAULT_WRITE_INTERVAL; - this.writeBuffer = writeBuffer || DEFAULT_BUFFER_SIZE; - this.keepDays = keepDays || DEFAULT_KEEP_DAYS; - this.home = home; - this.json = Boolean(json); - this.stream = null; - this.reopenTimer = null; - this.flushTimer = null; - this.lock = false; - this.buffer = []; - this.file = ''; - this.toFile = logTypes(toFile); - this.fsEnabled = toFile.length !== 0; - this.toStdout = logTypes(toStdout); - this.console = new Console((...args) => this.write(...args)); - return this.open(); - } - - createLogDir() { - return new Promise((resolve, reject) => { - fs.access(this.path, (err) => { - if (!err) resolve(); - fs.mkdir(this.path, (err) => { - if (!err || err.code === 'EEXIST') { - resolve(); - return; - } - const error = new Error(`Can not create directory: ${this.path}\n`); - this.emit('error', error); - reject(); - }); - }); - }); - } - - async open() { - if (this.active) return this; - this.active = true; - if (!this.fsEnabled) { - process.nextTick(() => this.emit('open')); - return this; - } - await this.createLogDir(); - const fileName = metautil.nowDate() + '-' + this.workerId + '.log'; - this.file = path.join(this.path, fileName); - const nextReopen = getNextReopen(); - this.reopenTimer = setTimeout(() => { - this.once('close', () => { - this.open(); - }); - this.close().catch((err) => { - process.stdout.write(`${err.stack}\n`); - this.emit('error', err); - }); - }, nextReopen); - if (this.keepDays) await this.rotate(); - const options = { flags: 'a', bufferSize: this.writeBuffer }; - this.stream = this.createStream(this.file, options); - this.flushTimer = setInterval(() => { - this.flush(); - }, this.writeInterval); - this.stream.on('open', () => { - this.emit('open'); - }); - this.stream.on('error', () => { - this.emit('error', new Error(`Can't open log file: ${this.file}`)); - }); - await events.once(this, 'open'); - return this; - } - - async close() { - if (!this.active) return Promise.resolve(); - if (!this.fsEnabled) { - this.active = false; - this.emit('close'); - return Promise.resolve(); - } - const { stream } = this; - if (!stream || stream.destroyed || stream.closed) { - return Promise.resolve(); - } - return new Promise((resolve, reject) => { - this.flush((err) => { - if (err) { - reject(err); - return; - } - this.active = false; - stream.end(() => { - clearInterval(this.flushTimer); - clearTimeout(this.reopenTimer); - this.flushTimer = null; - this.reopenTimer = null; - const fileName = this.file; - this.emit('close'); - fs.stat(fileName, (err, stats) => { - if (!err && stats.size === 0) { - fsp.unlink(fileName).catch(() => {}); - } - resolve(); - }); - }); - }); - }); - } - - async rotate() { - if (!this.keepDays) return; - const now = nowDays(); - const finish = []; - try { - const files = await fsp.readdir(this.path); - for (const fileName of files) { - if (metautil.fileExt(fileName) !== 'log') continue; - const fileAge = now - nameToDays(fileName); - if (fileAge < this.keepDays) continue; - finish.push(fsp.unlink(path.join(this.path, fileName))); - } - await Promise.all(finish); - } catch (err) { - process.stdout.write(`${err.stack}\n`); - this.emit('error', err); - } - } - - format(type, indent, ...args) { - const normalize = type === 'error' || type === 'debug'; - const s = `${' '.repeat(indent)}${util.format(...args)}`; - return normalize ? this.normalizeStack(s) : s; - } - - formatPretty(type, indent, ...args) { - const dateTime = new Date().toISOString(); - const message = this.format(type, indent, ...args); - const normalColor = TEXT_COLOR[type]; - const markColor = TYPE_COLOR[type]; - const time = normalColor(dateTime.substring(TIME_START, TIME_END)); - const id = normalColor(this.workerId); - const mark = markColor(' ' + type.padEnd(TYPE_LENGTH)); - const msg = normalColor(message); - return `${time} ${id} ${mark} ${msg}`; - } - - formatFile(type, indent, ...args) { - const dateTime = new Date().toISOString(); - const message = this.format(type, indent, ...args); - const msg = metautil.replace(message, '\n', LINE_SEPARATOR); - return `${dateTime} [${type}] ${msg}`; - } - - formatJson(type, indent, ...args) { - const log = { - timestamp: new Date().toISOString(), - workerId: this.workerId, - level: type, - message: null, - }; - if (isError(args[0])) { - log.err = this.expandError(args[0]); - args = args.slice(1); - } else if (typeof args[0] === 'object') { - Object.assign(log, args[0]); - if (isError(log.err)) log.err = this.expandError(log.err); - if (isError(log.error)) log.error = this.expandError(log.error); - args = args.slice(1); - } - log.message = util.format(...args); - return JSON.stringify(log); - } - - write(type, indent, ...args) { - if (this.toStdout[type]) { - const line = this.json - ? this.formatJson(type, indent, ...args) - : this.formatPretty(type, indent, ...args); - process.stdout.write(line + '\n'); - } - if (this.toFile[type]) { - const line = this.json - ? this.formatJson(type, indent, ...args) - : this.formatFile(type, indent, ...args); - const buffer = Buffer.from(line + '\n'); - this.buffer.push(buffer); - } - } - - flush(callback) { - if (this.lock) { - if (callback) this.once('unlocked', callback); - return; - } - if (this.buffer.length === 0) { - if (callback) callback(); - return; - } - if (!this.active) { - const err = new Error('Cannot flush log buffer: logger is not opened'); - this.emit('error', err); - if (callback) callback(err); - return; - } - this.lock = true; - const buffer = Buffer.concat(this.buffer); - this.buffer.length = 0; - this.stream.write(buffer, () => { - this.lock = false; - this.emit('unlocked'); - if (callback) callback(); - }); - } - - normalizeStack(stack) { - if (!stack) return 'no data to log'; - let res = metautil.replace(stack, STACK_AT, ''); - if (this.home) res = metautil.replace(res, this.home, ''); - return res; - } - - expandError(err) { - return { - message: err.message, - stack: this.normalizeStack(err.stack), - ...err, - }; - } -} - -const openLog = async (args) => new Logger(args); - -module.exports = { Logger, openLog }; +module.exports = require('./lib/logger.js'); diff --git a/test/file.js b/test/file.js index 3f0923c..9899c67 100644 --- a/test/file.js +++ b/test/file.js @@ -5,12 +5,13 @@ const metalog = require('..'); const createLogger = () => metalog.openLog({ - path: './log', + path: '../log', workerId: 3, writeInterval: 3000, writeBuffer: 64 * 1024, keepDays: 5, toStdout: [], + toFile: ['log'], home: process.cwd(), }); @@ -126,9 +127,9 @@ const createLogger = () => test.end(); }); - metatests.test('logger.rotate', async (test) => { + metatests.test('target.rotate', async (test) => { const logger = await createLogger(); - logger.rotate(); + await logger.target.rotate(); await logger.close(); test.end(); }); @@ -136,7 +137,7 @@ const createLogger = () => metatests.test('Truncate paths in stack traces', async (test) => { const logger = await createLogger(); const message = new Error('Example').stack; - const msg = logger.normalizeStack(message); + const msg = logger.formatter.normalizeStack(message); const dir = process.cwd(); if (msg.includes(dir)) throw new Error('Path truncation error'); await logger.close();