Following up on my last blog post Node.js real time logging with Winston, Redis and Socket.io, p1 I want to get into the integration of winston with Socekt.io to stream logs real time to a browser.
So just a quick recap, the idea here is to have a mechanism that logs messages categorized by different levels in the server and displays them at the same time in the browser, keeping the user informed at all times of the action in the back end, or helping a developer spot some bugs without the need to keep an eye on the terminal console or searching for log files buried in the server.
Socket.io
So first we need to initialize the socket.io lib.
This part could be done several different ways, I haven’t found a baseline to follow when initializing and sharing a socket.io handler on express, so if anybody knows please hit me up in the comments.
Anyway, the approach I decided to take was:
- Initialize the logger
- Register an event listener on the logger instance.
- Start the express http server
- Start the socket.io server
- Fire event on the logger instance
// Create logger var di = {}; di.config = require('./config/config.js'); var logger = require('./utils/logger.js').inject(di); // Start listening for socket event on the logger logger.on("socket", function () { this.socketIO = loggerSocket; }); // Create && Start http server var server = http.createServer(app); server.listen(app.get('port'), function() { console.log("Express server listening on port " + app.get('port')); }); // Create socket.io connection var io = require('socket.io') console.log("creating socket connection"); var loggerSocket = io.listen(server, {log: false}).of('/logger'); loggerSocket.on('connection', function(socket){ socket.join(socket.handshake.sessionID); // Emit event to logger logger.emit("socket"); });
As you can see in the code snippet above, all the dependencies for the logger are saved in an object and injected on the module, in the case it only depends on config.js.
And since the logger is a singleton, all other modules that require the logger will get an already initialized instance.
After we get a handle on the logger, we start listening for the ‘socket’ event, the name could be anything since we are firing the event later in the code. The reason behind this event is that we can to grab a hold of the socket connection and save it inside the logger so we can start streaming logs once they are generated.
We could simply set the reference to socketIO on the logger inside the connection event for the socket, however, by decoupling the socket connection with the logger socket.io handler initialization gives us the flexibility to move things around to different places.
Last, we start the http and socket.io server and fire a socket event whenever the socket.io finishes connecting.
Streaming logs with winston
Now that the logger has a handle of the socket.io connection it can start streaming logs to the browser in real time.
var CustomLogger = function (config) { .... .... winston.stream({ start: -1 }).on('log', function(log) { var type = log.transport[0]; if (self.socketIO && type === "redis") { console.log("\n**emitting socket msg"); self.socketIO.emit("newLog", log); } }); }
In the logger constructor we initialize the winston stream which listens for all new logs added to different Transports.
That’s why we check for the redis Transports specifically before emitting the logĀ with socket.io, since we don’t want to emit repeated logs.
Displaying logs on the client
Looking at the client side code
// Create socketIO connection this.logger = io.connect('/logger'); // Incoming log via socket connection this.logger.on('newLog', function (data) { var log = new app.Log({ 'timestamp' : data.timestamp, 'file' : data.file, 'line' : data.line, 'message' : data.message, 'level' : data.level, 'id' : data.id, }); self.socketLog = true; self.collections[data.level].add(log); });
We create a socket connection with the server and start listening for the ‘newLog’ event, which contains the log data being streamed from winston.
For our app, since we are using Backbone, we create a new Log model and add that Log to the Logger collection, which contains a bunch of Logs.
Just to give an idea of how the Logger prototype is shaping up:
In the end this works, but it could be better.
My idea is to deeply integrate a socket.io streaming functionality with winston, providing the option to start streaming logs straight out of the box. The goal is to make logs as useful as possible, and not just something that’s there but never used.