I'm not terribly experienced in JavaScript or NodeJS, but I've written a Discord bot using discord.js
that has a custom plugin framework and bunyan
for logging. I pass a log
object to this plugin before it is ever executed so I can keep the same logger throughout different plugins, they just have to call this.log
to get it.
The problem is, when I am executing a promise and pass a callback function, there is no reference to this.log
because the function is out of scope of the object that is the plugin, so I effectively have to use something global to get it into the callback during then
and catch
methods.
Example code that fails, as you'd expect:
var myPlugin = (function () {
return {
myFunction: async function(message) {
message.guild.channels.create(category, {
type: 'category'
})
.then(function(channel) {
channelID = channel.id;
this.log.debug(`Set channelID to ${channelID}`);
})
.catch(function(error) {
message.reply("Whoops, I hit an error. Please try again later.")
this.log.error(`Error while making category`)
});
}
The failure, predictably, is because this.log
is out of scope for the callback:
(node:8112) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'error' of undefined
at C:\Users\tunacado\Documents\GitHub\myproject\src\plugins\test.js:46:38
at processTicksAndRejections (internal/process/task_queues.js:93:5)
Should I just make a global plugin set of loggers available to get around this, or is there a better way to get a Logger into a callback function other than just a second global scoped logger available to all plugins? I considered writing a defined method and just passing that in instead of an anonymous function, but I am worried because technically this.log
doesn't exist until a certain initialization step in my plugin system.
Best practices appreciated. Thanks!
EDIT:
So adding a method like this to my object works for now:
fromMessageHandlePromiseError: function(error) {
message.reply("Whoops, I hit an error doing that. Please try again later.");
this.log.error(`Error while making category`);
}
I just do .catch(fromMessageHandlePromiseError)
and that works as this
belongs to the object itself, but not sure if this ideal or I am still doing something dumb. Thanks!
EDIT 2:
I've been asked by @MauriceNino to show how I pass the logger, sorry I didn't share that first.
It's done in my index.js
like so, defined here:
// Set up logging
const { DEBUG } = require('bunyan');
var Logger = require('bunyan');
const log = new Logger({
name: 'mybot',
streams: [
{
stream: process.stdout,
level: config.get('bot.logLevel')
}]
}
);
And actually passed to each plugin loaded here:
// Load plugins
var loadPlugins = require('plugin-system');
let plugins;
loadPlugins(
{
paths: [
__dirname + `/src/plugins/`
]
}, log)
.then(function onSuccess(loadedPlugins) {
log.info(`Loaded plugins: ${loadedPlugins}`);
plugins = loadedPlugins;
plugins.forEach(function(plugin) {
plugin.config = config;
plugin.log = log;
// Allow plugins to specify their own startup methods
// post-construction after config is passed
if(typeof plugin.initialize === "function") {
plugin.initialize()
}
})
})
.catch(function onError(err) {
log.error(`Error loading plugins: ${err}`);
})
So I pass to each plugin a config
and the log
for a global config and a global logger.