Contextual Logger Names

About

After getting tired of wasting time finding and naming loggers, I embarked on a project to standardize and contextualize logger names. The overall goal was to have a standard naming convention to quickly pinpoint where the logs were coming from. Alas, I have finally dedicated time to creating a proper project to share with the community. You can find the exchange resource here and a detailed explanation below.

It should be noted this is a fairly brittle implementation due to heavy reliance on stack traces. Ignition 8.3 stack traces had slight syntactical differences compared to 8.1. This project was built using 8.3.2 with modified scripts from my original 8.1 version. I have not fully tested it with 8.1, but I made modifications with the intent of being backwards compatible.

Verified scopes

The scripts have been tested against the following categories and are included in the project:

  • Gateway events
    • Message handler
    • Scheduled script
    • Tag change
    • Timer script
    • Shutdown
    • Startup
    • Update
  • Perspective session events
    • SessionKeyEvent
    • Session message
    • Authentication challenge
    • Page Startup
    • Shutdown
    • Startup
  • Perspective views and components
    • OnStartup*
    • OnShutdown*
    • Properties*
    • Property change event*
    • Message handler*
  • WebDev endpoints
    • Get/Post/Put/Delete/Head/Options/Patch/Trace
  • Project library scripts
    • Function
    • Class
    • Class Method

Anything denoted with '*' is a special case where sometimes there is no way to determine an exact path based off a reference object and stack trace. This is especially important for perspective due to the call stack. For example, if additional context is not passed to the ReferenceConstructor class, a view's OnStartup script would only resolve to the view's name. There is no way to know we are in the view's OnStartup scope.

Perspective Accelerometer, Barcode, Bluetooth, NFC events should be identifiable by the stack trace method name, but I did not have a mobile device to test with at the time.

Classes

ReferenceConstructor - Used to construct the path based off a reference object and/or stack trace. The init parameters are described below:

  • reference- A reference to the object to be used for introspection. This should be the self parameter when called from perspective or a class.
  • context- Additional context to be appended to the end of the generated path as a new node.
  • offsetStack- The number of stacks that will be ignored at the end of the stack trace. Use this to account for any inheritance/misdirection patterns. For example, when calling the Loggerclass, it automatically offsets the stack by 1 because of the call chain.
  • expandPerspectivePath- Retrieves the full designer path for both the view and the component it was called from (assuming reference is the perspective object)

Logger- Wrapper class on the built-in system.util.getLogger object. It also inherits the ReferenceConstructor class to generate the logger's name. Additional parameters outside the inherited class are described below:

  • echo- If true, will print the log to the console (useful for script console debugging)

Path Generation

Below is the expected return format for the different reference object source types.

Any white space will be replaced with a "_" in the returned path to make it play nice with the log viewer.

  • Perspective View - {ProjectName}.Perspective.{ViewName}
  • Perspective Component - {ProjectName}.Perspective.{ViewName}.{Component}
  • Project Script Function - {ProjectName}.Scripting.{ScriptPath}.{ScriptName}
  • Project Script Class - {ProjectName}.Scripting.{ScriptPath}.{ClassName}
  • Gateway Event - {ProjectName}.Scripting.{event type}_{ResourceName}
  • Session Event - {ProjectName}.Perspective.{event type}
  • WebDev - {ProjectName}.WebDev.{WebDevPath}_{method}
  • String - {ProjectName}.{reference}

Here is an example of what it looks like on the gateway logs page. Hoving over the name will give you the full path, in this case, to the view.

image

Logging Levels

Utilizing the Loggingclass provides some additional benefits when configuring loggers in the gateway. Since the names are structured, you can easily change the log levels based on the hierarchy. In the example below, I changed the logging level for all the children by just changing the parent contextual-logger-names.Perspective.Views.ContextualLoggerNames

Perspective UI

I built this simple interface to do my testing in 8.3. It uses a couple of methods and message handlers to capture the context and capture the log. You can expand the sub view to easily see what name it generated, and the FULL stack trace.

I will do my best to keep this updated and look forward to the community's feedback and ideas!

Your hierarchy implies that you have substantial code written directly in events. I have long advocated that event scripts be one-liners that delegate to the project library, for numerous reasons. In other words, there should be no code in any event script that needs its own logger, and I would discourage anyone from going down this path.

Each project library script can have one or more logger "constants" established outside any functions for efficient use throughout. I recommend the following at the top of each library script to automate logger names:

logger = system.util.getLogger(system.util.getProjectName() + ':' + system.reflect.getModulePath())

That latter relies on my Integration Toolkit, but you can achieve the same accessing the Jython frame.

(My toolkit also includes efficient conversion of jython exceptions to java throwables, to produce nice backtraces with java's loggers. See my later.py script for a pure jython implementation.)

Thanks @pturmel. standards and a little discipline go a long way to help manage and maintain applications.