How to use express-winston and winston-mongodb together? - node.js

I'm using express-winston and winston-mongodb to log express requests to mongodb.
express-winston config:
expressWinston.logger({
meta: true,
//...other unrelated config
})
winston-mongodb config:
new MongoDB({
//...other unrelated config
})
Logging to mongodb works, but meta field is null. Logging to file/console works perfectly.
The mongo plugin doesn't recognize the express plugin's log format.
How can I get them to play nice?

When you look at the express-winston and winston-mongodb codes, you can easily see the difference.
winston-mongodb writes the value that matches the metaKey field to the specified collection.
Therefore, if you define it as follows, the meta field will not be null.
...
transports: [
new winston.transports.Console({
format: winston.format.json({
space: 2
})
}),
new winston.transports.MongoDB({
db: config.db.mongooseURI,
options: config.db.options,
collection:'logs',
capped:true,
metaKey:'meta'
}),
],
meta: true,
...

This is what finally worked for me: v6.12.1 Noticed later that while this works for db logging, the meta is empty for the file transport. Not sure why.
const winston = require('winston');
module.exports = function(err, req, res, next) {
winston.error(err.message, {metadata: { prop: err } });
res.status(500).send('Something failed.');
};

If you want to log to File and MongoDB then:
winston.add(
new winston.transports.MongoDB({
db: process.env.CONNECTIONSTRING,
options: { useUnifiedTopology: true },
metaKey: 'meta'
})
)
module.exports = function (err, req, res, next) {
// Log the exception
winston.error({message: err.message, level: err.level, stack: err.stack, meta: err})
res.status(500).send("Something failed..Cannot connect to Server");
};

Related

Fastify plugin dependency - Registering MySQL before routes

I'm new to NodeJS and Fastify and I'm fairly sure I'm making a basic mistake here but I can't see what it is.
I have separated out my MySQL connection and routes into separate plugins. It appears that the MySQL plugin is not registering in time, leading to the routes not working.
I was under the impression that registering plugins was done asynchronously (loading one plugin at a time), but it can't seem to find "fastify.mysql.query".
Error:
"Cannot read properties of undefined (reading 'query')","stack":"TypeError: Cannot read properties of undefined (reading 'query')\n at Object.<anonymous> (/Users/dally/Projects/NodeJS/boodil-payments-api/routes/payments.js:4:23)\n at preHandlerCallback (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:126:28)\n at preValidationCallback (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:110:5)\n at handler (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:74:7)\n at handleRequest (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:22:5)\n at runPreParsing (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/route.js:487:5)\n at Object.routeHandler [as handler] (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/route.js:434:7)\n at Router.lookup (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/find-my-way/index.js:368:14)\n at Server.emit (node:events:527:28)\n at parserOnIncoming (node:_http_server:956:12)"},"msg":"Cannot read properties of undefined (reading 'query')"}
Server.js
const fastify = require('fastify')({ logger: true })
fastify.register(require('./config/db'))
fastify.register(require('./routes/payments'))
const PORT = 2000
const start = async () => {
try {
await fastify.listen({ port: PORT })
}
catch (e) {
fastify.log.error(e)
process.exit(1)
}
}
start()
DB.js
module.exports = function (fastify, options, done) {
fastify.register(require('#fastify/mysql'), {
connectionString: 'mysql://root:password#localhost/boodil'
})
done()
}
payment.js
function paymentRoutes(fastify, opts, done) {
fastify.get('/get-transactions', (req, reply) => {
fastify.mysql.query(
'SELECT * FROM transactions',
function onResult(err, result) {
reply.send(err || result)
}
)
})
fastify.get('/get-transaction:id', (req, reply) => {
fastify.mysql.query(
'SELECT * FROM transactions where id = ?', [req.params.id],
function onResult(err, result) {
reply.send(err || result)
}
)
})
done()
}
module.exports = paymentRoutes
As said in comments, you need to use the fastify-plugin wrapper in your DB.js file.
To understand this error, you must get confident with fastify's encapsulation context.
It is worth reading these answers:
https://stackoverflow.com/a/61054534/3309466
https://stackoverflow.com/a/73586266/3309466
In practice: your database connection was isolated into a context that was unreachable by the paymentRoutes.
I'm coming from a PHP/Laravel background where this is quite standard
What fastify refuses is creating a global context where all you need is shared. Of course, you can do it, by it is not the default behaviour. You need to declare that the exported plugin has something that can be used by other contexts.

