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\&gt;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.

  • Modify your package.json file in your project, and add the following dependencies if you aren’t already using them: socket.io and ansi-to-html. For example:

    "dependencies":<br>
    {<br>
        "express": "3.4.7",<br>
        "socket.io": "*",<br>
        "ansi-to-html": "0.1.1"<br>
    }<br>
  • 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&gt;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>
  • Modify lines 13 and 16 in tail.html so that they point to the URL of your BlueMix application, for example:

    &lt;script src="https://.ng.bluemix.net/socket.io/socket.io.js"&gt;<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 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\&gt;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.

  • Modify your package.json file in your project, and add the following dependencies if you aren’t already using them: socket.io and ansi-to-html. For example:

    "dependencies":<br>
    {<br>
        "express": "3.4.7",<br>
        "socket.io": "*",<br>
        "ansi-to-html": "0.1.1"<br>
    }<br>
  • 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&gt;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:
&lt;script src="https://.ng.bluemix.net/socket.io/socket.io.js"&gt;<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.

Categories

More from

IBM TechXchange underscores the importance of AI skilling and partner innovation

3 min read - Generative AI and large language models are poised to impact how we all access and use information. But as organizations race to adopt these new technologies for business, it requires a global ecosystem of partners with industry expertise to identify the right enterprise use-cases for AI and the technical skills to implement the technology. During TechXchange, IBM's premier technical learning event in Las Vegas last week, IBM Partner Plus members including our Strategic Partners, resellers, software vendors, distributors and service…

Kubernetes version 1.28 now available in IBM Cloud Kubernetes Service

2 min read - We are excited to announce the availability of Kubernetes version 1.28 for your clusters that are running in IBM Cloud Kubernetes Service. This is our 23rd release of Kubernetes. With our Kubernetes service, you can easily upgrade your clusters without the need for deep Kubernetes knowledge. When you deploy new clusters, the default Kubernetes version remains 1.27 (soon to be 1.28); you can also choose to immediately deploy version 1.28. Learn more about deploying clusters here. Kubernetes version 1.28 In…

“Teams will get smarter and faster”: A conversation with Eli Manning

3 min read - For the last three years, IBM has worked with two-time champion Eli Manning to help spread the word about our partnership with ESPN. The nature of that partnership is pretty technical, involving powerful AI models—built with watsonx—that analyze massive data sets to generate insights that help ESPN Fantasy Football team owners manage their teams. Eli has not only helped us promote awareness of these insights, but also to unpack the technology behind them, making it understandable and accessible to millions.…

Temenos brings innovative payments capabilities to IBM Cloud to help banks transform

3 min read - The payments ecosystem is at an inflection point for transformation, and we believe now is the time for change. As banks look to modernize their payments journeys, Temenos Payments Hub has become the first dedicated payments solution to deliver innovative payments capabilities on the IBM Cloud for Financial Services®—an industry-specific platform designed to accelerate financial institutions' digital transformations with security at the forefront. This is the latest initiative in our long history together helping clients transform. With the Temenos Payments…