Compare commits
2 Commits
23d880e96d
...
4e6b3418df
Author | SHA1 | Date | |
---|---|---|---|
4e6b3418df | |||
9be57d20c7 |
@ -161,7 +161,7 @@
|
|||||||
"no-var": "warn",
|
"no-var": "warn",
|
||||||
"no-void": "warn",
|
"no-void": "warn",
|
||||||
"no-whitespace-before-property": "error",
|
"no-whitespace-before-property": "error",
|
||||||
"nonblock-statement-body-position": "warn",
|
"nonblock-statement-body-position": ["warn", "below"],
|
||||||
"object-curly-spacing": [
|
"object-curly-spacing": [
|
||||||
"warn",
|
"warn",
|
||||||
"always"
|
"always"
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
Simple logger I wrote to have a unified system for logging throughout my projects.
|
Simple logger I wrote to have a unified system for logging throughout my projects.
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
- Automatic file rotation, currently only rotates files on startup
|
|
||||||
- Discord webhook, need to write a separate package for that, don't want to add the entirety of d.js as a dep just for a webhook
|
- Discord webhook, need to write a separate package for that, don't want to add the entirety of d.js as a dep just for a webhook
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
@ -19,10 +18,15 @@ The child processes are expected to be attached with the attach() method found i
|
|||||||
## Logger Options
|
## Logger Options
|
||||||
```
|
```
|
||||||
{
|
{
|
||||||
|
//////// SHARED BETWEEN CLIENT & MASTER
|
||||||
|
guard: '_logger', // Message guard, e.g this property has to be true in the IPC message for the logger to read it
|
||||||
|
debug: false, // On the master logger this determines whether the output is written to console (i.e. will always be written to file), on the client this determines whether it is sent to the master at all
|
||||||
customTypes: [], // Log types, defaults are 'error', 'warn', 'info', 'debug', 'status'. Each one of these has an associated shorthand function, the custom ones will receive one too, e.g. adding 'access' to the custom types will add a logger.access() function
|
customTypes: [], // Log types, defaults are 'error', 'warn', 'info', 'debug', 'status'. Each one of these has an associated shorthand function, the custom ones will receive one too, e.g. adding 'access' to the custom types will add a logger.access() function
|
||||||
|
/////// MASTER EXCLUSIVE
|
||||||
customStreams: [], // File streams, by default there are streams for error and default
|
customStreams: [], // File streams, by default there are streams for error and default
|
||||||
customTypeMapping: {}, // This maps a type to a stream, e.g. adding "warn": "error" will pipe any warnings to the error log file
|
customTypeMapping: {}, // This maps a type to a stream, e.g. adding "warn": "error" will pipe any warnings to the error log file
|
||||||
customColors: {}, // Supports any colours chalk.js supports, e.g. "warn": "green" will turn warning outputs green
|
customColors: {}, // Supports any colours chalk.js supports, e.g. "warn": "green" will turn warning outputs green
|
||||||
debug: false
|
fileRotationFreq: 1, // How frequently to roate files in days - will not work with anything less than 1 day currently
|
||||||
|
directory: './logs', // Directory in which to write log files
|
||||||
}
|
}
|
||||||
```
|
```
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@navy.gif/logger",
|
"name": "@navy.gif/logger",
|
||||||
"version": "1.1.9",
|
"version": "1.2.0",
|
||||||
"description": "Logging thing",
|
"description": "Logging thing",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Navy.gif",
|
"author": "Navy.gif",
|
||||||
|
@ -22,5 +22,20 @@ module.exports = {
|
|||||||
info: 0x76a9d8,
|
info: 0x76a9d8,
|
||||||
debug: 0xd8abd7,
|
debug: 0xd8abd7,
|
||||||
status: 0x72d4d7
|
status: 0x72d4d7
|
||||||
|
},
|
||||||
|
MasterOptions: {
|
||||||
|
fileRotationFreq: 1,
|
||||||
|
directory: './logs',
|
||||||
|
customTypes: [],
|
||||||
|
customStreams: [],
|
||||||
|
customTypeMapping: {},
|
||||||
|
customColors: {},
|
||||||
|
debug: false,
|
||||||
|
guard: '_logger'
|
||||||
|
},
|
||||||
|
ClientOptions: {
|
||||||
|
debug: true,
|
||||||
|
guard: '_logger',
|
||||||
|
customStreams: []
|
||||||
}
|
}
|
||||||
};
|
};
|
@ -4,13 +4,15 @@ class LoggerClient {
|
|||||||
|
|
||||||
static MaxChars = 0;
|
static MaxChars = 0;
|
||||||
|
|
||||||
constructor (opts) {
|
constructor (opts = Defaults.ClientOptions) {
|
||||||
|
|
||||||
this.name = opts.name || opts.constructor.name;
|
this.name = opts.name || opts.constructor.name;
|
||||||
if (this.name === 'Object') this.name = 'unknown';
|
if (this.name === 'Object')
|
||||||
|
this.name = 'unknown';
|
||||||
this.name = this.name.toUpperCase();
|
this.name = this.name.toUpperCase();
|
||||||
|
|
||||||
if (this.name.length > LoggerClient.MaxChars) LoggerClient.MaxChars = this.name.length;
|
if (this.name.length > LoggerClient.MaxChars)
|
||||||
|
LoggerClient.MaxChars = this.name.length;
|
||||||
|
|
||||||
const { customTypes = [] } = opts;
|
const { customTypes = [] } = opts;
|
||||||
this.types = [ ...customTypes, ...Defaults.Types ];
|
this.types = [ ...customTypes, ...Defaults.Types ];
|
||||||
@ -20,17 +22,23 @@ class LoggerClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.guard = opts.guard || Defaults.ClientOptions.guard;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transport (message, opts) {
|
transport (message, opts) {
|
||||||
|
|
||||||
if (typeof message !== 'string') message = inspect(message);
|
if (typeof message !== 'string')
|
||||||
|
message = inspect(message);
|
||||||
const spacer = ' '.repeat(LoggerClient.MaxChars - this.name.length);
|
const spacer = ' '.repeat(LoggerClient.MaxChars - this.name.length);
|
||||||
const header = `${`[${this.name.substring(0, LoggerClient.MaxChars)}]${spacer}`} `;
|
const header = `${`[${this.name.substring(0, LoggerClient.MaxChars)}]${spacer}`} `;
|
||||||
|
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
if (!process.send || !process.connected) console.log(`${header} ${message}`);
|
if (!process.send || !process.connected)
|
||||||
else process.send({ _logger: true, header, message, ...opts });
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(`${header} ${message}`);
|
||||||
|
else
|
||||||
|
process.send({ [this.guard]: true, header, message, ...opts });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,41 +8,67 @@ const { inspect } = require('node:util');
|
|||||||
|
|
||||||
const Defaults = require('./Defaults');
|
const Defaults = require('./Defaults');
|
||||||
|
|
||||||
|
const DAY = 1000 * 60 * 60 * 24;
|
||||||
|
|
||||||
|
const DefaultOpts = {
|
||||||
|
fileRotationFreq: 1,
|
||||||
|
directory: './logs',
|
||||||
|
customTypes: [],
|
||||||
|
customStreams: [],
|
||||||
|
customTypeMapping: {},
|
||||||
|
customColors: {},
|
||||||
|
debug: false,
|
||||||
|
guard: '_logger'
|
||||||
|
};
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - File rotation
|
|
||||||
// - Discord webhook, need to write a separate package for that, don't want to add the entirety of d.js as a dep just for a webhook
|
// - Discord webhook, need to write a separate package for that, don't want to add the entirety of d.js as a dep just for a webhook
|
||||||
class MasterLogger {
|
class MasterLogger {
|
||||||
|
|
||||||
constructor (options = {}) {
|
constructor ({
|
||||||
|
directory = './logs',
|
||||||
|
customTypes = [],
|
||||||
|
customStreams = [],
|
||||||
|
customTypeMapping = {},
|
||||||
|
customColors = {},
|
||||||
|
debug = false,
|
||||||
|
guard = '_logger',
|
||||||
|
fileRotationFreq = 1
|
||||||
|
} = DefaultOpts) {
|
||||||
|
|
||||||
this.directory = path.resolve(options.directory || './logs');
|
this.directory = path.resolve(directory);
|
||||||
if (!fs.existsSync(this.directory)) fs.mkdirSync(this.directory, { recursive: true });
|
if (!fs.existsSync(this.directory))
|
||||||
const { customTypes = [], customStreams = [], customTypeMapping = {}, customColors = {}, debug = false } = options;
|
fs.mkdirSync(this.directory, { recursive: true });
|
||||||
this._debug = debug;
|
this._debug = debug;
|
||||||
|
|
||||||
this.types = [ ...customTypes, ...Defaults.Types ];
|
this.types = [ ...customTypes, ...Defaults.Types ];
|
||||||
for (const type of this.types) {
|
for (const type of this.types) {
|
||||||
Object.defineProperty(this, type, {
|
Object.defineProperty(this, type, {
|
||||||
// Only write debug outputs if debug mode is enabled
|
value: (msg, opts) => this.write(type, msg, opts)
|
||||||
value: (msg, opts) => msg.type === 'debug' && debug || msg.type !== 'debug'
|
|
||||||
? this.write(type, msg, opts) : null
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.colours = { ...Defaults.Colours, ...customColors };
|
this.colours = { ...Defaults.Colours, ...customColors };
|
||||||
|
|
||||||
this.streamTypes = [ ...customStreams, 'error', 'default' ];
|
this.streamTypes = [ ...customStreams, 'error', 'default' ];
|
||||||
this.streamTypeMapping = { ...Defaults.TypeStream, ...customTypeMapping };
|
this.streamTypeMapping = { ...Defaults.TypeStream, ...customTypeMapping };
|
||||||
// eslint-disable-next-line no-return-assign
|
|
||||||
this.writeStreams = this.streamTypes.reduce((acc, type) => {
|
this.writeStreams = this.streamTypes.reduce((acc, type) => {
|
||||||
acc[type] = this.loadFile(type);
|
acc[type] = this.loadFile(type);
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
// Startup day, used to keep track of file rotation
|
||||||
|
this.rotationFreq = fileRotationFreq * DAY;
|
||||||
|
this.startDay = Math.floor(Date.now() / this.rotationFreq) * this.rotationFreq;
|
||||||
|
this.rotateTO = setTimeout(this.rotateLogFiles.bind(this), this.startDay + this.rotationFreq - Date.now());
|
||||||
|
|
||||||
|
this.guard = guard;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attach (shard) {
|
attach (shard) {
|
||||||
shard.on('message', (msg) => {
|
shard.on('message', (msg) => {
|
||||||
if (!msg._logger) return;
|
if (!msg[this.guard])
|
||||||
|
return;
|
||||||
const { message, type, header, broadcast } = msg;
|
const { message, type, header, broadcast } = msg;
|
||||||
this[type](message, { subheader: header, shard, broadcast });
|
this[type](message, { subheader: header, shard, broadcast });
|
||||||
});
|
});
|
||||||
@ -52,42 +78,62 @@ class MasterLogger {
|
|||||||
|
|
||||||
type = type.toLowerCase();
|
type = type.toLowerCase();
|
||||||
let colour = this.colours[type];
|
let colour = this.colours[type];
|
||||||
if (!colour) colour = this.colours.info;
|
if (!colour)
|
||||||
|
colour = this.colours.info;
|
||||||
|
|
||||||
if (typeof text !== 'string') text = inspect(text);
|
if (typeof text !== 'string')
|
||||||
|
text = inspect(text);
|
||||||
|
|
||||||
const header = `[${this.date}] [${this._shard(shard)}]`;
|
const header = `[${this.date}] [${this._shard(shard)}]`;
|
||||||
const maxChars = Math.max(...this.types.map(t => t.length));
|
const maxChars = Math.max(...this.types.map(t => t.length));
|
||||||
const spacer = ' '.repeat(maxChars - type.length);
|
const spacer = ' '.repeat(maxChars - type.length);
|
||||||
|
|
||||||
console.log(`${chalk[colour](type)}${spacer} ${chalk[colour](header)}: ${chalk.bold(subheader)}${text}`); // eslint-disable-line no-console
|
if (type === 'debug' && this._debug || type !== 'debug')
|
||||||
|
console.log(`${chalk[colour](type)}${spacer} ${chalk[colour](header)}: ${chalk.bold(subheader)}${text}`); // eslint-disable-line no-console
|
||||||
|
|
||||||
if (broadcast) {
|
if (broadcast) {
|
||||||
// Send to webhook - TODO
|
// Send to webhook - TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
const streamType = this.streamTypeMapping[type] || 'default';
|
const streamType = this.streamTypeMapping[type] || 'default';
|
||||||
if (this.writeStreams[streamType]) this.writeStreams[streamType].write(`\n${type}${spacer} ${header}: ${subheader}${text}`);
|
if (this.writeStreams[streamType])
|
||||||
else console.log(`${chalk.red(`[LOGGER] Missing file stream for ${streamType}`)}`); // eslint-disable-line no-console
|
this.writeStreams[streamType].write(`\n${type}${spacer} ${header}: ${subheader}${text}`);
|
||||||
|
else
|
||||||
|
console.log(`${chalk.red(`[LOGGER] Missing file stream for ${streamType}`)}`); // eslint-disable-line no-console
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFile (type) {
|
rotateLogFiles () {
|
||||||
if (!type) throw new Error('Missing file type');
|
const streams = Object.keys(this.writeStreams);
|
||||||
|
for (const type of streams) {
|
||||||
|
this.writeStreams[type].write('\nRotating log file');
|
||||||
|
this.writeStreams[type].end();
|
||||||
|
this.writeStreams[type] = this.loadFile(type);
|
||||||
|
}
|
||||||
|
const nextTime = Math.floor(Date.now() / this.rotationFreq) * this.rotationFreq + this.rotationFreq;
|
||||||
|
this.rotateTO = setTimeout(this.rotateLogFiles.bind(this), nextTime - Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
const fileName = `${moment().format('YYYY-MM-DD')}-${type}.log`;
|
loadFile (type, date = Date.now()) {
|
||||||
|
if (!type)
|
||||||
|
throw new Error('Missing file type');
|
||||||
|
|
||||||
|
const fileName = `${moment(date).format('YYYY-MM-DD')}-${type}-${date}.log`;
|
||||||
const filePath = path.join(this.directory, fileName);
|
const filePath = path.join(this.directory, fileName);
|
||||||
|
|
||||||
if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, '');
|
if (!fs.existsSync(filePath))
|
||||||
|
fs.writeFileSync(filePath, '');
|
||||||
|
|
||||||
return fs.createWriteStream(filePath, { flags: 'a' });
|
return fs.createWriteStream(filePath, { flags: 'a' });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_shard (shard) {
|
_shard (shard) {
|
||||||
if (!shard) return 'controller';
|
if (!shard)
|
||||||
|
return 'controller';
|
||||||
let id = '??';
|
let id = '??';
|
||||||
if ('id' in shard) id = `${shard.id < 10 ? `0${shard.id}` : shard.id}`;
|
if ('id' in shard)
|
||||||
|
id = `${shard.id < 10 ? `0${shard.id}` : shard.id}`;
|
||||||
return `shard-${id}`;
|
return `shard-${id}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,17 @@ const logger = new LoggerClient({
|
|||||||
customColors: { access: 'green' }
|
customColors: { access: 'green' }
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info('Info test');
|
const main = async () => {
|
||||||
logger.status('Status test');
|
for (let i = 0; i < 10000; i++) {
|
||||||
logger.debug('Debug test');
|
if (i % 500 === 0)
|
||||||
logger.warn('Warn test');
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
logger.error('Error test');
|
logger.info('Info test');
|
||||||
logger.access('Access test');
|
logger.status('Status test');
|
||||||
|
logger.debug('Debug test');
|
||||||
|
logger.warn('Warn test');
|
||||||
|
logger.error('Error test');
|
||||||
|
logger.access('Access test');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
main();
|
15
test/test.js
15
test/test.js
@ -15,7 +15,8 @@ const main = async () => {
|
|||||||
customTypes: [ 'access' ],
|
customTypes: [ 'access' ],
|
||||||
customStreams: [ 'access' ],
|
customStreams: [ 'access' ],
|
||||||
customTypeMapping: { access: 'access', warn: 'error' },
|
customTypeMapping: { access: 'access', warn: 'error' },
|
||||||
customColors: { access: 'green' }
|
customColors: { access: 'green' },
|
||||||
|
fileRotationFreq: 0.0001
|
||||||
});
|
});
|
||||||
const child = ChildProcess.fork('./test/otherProcess.js');
|
const child = ChildProcess.fork('./test/otherProcess.js');
|
||||||
logger.attach(child);
|
logger.attach(child);
|
||||||
@ -25,13 +26,9 @@ const main = async () => {
|
|||||||
const { types, colours, streamTypes, streamTypeMapping } = logger; // , writeStreams
|
const { types, colours, streamTypes, streamTypeMapping } = logger; // , writeStreams
|
||||||
console.log(types, colours, streamTypes, streamTypeMapping); // , writeStreams
|
console.log(types, colours, streamTypes, streamTypeMapping); // , writeStreams
|
||||||
|
|
||||||
logger.info('Info test');
|
for (let i = 0; i < 10; i++) {
|
||||||
logger.status('Status test');
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||||
logger.debug('Debug test');
|
logger.info(`Iteration ${i}`);
|
||||||
logger.warn('Warn test');
|
}
|
||||||
logger.error('Error test');
|
|
||||||
logger.access('Access test');
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
};
|
};
|
||||||
main();
|
main();
|
Loading…
Reference in New Issue
Block a user