Taking a Live Look: How to Observe Your Node.js Application Logs in Real-Time
Introduction
In this article, I’ll be demonstrating how you can add a “live” console web tail to your Node.js application hosted on BlueMix in a few easy steps without cluttering up your application code. We’ll be achieving this by overloading the Console
object, something you find in almost every javascript program. This is a handy feature for real-time debugging as well as monitoring your running applications. It also serves as a prototype for modifying console statements or adding entirely new console types. I’ll be assuming you have at least have a basic familiarity with Node.js. The instructions vary slightly depending on if you’d like to use an Express server or an HTTP server, so I have provided a set of steps and an example for both. The inspiration for this project/hack was a blog post about overloading the console functions while preserving their functionality found here by Udi Talias.
Before we start, here are the links for the finished Express example and the finished HTTP example.
Steps for Express
We’ll start by taking a look at a sample Express application:
'use strict';<br>
// dependencies<br>
var http = require('http')<br>
, express = require('express');<p></p>
<p>// port and host<br>
var host = process.env.VCAP_APP_HOST || process.env.HOST || 'localhost';<br>
var port = process.env.VCAP_APP_PORT || process.env.PORT || 3000;</p>
<p>// create Express server<br>
var app = express();</p>
<p>// set up middleware<br>
app.use(express.bodyParser());<br>
app.use(express.logger('dev'));</p>
<p>// endpoint that creates logs to demonstrate web tail<br>
app.get('/', function(req, res){<br>
setInterval(function() {<br>
console.log("you would be seeing the logs of your app");<br>
}, 3000);<br>
setInterval(function() {<br>
console.log("right here.");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("as it runs");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("isn't that exciting?");<br>
}, 5000);<br>
res.end("Hello world. I'm demonstrating the functionality of /dash.html by logging test messages on an interval.");<br>
});</p>
<p>app.listen(port, host);</p>
<p></p>
Not much more than a basic Hello World Express application, that will create some console.log chatter for us to observe in our web tail. For reference, this is the starting file structure of our project:
C:\Users\IBM_ADMIN\console tailing\>tree /F<br>
Folder PATH listing<br>
Volume serial number is 1E2C-67D3<br>
C:.<br>
app.js<br>
manifest.yml<br>
package.json<br>
Now we can jump into installing our live console with the following steps.
-
Create two folders in your project folder, named lib
and public
. Drop console.js into lib
and drop tail.html in public
. Your file structure should now look something like this (feel free to ignore my stylesheets
folder, it isn’t required) :
C:\Users\IBM_ADMIN\console tailing>tree /F<br>
Folder PATH listing<br>
Volume serial number is 1E2C-67D3<br>
C:.<br>
│ app.js<br>
│ manifest.yml<br>
│ package.json<br>
│<br>
├───lib<br>
│ console.js<br>
│<br>
└───public<br>
│ tail.html<br>
│<br>
└───stylesheets<br>
index.css<br>
-
The only thing left to do is modify our app.js
slightly. What we’ll need to do is
-
add this line so that your Express server will serve static files from your public
directory:
app.use(express.static(__dirname + '/public'));<br>
-
replace the line of code telling your app to listen on the removed port
and host
variables with a line requiring console.js
and passing your Express server to it as a parameter.
// app.listen(port, host);<br>
var custom_console = require('./lib/console.js')(app);<br>
So that when you’re all done, it looks like this:
'use strict';<br>
// dependencies<br>
var http = require('http')<br>
, express = require('express');<p></p>
<p>// port and host<br>
var host = process.env.VCAP_APP_HOST || process.env.HOST || 'localhost';<br>
var port = process.env.VCAP_APP_PORT || process.env.PORT || 3000;</p>
<p>// create Express server<br>
var app = express();</p>
<p>// set up middleware<br>
app.use(express.bodyParser());<br>
// line for serving static files from public directory<br>
app.use(express.static(__dirname + '/public'));<br>
app.use(express.logger('dev'));</p>
<p>// endpoint that creates logs to demonstrate web tail<br>
app.get('/', function(req, res){<br>
setInterval(function() {<br>
console.log("you would be seeing the logs of your app");<br>
}, 3000);<br>
setInterval(function() {<br>
console.log("right here.");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("as it runs");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("isn't that exciting?");<br>
}, 5000);<br>
res.end("Hello world. I'm demonstrating the functionality of /dash.html by logging test messages on an interval.");<br>
});</p>
<p>// app.listen(port, host);<br>
// require to connect console.js by passing the express server<br>
// Anonymous Function that prefixes timestamps to console.log<br>
var custom_console = require('./lib/console.js')(app);<br>
</p>
And we’re done! Jump down to the Result’s section to test drive it.
Steps for HTTP
We’ll start by taking a look at a sample Node.js application using an HTTP server:
'use strict';<br>
// dependencies<br>
var app = require('http').createServer(handler)<br>
, fs = require('fs');<p></p>
<p>// port and host<br>
var host = process.env.VCAP_APP_HOST || process.env.HOST || 'localhost';<br>
var port = process.env.VCAP_APP_PORT || process.env.PORT || 3000;</p>
<p>function handler (req, res) {<br>
res.writeHead(200);<br>
triggerConsole();<br>
res.end("Hello world! I am demonstrating the live web console tail. Navigate to /tail.html and check it out.");<br>
}</p>
<p>// dummy function used to recreate standard application log "chatter"<br>
function triggerConsole() {<br>
setInterval(function() {<br>
console.log("you would be seing the logs of your app");<br>
}, 3000);<br>
setInterval(function() {<br>
console.log("right here.");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("as it runs");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("isn't that exciting?");<br>
}, 5000);<br>
}</p>
<p>app.listen(port, host);<br>
</p>
Not much more than a basic Hello World application, that will create some console.log
chatter for us to observe in our web tail. For reference, this is the starting file structure of our project:
C:\Users\IBM_ADMIN\console tailing\>tree /F<br>
C:.<br>
app.js<br>
manifest.yml<br>
package.json<br>
Now we can jump into installing our live console with the following steps.
C:\Users\IBM_ADMIN\console tailing>tree /F<br>
C:.<br>
│ app.js<br>
│ manifest.yml<br>
│ package.json<br>
│<br>
├───lib<br>
│ console.js<br>
│<br>
└───public<br>
│ tail.html<br>
│<br>
└───stylesheets<br>
index.css<br>
- Modify lines 13 and 16 in
tail.html
so that they point to the URL of your BlueMix application, for example:
<script src="https://.ng.bluemix.net/socket.io/socket.io.js"><p></p>
<p>var socket = io.connect("https://.ng.bluemix.net");<br>
</p>
-
The only thing left to do is modify our app.js
slightly. What we’ll need to do is
-
add these lines to your callback function passed when creating your HTTP server, in order to conditionally determine if you are navigating to tail.html
or not. We do this so that tail.html
can be served from our public
directory:
// detect if we are navigating to the tail or not<br>
if (req.url == "/tail.html") {<br>
fs.readFile(__dirname + '/public/tail.html',<br>
function (err, data) {<br>
if (err) {<br>
res.writeHead(500);<br>
return res.end('Error loading tail.html');<br>
}<br>
res.writeHead(200);<br>
res.end(data);<br>
});<br>
}<br>
-
replace the line of code telling your app to listen on the removed port
and host
variables with a line requiring console.js
and pass your HTTP server to it as a parameter.
// app.listen(port, host);<br>
var custom_console = require('./lib/console.js')(app);<br>
So that when you’re all done, it looks like this:
'use strict';<br>
// dependencies<br>
var app = require('http').createServer(handler)<br>
, fs = require('fs');<p></p>
<p>// port and host<br>
var host = process.env.VCAP_APP_HOST || process.env.HOST || 'localhost';<br>
var port = process.env.VCAP_APP_PORT || process.env.PORT || 3000;</p>
<p>function handler (req, res) {<br>
// detect if we are navigating to the tail or not<br>
if (req.url == "/tail.html") {<br>
fs.readFile(__dirname + '/public/tail.html',<br>
function (err, data) {<br>
if (err) {<br>
res.writeHead(500);<br>
return res.end('Error loading tail.html');<br>
}<br>
res.writeHead(200);<br>
res.end(data);<br>
});<br>
} else {<br>
// if not tail then generic endpoint<br>
res.writeHead(200);<br>
triggerConsole();<br>
res.end("Hello world! I am demonstrating the live web console tail. Navigate to /tail.html and check it out.");<br>
}<br>
}</p>
<p>// dummy function used to recreate standard application log "chatter"<br>
function triggerConsole() {<br>
setInterval(function() {<br>
console.log("you would be seing the logs of your app");<br>
}, 3000);<br>
setInterval(function() {<br>
console.log("right here.");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("as it runs");<br>
}, 1500);<br>
setInterval(function() {<br>
console.log("isn't that exciting?");<br>
}, 5000);<br>
}</p>
<p>// app.listen(port, host);<br>
// line requiring the external file lib/console.js passing it the expected parameters<br>
var timestamp_console = require('./lib/console.js')(app);<br>
</p>
And we’re done! Jump down to the Result’s section to test drive it.
Checking Out the Result
It’s time to see the fruits of our labor. Push your example of choice to BlueMix. Now navigate to your application’s route and append /tail.html
to it. You should be greeted by the web tail:
You can confirm everything is working properly by opening your application’s route in another tab and hitting that endpoint to trigger some logging functions. You should see your tail.html
populate itself with logs as they are called by your application. You can now debug your app in real-time or monitor it’s usage on the fly.