From 5d7c9bb198ca27d91c2d465c38284fc8168c55aa Mon Sep 17 00:00:00 2001 From: "Navy.gif" Date: Wed, 12 Apr 2023 18:45:44 +0300 Subject: [PATCH] add webhook support, better colour resolving --- .eslintrc.json | 6 ----- README.md | 28 +++++++++++++++--------- package.json | 1 + src/Defaults.js | 22 ++++++++++++------- src/MasterLogger.js | 53 ++++++++++++++++++++++++++++++++++----------- test/test.js | 11 ++++++++-- yarn.lock | 46 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 128 insertions(+), 39 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 0028d90..815f350 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,7 +17,6 @@ "array-callback-return": "warn", "array-bracket-newline": ["warn", "consistent"], "array-bracket-spacing": ["warn", "always", { "objectsInArrays": false, "arraysInArrays": false }], - // "arrow-parens": "warn", "arrow-spacing": "warn", "block-scoped-var": "warn", "block-spacing": ["warn", "always"], @@ -76,10 +75,6 @@ ], "lines-around-comment": "warn", "lines-around-directive": "warn", - // "lines-between-class-members": [ - // "warn", - // "always" - // ], "max-classes-per-file": "warn", "max-nested-callbacks": "warn", "new-parens": "warn", @@ -108,7 +103,6 @@ "no-invalid-this": "warn", "no-iterator": "warn", "no-label-var": "warn", - // "no-labels": "warn", "no-lone-blocks": "warn", "no-lonely-if": "warn", "no-loop-func": "warn", diff --git a/README.md b/README.md index a063a62..4da960a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ # Navy's logger -Simple logger I wrote to have a unified system for logging throughout my projects. - -## TODO -- 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 +Simple logger I wrote to have a unified system for logging throughout my projects. ## Features Split into Master and Client for logging between processes, where master resides on the master process and the clients on the spawned processes. -Should be fairly trivial to modify it to work across nodes with websockets. +Should be fairly trivial to modify it to work across nodes with websockets or some other IPC protocol. **Note** When logging from a child process, the master logger expects the child process to be be in a wrapper containing at the very least an ID property to work properly (used for identifying which child the message came from). @@ -28,12 +25,23 @@ The child processes are expected to be attached with the attach() method found i customColors: {}, // Supports any colours chalk.js supports, e.g. "warn": "green" will turn warning outputs green 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 + logLevelMapping: { + debug: 0, + info: 1, + status: 2, + warn: 3, + error: 4 + } + broadcastLevel: 4, // Level at which to broadcast to webhook if supplied + webhook: { + url: string + } } ``` **Log levels** -0 - Debug -1 - Info -2 - Status -3 - Warning -4 - Error \ No newline at end of file +0 - Debug +1 - Info +2 - Status +3 - Warning +4 - Error \ No newline at end of file diff --git a/package.json b/package.json index 0ec549d..6268d9f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "eslint": "^8.26.0" }, "dependencies": { + "@navy.gif/discord-webhook": "^1.0.0", "chalk": "^4.1.2", "moment": "^2.29.4" }, diff --git a/src/Defaults.js b/src/Defaults.js index b15615f..9b0c94e 100644 --- a/src/Defaults.js +++ b/src/Defaults.js @@ -1,3 +1,11 @@ +const ColourCodes = { + error: 0xe88388, + warn: 0xf9d472, + info: 0x76a9d8, + debug: 0xd8abd7, + status: 0x72d4d7 +}; + module.exports = { Types: [ 'error', @@ -16,13 +24,7 @@ module.exports = { debug: 'magenta', status: 'cyanBright' }, - ColourCodes: { - error: 0xe88388, - warn: 0xf9d472, - info: 0x76a9d8, - debug: 0xd8abd7, - status: 0x72d4d7 - }, + ColourCodes, MasterOptions: { fileRotationFreq: 1, directory: './logs', @@ -31,6 +33,7 @@ module.exports = { customTypeMapping: {}, customColors: {}, guard: '_logger', + broadcastLevel: 4, logLevel: 1, logLevelMapping: { debug: 0, @@ -38,7 +41,10 @@ module.exports = { status: 2, warn: 3, error: 4 - } + }, + webhook: { + url: null + }, }, ClientOptions: { guard: '_logger', diff --git a/src/MasterLogger.js b/src/MasterLogger.js index 3493cdf..f04c167 100644 --- a/src/MasterLogger.js +++ b/src/MasterLogger.js @@ -6,32 +6,35 @@ const path = require('node:path'); const fs = require('node:fs'); const { inspect } = require('node:util'); +// const DiscordWebhook = require('@navy.gif/discord-webhook'); const Defaults = require('./Defaults'); const DAY = 1000 * 60 * 60 * 24; -// TODO: -// - 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 { #_guard = null; #_logLevel = null; #_logLevelMapping = null; + #_broadcastLevel = null; + #webhook = null; constructor (config = Defaults.MasterOptions) { - + const { directory, customTypes, customStreams, customTypeMapping, - customColors, guard, fileRotationFreq, logLevel, logLevelMapping + customColors, guard, fileRotationFreq, logLevel, logLevelMapping, + webhook, broadcastLevel } = { ...Defaults.MasterOptions, ...config }; this.directory = path.resolve(directory); if (!fs.existsSync(this.directory)) fs.mkdirSync(this.directory, { recursive: true }); + this.#_broadcastLevel = broadcastLevel; this.#_logLevel = logLevel; this.#_logLevelMapping = { ...Defaults.MasterOptions.logLevelMapping, ...logLevelMapping }; this.#_guard = guard; - + this.types = [ ...customTypes, ...Defaults.Types ]; for (const type of this.types) { Object.defineProperty(this, type, { @@ -39,6 +42,20 @@ class MasterLogger { }); } this.colours = { ...Defaults.Colours, ...customColors }; + + this.colourFuncs = Object.entries(this.colours).reduce((prev, [ type, colour ]) => { + if (typeof colour === 'number') + colour = `#${colour.toString(16)}`; + else if (typeof colour !== 'string') + throw new Error('Expecting colours to be either an integer, a hex string or one of the chalk compatible colour names'); + + if ((/#[A-Fa-f0-9]+/u).test(colour)) + prev[type] = { func: chalk.hex(colour), int: parseInt(colour.replace('#', ''), 16) }; + else + prev[type] = { func: chalk[colour], int: Defaults.ColourCodes[type] || Defaults.ColourCodes.info }; + + return prev; + }, {}); this.streamTypes = [ ...customStreams, 'error', 'default' ]; this.streamTypeMapping = { ...Defaults.TypeStream, ...customTypeMapping }; @@ -52,6 +69,12 @@ class MasterLogger { this.startDay = Math.floor(Date.now() / this.rotationFreq) * this.rotationFreq; this.rotateTO = setTimeout(this.rotateLogFiles.bind(this), this.startDay + this.rotationFreq - Date.now()); + if (webhook && webhook.url) + import('@navy.gif/discord-webhook').then(({ default: DiscordWebhook }) => { + this.#webhook = new DiscordWebhook({ url: webhook.url }); + this.#webhook.fetch(); + }); + } get logLevel () { @@ -83,9 +106,9 @@ class MasterLogger { write (type = 'info', text, { subheader = '', shard = null, broadcast = false } = {}) { type = type.toLowerCase(); - let colour = this.colours[type]; + let colour = this.colourFuncs[type]; if (!colour) - colour = this.colours.info; + colour = this.colourCodes.info; if (typeof text !== 'string') text = inspect(text); @@ -94,14 +117,18 @@ class MasterLogger { const maxChars = Math.max(...this.types.map(t => t.length)); const spacer = ' '.repeat(maxChars - type.length); - // if (type === 'debug' && this._debug || type !== 'debug') if (this.#_logLevelMapping[type] >= this.#_logLevel) - console.log(`${chalk[colour](type)}${spacer} ${chalk[colour](header)}: ${chalk.bold(subheader)}${text}`); // eslint-disable-line no-console + console.log(`${colour.func(type)}${spacer} ${colour.func(header)}: ${chalk.bold(subheader)}${text}`); // eslint-disable-line no-console + + if (broadcast || this.#_broadcastLevel <= this.#_logLevelMapping[type] && this.#webhook) + this.#webhook.send({ + embeds: [{ + title: `[__${type.toUpperCase()}__] ${this._shard(shard)}`, + description: `**${subheader}**\n${text}`, + color: colour.int + }] + }); - if (broadcast) { - // Send to webhook - TODO - } - const streamType = this.streamTypeMapping[type] || 'default'; if (this.writeStreams[streamType]) this.writeStreams[streamType].write(`\n${type}${spacer} ${header}: ${subheader}${text}`); diff --git a/test/test.js b/test/test.js index bc57d33..a27c685 100644 --- a/test/test.js +++ b/test/test.js @@ -15,9 +15,16 @@ const main = async () => { customTypes: [ 'access' ], customStreams: [ 'access' ], customTypeMapping: { access: 'access', warn: 'error' }, - customColors: { access: 'green' }, + customColors: { + access: 'green', + // error: '#FF0000' + }, fileRotationFreq: 0.0001, - logLevelMapping: { access: 2 } + logLevelMapping: { access: 2 }, + broadcastLevel: 3, + webhook: { + url: 'https://discord.com/api/webhooks/1093874668886294548/uDMRD6g1lmq_2EZynsbKytzWoMM-0N4te0m61r_cv1BsSnDKDxG3fvI6sxSoG5t5b_xn' + } }); const child = ChildProcess.fork('./test/otherProcess.js'); logger.attach(child); diff --git a/yarn.lock b/yarn.lock index 4db9fd3..53c2b6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -36,6 +36,13 @@ resolved "https://registry.corgi.wtf/@humanwhocodes%2fobject-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@navy.gif/discord-webhook@^1.0.0": + version "1.1.0" + resolved "https://registry.corgi.wtf/@navy.gif/discord-webhook/-/discord-webhook-1.1.0.tgz#b3f980bc42663ff1617c3756ce6c9184bc2a48f3" + integrity sha512-+aJOfZD5kRoxEWU+sDAjX4qSRZ7vTPDBuFd6MsoaWC7ZSjSFuDqryyumBCs+w0EKU94xdxoH2qQ8ebFZ//ZugQ== + dependencies: + node-fetch "^3.3.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.corgi.wtf/@nodelib%2ffs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -146,6 +153,11 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.corgi.wtf/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + debug@^4.1.1, debug@^4.3.2: version "4.3.4" resolved "https://registry.corgi.wtf/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -295,6 +307,14 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.corgi.wtf/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.corgi.wtf/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -323,6 +343,13 @@ flatted@^3.1.0: resolved "https://registry.corgi.wtf/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.corgi.wtf/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.corgi.wtf/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -481,6 +508,20 @@ natural-compare@^1.4.0: resolved "https://registry.corgi.wtf/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.corgi.wtf/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.1: + version "3.3.1" + resolved "https://registry.corgi.wtf/node-fetch/-/node-fetch-3.3.1.tgz#b3eea7b54b3a48020e46f4f88b9c5a7430d20b2e" + integrity sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + once@^1.3.0: version "1.4.0" resolved "https://registry.corgi.wtf/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -635,6 +676,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +web-streams-polyfill@^3.0.3: + version "3.2.1" + resolved "https://registry.corgi.wtf/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" + integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q== + which@^2.0.1: version "2.0.2" resolved "https://registry.corgi.wtf/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"