Capturing Twilio Video JS SDK Logs

January 20, 2021
Written by

Capturing Twilio Video JS SDK Logs

This article is for reference only. We're not onboarding new customers to Programmable Video. Existing customers can continue to use the product until December 5, 2024.


We recommend migrating your application to the API provided by our preferred video partner, Zoom. We've prepared this migration guide to assist you in minimizing any service disruption.

Logging provides visibility into your application's behavior. This information is essential when troubleshooting operational issues. Traditionally, however, logging is only performed on the backend in a production build. Frontend applications usually hide client side debug logs and only expose warnings and errors by default. Even with debug logging enabled, there is no easy way to send logs to a remote server, making it hard to debug issues in production.

While application developers can build logging mechanisms to capture client side logs and send them to a remote server, the growing number of third party libraries and SDKs makes this harder to do. In most cases, third party libraries and SDKs have their own logging mechanisms and do not expose a way for developers to capture their specific logs.

Now with Twilio Video JS Logger, developers can intercept logs generated by the Twilio Video JS SDK. This allows for real-time log processing to easily monitor your frontend applications and see how they behave in production.

Getting Started with Twilio Video JS Logger

In this section, I’ll show you how to add the Twilio Video JS Logger to your existing Twilio Video JS application. If you are new to Twilio Video JS, check out our example application and get started in minutes. Let’s dive into the code!

Setup

The SDK uses the loglevel module and is exposed via Video.Logger API. Use this API to access internal loggers and perform actions as defined by the loglevel APIs.


const Video = require('twilio-video');
const { Logger } = Video;
const logger = Logger.getLogger('twilio-video');

Intercepting Logs

You will need to override the logger's original method factory to begin intercepting logs. With this approach, you have the option to process each log before it is printed out to the console. The following example shows how to add the prefix `[My Application]` to each log prior to connecting to a room.


const originalFactory = logger.methodFactory;
logger.methodFactory = function (methodName, logLevel, loggerName) {
  const method = originalFactory(methodName, logLevel, loggerName);
  return function (datetime, logLevel, component, message, data) {
    const prefix = '[My Application]';
    method(prefix, datetime, logLevel, component, message, data);
  };
};
logger.setLevel('debug');

const { connect } = Video;
connect(token, {
  name: 'my-cool-room'
}).then(function(room) {
  room.on('participantConnected', function(participant) {
    console.log(participant.identity + ' has connected');
  });
}).catch(error => {
  console.log('Could not connect to the Room:', error.message);
});

Example log output:

My Application] 2021-01-06T02:08:12.078Z info [connect #1] Connecting ...
[My Application] 2021-01-06T02:08:12.078Z debug [connect #1] Options ...
[My Application] 2021-01-06T02:08:12.079Z info [connect #1] LocalTracks ...
[My Application] 2021-01-06T02:08:12.415Z info [createLocalTracks #1] ...

Callback Parameters

The Logger callback returned in the method factory contains parameters provided by the SDK. You can use these parameters to gather more information about the log – and potentially act on it. For example, you may want to filter logs for a specific component and send them to a different server.

logger.methodFactory = function (methodName, logLevel, loggerName) {
  const method = originalFactory(methodName, logLevel, loggerName);
  return function (datetime, logLevel, component, message, data) {
    if (component.includes('connect')) {
      sendToServerFoo(datetime, logLevel, component, message, data);
    }
    method(datetime, logLevel, component, message, data);
  };
};

Here's a list of the callback parameters along with a brief description.

  • datetime - The current date and time in simplified extended ISO format.
  • logLevel - The current logging level. Possible values include debug, info, warn, and error.
  • component - The component where the log originated using `[name #count]` format, where name is the component name, and count is the instance count. For example, `[createLocalTracks #1]`.
  • message - The message that is being logged.
  • data - An optional data object which can be inspected for more information about the log. The example below shows how to use the data object when checking for signaling events. In the future, any new type of data will be published in the SDK changelog.
logger.methodFactory = function (methodName, level, loggerName) {
 const method = originalFactory(methodName, level, loggerName);

 return function (datetime, logLevel, component, message, data) {
   method(datetime, logLevel, component, message, data);
   if (message === 'event' && data.group === 'signaling') {
     if (data.name === 'waiting') {
       console.warn('Twilio\'s signaling server is busy.');
     } else if (data.name === 'connecting') {
       console.log('Connecting to Twilio\'s signaling server.');
     } else if (data.name === 'open') {
       console.log('Connected to Twilio\'s signaling server.');
     } else if (data.name === 'closed') {
       if (data.level === 'error') {
         const { payload: { reason } } = data;
         console.error('Connection to Twilio\'s signaling server abruptly closed:', data.reason);
       } else {
         console.log('Connection to Twilio\'s signaling server closed.');
       }
     }
   }
 };
};

Plugins

The loglevel module used by the SDK is a lightweight logging library that works in all modern browsers and NodeJS environments.

While loglevel isn't useful in all instances, its plugin system allows you to add features specific to your applications, such as formatting a log message. For example, in the getting started section, we are able to prefix each log with [My Application] by overriding the logger’s original method factory. This approach is basically how the plugin system works when using loglevel.

One common use case in client side logging is the ability to forward logs to a remote server to monitor your frontend applications. Using the plugin system, you can intercept the logs, transform them into your own format, and forward them to your server. Fortunately, there is a plugin that supports this use case. The loglevel-plugin-remote module allows loglevel to send logs to a server with very minimal configuration.

const remote = require('loglevel-plugin-remote');
const { Logger } = require('twilio-video');
remote.apply(Logger);

const logger = Logger.getLogger('twilio-video');
logger.setLevel('debug');

The example above demonstrates how to use loglevel-plugin-remote where the plugin is applied to the root Logger object before accessing the SDK’s internal logger. With this configuration, logs are automatically sent to /logger endpoint. You can update this endpoint and other configurations by following the loglevel-plugin-remote‘s APIs.

Start Collecting Logs!

By enriching your logs and centralizing them with a logging server, you can easily monitor your frontend applications and see how they behave in production. This is just one of the many use cases we’ve heard from our customers and can’t wait to see what comes next.

Please feel free to head over our GitHub repo and leave feedback.

Charlie Santos is a Software Engineer on the Programmable Voice and Video team at Twilio. He builds real-time communication web applications and libraries that help customers hit the ground running with Twilio and WebRTC. He can be reached at csantos@twilio.com.