@8pattern/jlogger

a tiny & flexible logger for JS

Usage no npm install needed!

<script type="module">
  import 8patternJlogger from 'https://cdn.skypack.dev/@8pattern/jlogger';
</script>

README

jLogger

A tiny & flexible logger for JS

[toc]

Install

npm install -S @8pattern/jlogger

Usage

Demo with default appenders

import JLogger from '@8pattern/jlogger'
const logger = new JLogger()
logger.info('hello, world')

if you execute the previous codes, you will find the console prints a JSON like {"level":"INFO", ... ,"count":1,"content":"hello, world"} automatically.

Demo with appenders

import JLogger, { appender } from '@8pattern/jlogger'
const logger = new JLogger()
logger.Appender.register(new appender.FileAppender())
logger.info('hello, world')

Execute the previous codes, not only the console will print a JSON like {"level":"INFO", ... ,"count":1,"content":"hello, world"}, but also a log file (yyyy-mm-dd.log) can be found in same dir with the same JSON (in Node environment).

Procedure

Preparation

Before print logs by jLogger, a instance should be generated. It allows users to bind some arguments and configurations on the instance. The construction function can receive two arguments.

const logArguments = {}, logConfig = {}
const logger = new JLogger(logArguments, logConfig)
  1. logArguments <object>: receive some default arguments within this instance scope, i.e., each log will receive these arguments.

    In Node environment, the default value is:

    • pid: same as process.pid

    In browser, the default value are:

    • wid: a random number less than 100,000
    • url: the current route path, i.e., the href without protocol, host and port

    You CAN assign some custom arguments, such as: filePath, author and etc.

    const logArguments = { hello: 'world' }
    const logger = new JLogger(logArguments)
    logger.info('') // { ..., hello: 'world', ... }
    
  2. logConfig <object>: some configurations to change the logger behavior.

    Only the following configurations is valid.

    • category <JSON>

      Default value:

      {
          notice: 'NOTICE',
          action: 'ACTION',
          function: 'FUNCTION',
      }
      

    NOTE: you can change these category values or add some new categories. Deletion is useless (Because we use them inside).

    • level <JSON>

      Default value:

      {
          error: 'ERROR',
          warn: 'WARN',
          info: 'INFO',
          debug: 'DEBUG',
      }
      

    NOTE: Same as category, change values and add new levels works but Deletion is useless.

    • format <function>

      • argument: logArguments <JSON>
      • return: logString <string>

      Default value:

      (logArgs) => JSON.stringify(logArgs)
      
    1. The content of logArguments will be presented at the following section.
    2. If some log arguments don't wanted to be printed, such as some levels or categories, you can modify this format implementation.
    • printLevel <array>

      Default value:

      ['debug', 'info', 'warn', 'error']
      

    The elements must be the keys rather than values in logConfig.level

Control appenders

Pre-given appenders

Appenders illustrates the print methods of every logs. You can find some pre-given appenders from appender of the jLogger.

import { appender } from '@8pattern/jlogger'
const { Appender, ConsoleAppender, FileAppender } = appender
  • Appender

    Abstract class for every specific appender. Only a abstract method print is binded on it. When you design a custom appender, you MUST extend from this parent class (presented later).

  • ConsoleAppender

    Print the logs into the console. It's also the default appenders, so you won't do anything but work.

  • FileAppender

    Print the logs into the file (only works in Node).

    import JLogger, { appender } from '@8pattern/jlogger'
    const logger = new JLogger()
    logger.Appender.set([new appender.FileAppender()])
    

    It can be instantiated with some configurations as well.

    const fileAppender = new FileAppender({
        filePath: './',
        fileName: `${dateformat(Date.now(), 'yyyy-mm-dd')}`,
        fileExtension: 'log',
    })
    
    • filePath <string>: the revelant path of the log file.

      Default value: './'

    • fileName <string>: the name of the log file.

      Default value: ${dateformat(Date.now(), 'yyyy-mm-dd')}

    • fileExtension <string>: the extension of the log file.

      Default value: log

Update appenders

To activate an appender, you need process the logger.Appender, and it provides some methods to control registered appenders.

  • appenders <array>: store the registered appenders.

    const logger = new JLogger()
    console.log(logger.Appenders.appenders.length)
    // 1, there is a instance of ConsoleAppender by default
    
  • set <function>

    • argument: appenders <array>

    • return: currentRegisteredAppendersNumber <number>

    logger.Appenders.set([new ConsoleAppender(), new FileAppender()])
    

    NOTE:

    1. This method will replace all appenders.
    2. Any appenders which not extended from the abstract class Appender will be filtered.
  • get <function>

    • argument: null

    • return: appenders<array>

    logger.Appender.get() // === logger.Appender.appenders
    
  • register <function>

    • argument: appender

    • return: null

    logger.Appender.register(new FileAppender()) // logger.Appender.appenders.length === 2

    
    > NOTE:
    >
    > 1. Compare to set method, it only add a new appender RATHER THAN replace all of them.
    > 2. ONLY instance of abstract class Appender will make effect as well.
    > 3. If the appender has been registered, it won't add a new one.
    
    ```javascript
    logger.set([new ConsoleAppender()])
    // logger.Appenders.appenders.length === 1
    
    const fileAppender = new FileAppender()
    
    logger.Appender.register(fileAppender)
    // logger.Appenders.appenders.length === 2, register a new FileAppender instance
    
    logger.Appender.register(fileAppender)
    // logger.Appenders.appenders.length === 2, because it has been registered before
    
    logger.Appender.register(new FileAppender())
    // logger.Appenders.appenders.length === 3, a new FileAppender registered
    
    logger.Appender.register({})
    // throw a error, because the provided appender is illegal.
    
  • delete <function>

    • argument: appender

    • return: isSuccess <boolean>

    const fileAppender = new FileAppender()
    
    logger.Appender.set([fileAppender])
    // logger.Appenders.appenders.length === 1
    
    logger.Appender.delete(new FileAppender())
    // === false, logger.Appenders.appenders.length === 1, the given appender doesen't exist
    
    const fileAppender = new FileAppender()
    
    logger.Appender.delete(fileAppender)
    // === true, logger.Appenders.appenders.length === 0
    
Design a custom appender

If you want to customize a appender, you only need to extend your class from Appender and implement the print method.

import { appender } from '@8pattern/jlogger'

class CustomAppender extends appender.Appender {
    print(logArgs) {
        console.log(logArgs)
    }
}

jlogger.Appender.register(new CustomAppender())

Call to log

logArgument

The logArgument contains all fields of a log, and it may be used to format the logger or print in the appender. It must contains the following values.

  • level: the log level. Default value: logger.config.level.info.
  • timeStamp: current time stamp (without formation). Value: Date.now().
  • count: the number of log called. Value: automatically increased from 0.
  • content: the log content.

Besides, some custom arguments can be added from construction or log call function. And the arguments from call function has higher priority than the construction. But, "timeStamp" and "count" have the higher priority and can't be overridden.

const logger = new Logger({a: 1, b: 2})
logger.info('', { b: 3, c: 4 })
// { ..., a: 1, b: 3, c: 4, ... }

If you want change the time or count in final logs, add another arguments and change the format function.

Ordinary log method
  • log

    • argument: content <string>, logArgs <JSON>
    • return: finalLogArgs <JSON>
    logger.log('hello world', { a: 1 })
    // { a: 1, ... content: 'hello world', ... }
    
  • debug / info / warn

    • argument: content <string>, logArgs <JSON>
    • return: finalLogArgs <JSON>

    They are all sugar of log with levels.

    logger.debug('') // { ..., level: 'DEBUG', ... }
    logger.info('') // { ..., level: 'INFO', ... }
    logger.warn('') // { ..., level: 'WARN', ... }
    
  • error

    • argument: content <string> | <Error>, logArgs <JSON>
    • return: finalLogArgs <JSON>

    It is also the sugar of log with error level, but it can receive Error as content. If called with an Error, "errType" and "errDesc" will be added into finalLogArgs, and the stack of Error will be regarded as the content

    logger.error('')
    // { ..., content: '', ..., level: 'ERROR', ... }
    
    const err = new Error('Some errors')
    logger.error(err)
    // { ..., content: 'Error: Some errors at ...', ..., errDesc: 'Some errors', errType: 'Error', ... }
    
  • other level defined in config

    If some other levels defined in config, it provides these sugars as well.

    const level = {
        fatal: 'FATAL',
    }
    const logger = new JLogger({}, { level })
    logger.fatal('')
    // { ..., level: 'FATAL', ... }
    
Function log

Since function logs are so important but their same structures bored the coder, we provide two methods for function logs particularly.

  • logWrap

    • argument: wrapped <Function>, logArgs <JSON>
    • return: <Function>

    This method will print logs of the wrapped function input and output. If the function throws an error, it will print a error log automatically.

    const fun(a) {
        return a + 1;
    }
    
    const wrappedFun = jLogger.logWrap(fun, { t: 1 })
    
    wrappedFun(1)
    // { ..., content: 'INPUT: [1]',category: 'FUNCTION', funName: 'fun', t: 1, ...}
    // { ..., content: 'OUTPUT: 2',category: 'FUNCTION', funName: 'fun', t: 1, ...}
    
    const fun() {
      throw new Error('some errors')
    }
    
    const wrappedFun = jLogger.logWrap(fun)
    
    wrappedFun('hello')
    // { ..., content: 'INPUT: ['hello']', category: 'FUNCTION', funName: 'fun', ...}
    // { ..., category: 'FUNCTION', funName: 'fun', level: 'ERROR', ...}
    
  • logDecorator

    • argument: logArgs <JSON>
    • return: <Function>

    There is a proposal for decorator for class method,and we also provide a logDecorator which is based on logWrap.

    const log = logger.logDecorator({ test: 1 })
    
    class T {
        @log
        test1(a) {
            return a + 1
        }
        
        @logger.logDecorator({ test: 2 })
        test2() {
            throw new Error('some errors')
        }
    }
    
    const t = T()
    
    t.test1(1)
    // { ..., content: 'INPUT: [1]', category: 'FUNCTION', className: 'T', funName: 'test1', t: 1, ...}
    // { ..., content: 'OUTPUT: 2',category: 'FUNCTION', className: 'T', funName: 'test1', t: 1, ...}
    
    t.test(2)
    // { ..., content: 'INPUT: [2]', category: 'FUNCTION', className: 'T', funName: 'test2', t: 2, ...}
    // { ..., category: 'FUNCTION', level: 'ERROR', className: 'T', funName: 'test2', t: 2, ...}
    

Util

We provide some useful tools relevant to logs, which can be found from jLogger.util

import { util } from '@8pattern/jlogger'
const { dateformat, contentLengthFormat, logTag } = util

dateformat

  • argument:
    • date <Date> | <number>. Default value: new Date()
    • formatStr <string>. Default value: 'yyyy-mm-dd HH:MM:ss.l'
  • return: <string>

This method is a tiny function to format date. The usage is similar with the other mature library, i.e.,

y -> year
m -> month
d -> day
H -> hour
M -> minute
s -> second
l -> millisecond

For example:

dateformat(0, 'yyyy-mm-dd') // 1970-01-01
dateformat(new Date(0), 'yy-m-d') // 70-1-1
dateformat(0, 'HH:MM:ss') // 00:00:00
dateformat(0, 'H:M:s.l') // 0:0:0.0000

contentLengthFormat

  • argument:
    • content <string>.
    • lengthLimit <number> | <null>. Default value: null
    • replaceString <string>. Default value: ' <<<... $IGNORED_STRING_LENGTH chars are ignored ...>>> '
  • return: <string>
contentLengthFormat('123456789')
// 123456789, because lengthLimit === null

contentLengthFormat('123456789', 4)
// 12 <<<... 5 chars are ignored ...>>> 89

contentLengthFormat('123456789', 4, '...igonred...')
// 12...ignored...89

logTag

The tag for the template string for a log content. It will transform Function, Array and Object into more readable formation.

function fun(a) { return a + 1; }
`${fun}` // "function fun(a) { return a + 1; }"
logTag`${fun}` // "-Function[fun]-"
const arr = [1,2,3,4,5]
`${arr}` // "1,2,3,4,5"
logTag`${fun}` // "[1,2,3,4,5]"
const dict = { a: 1 }
`${dict}` // "[object Object]"
logTag`${dict}` // "{ a: 1 }"
class CustomClass {}
const t = new CustomClass()
`${t}` // "[object Object]"
logTag`${t}` // "-CustomClass-"

A complete example:

const obj = {
    arr: [1, 2, function() {}],
    fun() {},
}

`content is ${obj}` // "content is [object Object]"
logTag`content is ${obj}` // "content is {arr:[1,2,-Function-], fun:-Function[fun]-}"