Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement logging helper #3

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Implement logging helper #3

wants to merge 1 commit into from

Conversation

nikolai-laevskii
Copy link

Helper utility for better logging

Overview

This utility introduces following features:

  1. Log contexts - allow to define custom contexts which would be displayed before any log. Useful for outlining different sections of logs and clarifying the which context the log is related to
  2. Log levels - allow to define different logging level for logging instance and toggle between silent and verbose logging modes (as well as anything in-between)
  3. Log outputs - allows to define custom outputs for logging instance with specialized adapter modules

Usage

Initializing logger instance
    const logger = new Logger({
     // define logging level: how verbose logging would be of 0-3 values
      level: Logger.LOG_LEVELS.INFO,
     // provide one or multiple output adapters: where logs would be outputted
      outputs: [new Logger.OUTPUTS.CoreOutput()]
    })
Basic logging - behavior is equivalent to core module
   /** These methods rely on logging levels **/
   
   // level >= 0
    logger.error('test')
    
    // level >= 1
    logger.warning('test')
    
    // level >= 2
    logger.notice('test')
    
    // level >= 3
    logger.info('test')
    
    /**
      debug does not rely on logging level
      is turned on/off with RUNNER_DEBUG
      environment variable, just like in core module      
     **/
    
    logger.debug('test')
Grouping (collapsable groups supported by github actions)

Just like in core module collapsable groups can be used by invoking methods startGroup and endGroup, everything in-between will be placed in collapsable group. Behavior is also the same as in core module: declaring new group without explicitly closing first one will close previous group implicitly and start a new one, invoking endGroup when no group is active will result in no effect.

    logger.info('outside group')
    logger.startGroup('group')
    logger.info('within group')
    logger.endGroup()
    logger.info('outside group')

Additional features for better readability in environments where collapsable groups are not supported.

Core addapter accepts one parameter which indicates whether groups are supported.

    const logger = new Logger({
      level: Logger.LOG_LEVELS.INFO,
      outputs: [new Logger.OUTPUTS.CoreOutput(false)]
    })

If not, instead of outputting commands, messages will be displayed with title of a group in parentheses:

    // enableGrouping = true (default)
    logger.info('outside group') // -> outside group
    logger.startGroup('group') // -> ::group::group
    logger.info('within group') // -> within group
    logger.endGroup() // -> ::endgroup::
    logger.info('outside group') // -> outside group
    
    // enableGrouping = false
    logger.info('outside group') // -> outside group
    logger.startGroup('group') // ->
    logger.info('within group') // -> [group] within group
    logger.endGroup() // ->
    logger.info('outside group') outside group
Contexts

Contexts allow to write additional information in [parenthesis] before main logging message to help navigating through logs. Contexts are used much the same as groups:

    logger.startContext('Context 1')
    logger.info('Hello, world!') // -> [Context 1] Hello, world!
    // Unlike groups contexts can be stacked within each other
    logger.startContext('Context 2')
    logger.info('Hello, world!') // -> [Context 1] [Context 2] Hello, world!
    logger.endContext()
    logger.info('Hello, world!') // -> [Context 1] Hello, world!
    logger.endContext()

Contexts are heirarchically lower the groups. Therefore, if one or more contexts are defined within the group, they will stop being active as soon as group is closed:

    logger.startGroup('Some group') // -> ::group::Some group
    logger.startContext('Context 1')
    logger.info('Hello, world!') // -> [Context 1] Hello, world!
    logger.startContext('Context 2')
    logger.info('Hello, world!') // -> [Context 1] [Context 2] Hello, world!
    // Removes contexts as well
    logger.endGroup()
    logger.info('Hello, world!') // -> Hello, world!

If context was declared outside of a group, the group title will inherit prefixes and all messages within group will appear without context as they now all would be under the same title:

    logger.startContext('Context 1')
    logger.startGroup('Some group') // -> ::group::[Context 1] Some group
    logger.info('Hello, world!') // -> Hello, world!
    logger.startContext('Context 2')
    logger.info('Hello, world!') // -> [Context 2] Hello, world!
    // Removes only 'Context 2' as it was declared within group
    logger.endGroup() // ::endgroup::
    logger.info('Hello, world!') // -> [Context 1] Hello, world!
Wrapper functions for contexts and groups

withGroup
withGroupSync
withContext
withContextSync

Are serving the purpose of automating invocation of closing function. sync and async versions have the same semantics and api and serve for wrapping synchronous and asynchronous code respectively.

logger.info('foo') // -> foo
logger.withContextSync('bar', () => {
  logger.info('foo') // -> [bar] foo
})
logger.info('foo') // -> foo

They also do not allow to go lower than context or group defined by them in order to keep behavior consistent and avoid unnecessary debugging

logger.info('foo') // -> foo
logger.withContextSync('bar', () => {
  logger.startContext('baz')
  logger.info('foo') // -> [baz] [bar] foo
  logger.endContext()
  logger.info('foo') // -> [bar] foo
  logger.endContext() // has no effect
  logger.info('foo') // -> [bar] foo
})
logger.info('foo') // -> foo

Therefore, contexts defined by wrapper-functions would not allow to remove that particular context within the same function and groups defined by wrapper-functions would not allow to end group defined within that same function as well as switching to a different group.

@nikolai-laevskii nikolai-laevskii self-assigned this Aug 21, 2023
@dsame dsame self-requested a review September 6, 2023 08:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants