Production Logging

Use Production Logging to provide users with a dialog to report issues and submit logs to technical support.

Production Logging collates JavaScript log messages, JavaScript errors, and StreamLink log messages. Log data is persisted in local web storage and included automatically with issue reports.

Use the 'Report an Issue' dialog in your application, or use the Production Logging APIs to write your own solution.

Reporting an issue

To report an issue, users click the Report an Issue command in the main menu. They are presented with a dialog in which to write a description of the issue. StreamLink and JavaScript logs are automatically included in the report submission.

To report an issue:

  1. Click Main Menu > Report an Issue.
  2. Enter a description of the issue.
  3. Click Report Issue.
    1. Your issue will be submitted to technical support and an issue ID will be returned to you. Please quote the issue ID in all further correspondence.

Getting started with Production Logging

Production Logging is enabled by default and integrates with a demonstration backend servlet, DemoLoggingServlet. The servlet saves each reported issue to a file and returns a random issue ID. This servlet is provided for demonstration purposes only and is not supported for use in production environments.

To use Production Logging in a live environment, you must write a backend service that integrates with your own issue tracking workflow. See Processing logs on the server.

If you want to deploy FX Professional without first writing a backend service to store reported issues, you must disable Production Logging.

To disable Production Logging:

  1. Set the LOGGING > ENABLED configuration property to false.
  2. Set the LOGGING > SHOW_REPORT_ISSUE_MENU_ITEM configuration property to false.

For more information on setting configuration properties, see Configuration.

Writing custom log messages

Production Logging automatically logs JavaScript errors and StreamLink log messages. To log your own messages, use the Fell module. Use Fell in preference to caplin/core/log/Log, which is now deprecated. Messages logged to caplin/core/log/Log are automatically copied to Fell.

Introducing Fell

Fell is a lightweight logging module that supports:

  • namespacing of log messages
  • multiple log stores (logging destinations)

Namespacing of messages provides great flexibility. Fell can be configured to log at different levels for different namespaces, and log stores can be programmed to filter messages by namespace.

The namespace caplin is reserved for internal use by Production Logging. Do not write messages to the caplin namespace or its derivatives.

For a more detailed overview of Fell, see the Fell page on GitHub and the Logging page on bladerunnerjs.org.

Production Logging's use of Fell

FX Professional registers two log stores with Fell: one for JavaScript and one for StreamLink. The StreamLink log store processes messages in the caplin.sljs namespace; the JavaScript log store processes messages in all namespaces other than caplin.sljs.

Each log store stores messages in its own ring-buffer. By default, each ring buffer stores 1000 lines (messages) before overwriting older lines. To change the capacity of the ring buffers, edit the configuration options LOGGING > STREAMLINK_STORAGE > MAX_LINES and LOGGING > JS_STORAGE > MAX_LINES.

The ring buffers are periodically persisted to local web storage. By default, the ring buffers are persisted every 10 seconds. To change the interval between writes to local web storage, edit the configuration options LOGGING > STREAMLINK_STORAGE > WRITE_INTERVAL and LOGGING > JS_STORAGE > WRITE_INTERVAL.

The capacity of local web storage varies between browser vendors. Microsoft Internet Explorer accepts up to 10MB of data per domain; Google Chrome and Mozilla Firefox accept up to 5MB per domain. By default, the StreamLink and JavaScript log stores are configured to persist a maximum of 2MB of ring buffer data each. Serialised ring-buffer data is trimmed, line by line, until it fits within the 2MB limit. To change the maximum size of persisted data, edit the configuration options LOGGING > STREAMLINK_STORAGE > MAX_SIZE and LOGGING > JS_STORAGE > MAX_SIZE.

For more information on configuring Production Logging, see Configuration.

Writing a message

To log a message using Fell, get the logger for a Fell namespace, and then call the logger method that is equivalent to the severity level of the message.

The example below gets a logger for the 'news-blade' namespace, and writes a message with a severity-level of INFO.

var fell = require('fell');
var logger = fell.getLogger('news-blade');
logger.info('Requesting ' + this.topic + ' news');

Changing logging levels

The default logging level for all namespaces is DEBUG. The DEBUG level maps to StreamLink's FINE level. Currently, no levels in Fell map to StreamLink's FINER and FINEST levels; StreamLink messages with these levels will not be logged by Fell.

To change log levels at application start up, edit the call to Fell.configure found in the definition of caplinps/logging/LoggingBootstrap

To change log levels at runtime, use the Fell.changeLevel method.

Sending logs to the server

The 'Report an Issue' form uses the caplinps/logging/LogPoster class to post issues and logs to the server. Use or extend this class to post logs for other use cases.

The LogPoster.sendLogs method sends 5 x-www-form-urlencoded fields to the server:

  • jslogs — A JSON-formatted array of strings
  • streamlinklogs — A JSON-formatted array of strings
  • message — The message the user entered into the 'Report an Issue' form
  • username — The username of the user reporting the issue
  • appversion — The application version (see below)

The LogPoster.sendLogs method returns a jQuery promise object.

  • On success, the promise object resolves to an object with a single property, requestID, which contains the requestID returned by your issue management system.
  • On failure, the promise object resolves to an object with a single property, error, which contains the error message returned by the server.

 The code sample below sends logs to the server and specifies handlers for success and failure.

var LogPoster = require('caplinps/logging/LogPoster');
var ServiceRegistry = require('br/ServiceRegistry');

var config = ServiceRegistry.getService('caplin.config-service').getProperty('LOGGING');

var logPoster = new LogPoster({url:config.BACKEND_URL});
logPoster.sendLogs()
    .done(function(data){
        window.console.log('Logs sent', data.requestId);
    })
    .fail(function(data){
        window.console.log('Error sending logs', data.error);
    });

Application version

The LogPoster.sendLogs method includes the application version in the data it posts to the server. The application version is retrieved from the service br.app-meta-service

To set the application version at build, include the command-line parameter -v <version> or --version=<version> to the brjs app-build command.

To override the application version at run time, include an appVersion property in the configuration object passed to the LogPoster constructor.

Processing logs on the server

To use Production Logging in live deployments, you will need to write your own backend service to process issue reports posted by caplinps/logging/LogPoster.

FX Professional includes a demonstration servlet, DemoLoggingServlet, which processes an issue posted by the caplinps/logging/LogPoster class. The servlet saves the issue to a file and returns a randomly-generated request ID.

The DemoLoggingServlet class is not suitable for production use.

To use DemoLoggingServlet, ensure that your J2EE web container has permission to write to the filesystem directory where DemoLoggingServlet saves issue reports. To review the location of this directory, see the servlet definition for DemoLoggingServlet in FX Professional's web.xml file.

Follow the guidelines below when writing your own backend service:

  • Collect as many of the following issue fields as you need:
    • username
    • message
    • appversion
    • jslogs
    • streamlinklogs
  • Consider collecting the following HTTP headers to aid in debugging:
    • User-Agent
    • Origin
    • Referer
  • The content type of the service's response must be 'application/json'.
  • On success, the service must return a JSON-encoded object that contains a single property, requestId. The value of the property is the ID of the issue in your issue tracking system. The Report an Issue dialog will display this ID to the user.
    • Example: {"requestId":"12345"}
  • On failure, the service must return a JSON-encoded object that contains a single property, error. The value of the property is the error message that you wish to return to the web browser. The Report an Issue dialog will log the error message to the browser's JavaScript console, and display a generic error message to the user.
    • Example: {"error":"There was an error processing your request."}

To get started with writing your own backend service, use the example code template below:

public class DemoLoggingServlet extends HttpServlet {

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("application/json");
        Map<String, String> responseJSON = new HashMap<>();

        // Collect form data and HTTP headers. Post this information to your issue tracking system.
        String username = request.getParameter("username");
        String message = request.getParameter("message");
        String appVersion = request.getParameter("appversion");
        String jsLogs = request.getParameter("jslogs");
        String streamlinkLogs = request.getParameter("streamlinklogs");
        String userAgent = request.getHeader('User-Agent');
        String origin = request.getHeader('Origin');
        String referer = request.getHeader('Referer');

        if (username == null || (jsLogs == null && streamlinkLogs == null)) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            responseJSON.put("error", "Missing required parameters 'username' and ('jslogs' or 'streamlinklogs')");
        } else {
            try {
                // Process the reported issue
                // ==========================
                //
                // Post the issue to your issue tracking system
                // and retrieve the issue's ID.
                String issueId = postIssue( ... );
                //
                // Prepare the response to the client
                response.setStatus(HttpServletResponse.SC_CREATED);
                responseJSON.put("requestId", issueId);
                //
            } catch (Exception e) {
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                responseJSON.put("error", e.getMessage());
            }
        }

        Gson Gson = new Gson();
        PrintWriter rw = response.getWriter();
        rw.println(Gson.toJson(responseJSON));
        rw.close();
    }

}