Transport winston logs to a server

i was reading through the winston documentation and I came across the following statement
A transport is essentially a storage device for your logs
So I assumed if i set up a http transport i would be able to aggregate the logs somewhere else. In my case in an application running in localhost:3210
Does anybody know why I'm not receiving the log I'm trying to send ?
Here is my code:
import { createLogger, format, transports } from 'winston';
const { combine, timestamp, label, printf} = format;
const myFormat = printf(({ level, message, label, timestamp }) => {
return `${timestamp} [${label}] ${level}: ${message}`;
});
export const logger = createLogger({
format: combine(label({ label: 'Test Service' }), timestamp(), myFormat),
transports: [
new transports.Http({
host: 'localhost',
port: 3210,
path: '/'
})
]
});
logger.log({
level: 'info',
message: `Hello there`
});
Try logger.info('Hello there');. Also, no idea how your server looks like, but make sure it has a defined POST method with the required path.

Mongoose CastError

I have the following code, which does absolutely nothing, and for some reasons, I have an error with mongoose which is not even called, here's the code:
.post('/testRequest', express.json(), upload.none(), async (req, res) => {
try {
res.status(200);
res.send('test');
} catch (err) {
console.error(err.stack);
res.status(err.code || 400);
res.send(err.message || err);
}
})
And here's the error:
message: 'Cast to ObjectId failed for value "test" at path "_id" for model "Requests"',
name: 'CastError',
stringValue: '"test"',
kind: 'ObjectId',
value: 'test',
path: '_id',
reason: undefined,
I've tried many things but didn't seem to fix it
You probably have a .post(':id', ...) on top of your code. So a post request to /testRequest matches the '/:id' and '/testRequest' routes, but only the top one executes.
The ':id' route reads testRequest as an ID and throws the CastError.
You can swap the order of the methods, which was already discussed here.

Sailsjs - Custom Logging with Winston

I am currently trying to write a custom logger for sailsjs that will use winston to send files to either an s3 bucket or a mongodb database.
The documentation seems to be lacking but so far i have found this:
var customLogger = new winston.Logger({
transports: [
new(winston.transports.File)({
level: 'debug',
filename: './logs/my_log_file.log'
})
]
});
module.exports.log = {
colors: false, // To get clean logs without prefixes or color codings
custom: customLogger
};
Which overall is not working for me.
Any ideas?
After extending above MayBeColin's work, the working solution:
Create a new js file inside a config folder(code inside of this will be executed automatically by sails) and add mongodb transports as below,
var winston = require('winston');
var MongoDB = require('winston-mongodb').MongoDB;
var customLogger = new(winston.Logger)({
transports: [
new(winston.transports.MongoDB)({
db: 'mongodb://localhost:27017/test',
collection: 'logs',
level: 'debug'
})
]
});
module.exports.logging = {
colors: false, // To get clean logs without prefixes or color codings
custom: customLogger
};
And use it anywhere like
sails.config.logging.custom.debug("winston mongodb transport logging");

hapi-auth-cookie failing to load session strategy