Configuring Production Logging

The default configuration settings for FX Professional are declared in the definition of the caplinx.AppConfig class, located at /default-aspect/src/caplinx/AppConfig.js. Production Logging's configuration is stored in the LOGGING property of caplinx.AppConfig.options. The code sample below shows the caplinx.AppConfig.options.LOGGING property, with other properties hidden for clarity:

caplinx.AppConfig = function() {
    .
    .
    this.options = {
        .
        .
        LOGGING: <object>
        .
        .
    };
    .
    .
};

To override the default configuration settings, add your changes to the caplinx.ExtendedAppConfig class, which extends caplinx.AppConfig. The definition of caplinx.ExtendedAppConfig is located at /default-aspect/src/caplinx/ExtendedAppConfig.js.

caplinx.ExtendedAppConfig = function() {
    caplinx.AppConfig.call(this);
    .
    .
    this.options.LOGGING = <object>;
    .
    .
};

LOGGING configuration property

The LOGGING property holds the configuration for Production Logging.

This property takes a single object as its value.

LOGGING object
Property Data Type Description
ENABLED Boolean Set to true to enable Production Logging.
BACKEND_URL String The URL of the back-end service to post logs to. Used by the LogPoster class.
SHOW_REPORT_ISSUE_MENU_ITEM Boolean Display a menu entry for Production Logging's "Report an Issue" dialog.
JS_STORAGE Object Configuration options for the JavaScript ring-buffer and local web storage. See LOGGING > JS_STORAGE object.
STREAMLINK_STORAGE Object Configuration options for the StreamLink ring-buffer and local web storage. See LOGGING > STREAMLINK_STORAGE.

The LOGGING > JS_STORAGE property takes a single object as its value:

LOGGING > JS_STORAGE object
Property Data Type Description
MAX_LINES Integer The maximum capacity of the ring buffer used for JavaScript log entries. When the ring buffer is at full capacity, adding a new line to the ring buffer overwrites the oldest line.
MAX_SIZE Integer

The maximum number of bytes to persist in the web browser's local web storage. If the serialised contents of the ring buffer exceeds MAX_SIZE in bytes, the data is trimmed, one line at a time, until its size is less than MAX_SIZE.

Note: JavaScript uses UTF-16 character encoding (2 bytes per character). A MAX_SIZE of 1048576 bytes (1MB) equates to 524,288 characters.

WRITE_INTERVAL Integer The time, in milliseconds, between writes to local web storage.

The LOGGING > STREAMLINK_STORAGE property takes a single object as its value:

LOGGING > STREAMLINK_STORAGE object
Property Data Type Description
MAX_LINES Integer The maximum capacity of the ring buffer used for StreamLink log entries. When the ring buffer is at full capacity, adding a new line to the ring buffer overwrites the oldest line.
MAX_SIZE Integer

The maximum number of bytes to persist in the web browser's local web storage. If the serialised contents of the ring buffer exceeds MAX_SIZE in bytes, the data is trimmed, one line at a time, until its size is less than MAX_SIZE.

Note: JavaScript uses UTF-16 character encoding (2 bytes per character). A MAX_SIZE of 1048576 bytes (1MB) equates to 524,288 characters.

WRITE_INTERVAL Integer The time, in milliseconds, between writes to local web storage.

Example:

caplinx.ExtendedAppConfig = function() {
    caplinx.AppConfig.call(this);
    .
    .
    this.options.LOGGING = {
        ENABLED: true,
        BACKEND_URL: './servlet/logger',
        SHOW_REPORT_ISSUE_MENU_ITEM: true,
        JS_STORAGE: {
            MAX_LINES: 1000,
            MAX_SIZE: 1048576, // 2 bytes per char = 2MB
            WRITE_INTERVAL: 10*1000 // 10 seconds
        },
        STREAMLINK_STORAGE: {
            MAX_LINES: 1000,
            MAX_SIZE: 1048576, // 2 bytes per char = 2MB
            WRITE_INTERVAL: 10*1000 // 10 seconds
    };
    .
    .
};