There's not many examples out there for hapi and its auth-cookie plugin but here's what I have so far in an attempt to secure a route. Note, most of the examples I've seen are using an older version of hapi which doesn't seem to quite apply to this situation and im hoping im just missing something simple:
var Hapi = require('hapi');
var Mongoose = require('mongoose');
Mongoose.connect('mongodb://localhost/rfmproducetogo');
var server = new Hapi.Server(8080, "localhost");
server.pack.register([{
plugin: require("lout")
}, {
plugin: require('hapi-auth-cookie')
}, {
plugin: require("./plugins/togo")
}, {
plugin: require("./plugins/auth")
}], function(err) {
if (err) throw err;
server.auth.strategy('session', 'cookie', {
password: 'shhasecret',
cookie: 'wtfisthisfor',
isSecure: false,
redirectTo: false
});
server.start(function() {
console.log("hapi server started # " + server.info.uri);
});
});
And in my togo plugin I have this route setup to use the session
exports.create = function(plugin) {
plugin.route({
method: 'POST',
path: '/togo/add',
handler: function(request, reply) {
produce = new Produce();
produce.label = request.payload.label;
produce.price = request.payload.price;
produce.uom = request.payload.uom;
produce.category = request.payload.category;
produce.save(function(err) {
if (!err) {
reply(produce).created('/togo/' + produce._id);
} else {
reply(err);
}
});
},
config: {
auth: 'session'
}
});
};
The error im seeing is this:
/home/adam/Projects/bushhog/node_modules/hapi/node_modules/hoek/lib/index.js:421
throw new Error(msgs.join(' ') || 'Unknown error');
^
Error: Unknown authentication strategy: session in path: /togo/add
at Object.exports.assert (/home/adam/Projects/bushhog/node_modules/hapi/node_modules/hoek/lib/index.js:421:11)
at /home/adam/Projects/bushhog/node_modules/hapi/lib/auth.js:123:14
at Array.forEach (native)
at internals.Auth._setupRoute (/home/adam/Projects/bushhog/node_modules/hapi/lib/auth.js:121:24)
at new module.exports.internals.Route (/home/adam/Projects/bushhog/node_modules/hapi/lib/route.js:118:43)
at /home/adam/Projects/bushhog/node_modules/hapi/lib/router.js:110:25
at Array.forEach (native)
at /home/adam/Projects/bushhog/node_modules/hapi/lib/router.js:107:17
at Array.forEach (native)
at internals.Router.add (/home/adam/Projects/bushhog/node_modules/hapi/lib/router.js:104:13)
Running node 0.10.28, hapijs 6.x, hapi-auth-cookie 1.02
this issue occurs when you try to use an authentication strategy before it's actually available.
You're already following a good application setup by splitting the functionality into individual, small plugins with a given scope.
UPDATE: here's a dedicated tutorial for that problem, how to fix „unknown authentication strategy“
A good way to set up authentication and your plugins that rely on authentication is to create an extra "auth plugin" that adds your desired strategies and can be used as a dependency in your other plugins.
hapi auth plugin example
exports.register = function (server, options, next) {
// declare/register dependencies
server.register(require('hapi-auth-cookie'), err => {
/**
* Register authentication strategies to hapi server
*
* We’re using hapi-auth-cookie plugin to store user information on
* client side to remember user data on every website visit
*
* For sure, we could and will add more authentication strategies.
* What’s next: JWT (we highly welcome pull requests to add JWT functionality!)
*/
server.auth.strategy('session', 'cookie', {
password: 'ThisIsASecretPasswordThisIsASecretPassword',
cookie: 'hapi-rethink-dash',
redirectTo: '/login',
isSecure: false
});
server.log('info', 'Plugin registered: cookie authentication with strategy »session«')
next()
})
}
exports.register.attributes = {
name: 'authentication',
version: '1.0.0'
}
In your /plugins/togo you set the authentication plugin as a dependency (with server.dependency([array-of-deps])) which means hapi registers the auth plugin first and the depending ones afterwards.
You register your plugins like this:
server.register([{
plugin: require('./plugins/authentication')
}, {
plugin: require("./plugins/togo")
}], function(err) {
// handle callback
})
Check hapi-rethinkdb-dash for a detailed example.
Hope that helps!
keep in mind if you use server.dependency inside a plugin like Marcus Poehls did, you also need to register that dependency
server.register([{
plugin: require('hapi-auth-cookie')
},{
plugin: require('./plugins/authentication')
}, {
plugin: require("./plugins/togo")
}], function(err) {
// handle callback
})

Resources