@intermattory/logging v2.0.3
Intermattory Logging
JavaScript message logging system.
- Features
- Simplest setup
- Typical setup
- Configuration
- LogLevel
- Logger
- LogEntry
- Creating custom message formatter
- Creating custom log target
- Demo
Features
- Numbering logs
- Custom log numbers
- Predefined log levels: DEBUG, INFO, WARN, ERROR, FATAL
- Custom log levels support
- Multiple log targets
- Logging errors
- Log configuration per target
- Filtering log entries
To use the logger you need to first set it up. The best way to do that is to create a separate JavaScript module. The simplest logger setup would look like this:
Simplest setup
logging/online-shop-logger.js
import { Log } from '@intermattory/logging';
const log = Log()
log.start()
export const Logger = log.Logger
you can than use it like so:
import { Logger } from 'logging/online-shop-logger.js'
function ShoppingCart(items) {
const logger = Logger(ShoppingCart)
logger.info('Initializing shopping cart with items', items)
}
By default, the Logger logs all messages to the console.
Typical setup
Usually you need more than this. Intermattory logging system lets you configure multiple log targets, where you can log messages of different levels, with custom formatting, custom log number pattern, filtered with given filter function.
logging/online-shop-logger.js
import { Log, ArrayTarget, ConsoleTarget } from '@intermattory/logging';
import { Config } from './config.js';
import { LogServiceTarget } from './logging/log-service.js'
const consoleTarget = ConsoleTarget()
const arrayTarget = ArrayTarget()
const configuration = {
DEV: {
logTargets: [
{
target: consoleTarget,
level: LogLevel.ALL
},
{
target: arrayTarget,
level: LogLevel.ALL
}
]},
STAGE: {
logTargets: [
{
target: consoleTarget,
level: LogLevel.WARN,
filter: logEntry => logEntry.module !== 'authorization'
},
{
target: arrayTarget,
level: LogLevel.ALL
}
]},
PROD: {
logTargets: [
{
target: consoleTarget,
level: LogLevel.ERROR,
filter: logEntry => logEntry.module !== 'authorization'
},
{
target: arrayTarget,
level: LogLevel.ALL
},
{
target: LogServiceTarget(),
level: LogLevel.WARN
}
]}
}
const log = Log(configuration[Config.environment])
log.start()
export const Logger = (source, module) => log.Logger(source, { module, application: 'Web application' })
export const getLogs = () => arrayTarget.array
Usage example:
import { Logger } from 'logging/online-shop-logger.js'
function ShoppingCart(items) {
const logger = Logger(ShoppingCart, 'eCommerce')
logger.info('Initializing shopping cart with items', items)
const getDiscount = items => service.getDiscount(items)
.then(discount => {
logger.debug('Discount:', discount)
if (discount > 50)
logger.warn('Discount calculation formula is probably wrong')
})
.catch(error => {
const logEntry = logger.error.withError(error, 'Service failed to calculate the discount')
showAlert(`Your discount could not be calculated. Please try again later. See log #${logEntry.logNumber} for details`)
})
// ...
}
Intermattory logging doesn't provide the ServiceTarget
. If you need one, check Creating custom log target.
Configuration
By default, the logger logs all messages to the console
const DEFAULT_LOGGER_CONFIGURATION = {
logNumberGenerator: DEFAULT_LOG_NUMBER_GENERATOR,
logTargets: [
{
target: ConsoleTarget(),
level: LogLevel.ALL
}
]
}
to change that, pass your configuration to Log
's "constructor":
const consoleTarget = ConsoleTarget()
const arrayTarget = ArrayTarget()
const configuration = {
logTargets: [
{
target: consoleTarget,
level: LogLevel.ALL,
filter: logEntry => logEntry.source !== 'IgnoredSource'
},
{
target: arrayTarget,
level: LogLevel.INFO
}
]}
const log = Log(configuration)
log.start()
Log targets
ArrayTarget
is useful to list the logs in GUI. Check out the demo to see it in action.
Get the array from arrayTarget.array
.
ConsoleTarget
can be initialized with an optional messageFormatter
argument.
It lets you set the template of a message and customize information it contains. ConsoleTarget
uses TemplateMessageFormatter
as default.
You can also create your own formatter - check out Creating custom message formatter.
You can create your own log targets. Check out Creating custom log target to find out how.
Use filter
field to exclude the messages you don't want to log. It expects a function which returns true
or false
.
Filtering doesn't affect the log number incrementation.
Log number
By default, the log number is an integer incremented by 1 with every new log entry. Counting starts at the moment you call log.start()
.
You can customize it by setting the logNumberGenerator
field to a function which generates unique IDs.
The function takes the default integer log number and a date as arguments. It can return a number
or string
.
Logger does not validate the result of the function, so make sure you return unique values.
All log targets use the same log number generator.
Logger arguments
Logger
function takes 2 arguments: source
and optional parameters
.
The parameters
are sort of tags which can help you filter the log entries.
In different projects or even modules of the same project, you may need to log different information.
Sometimes you may want to log only the source of the message, sometimes the module name or message category.
You can define your Logger arguments by wrapping the original log.Logger
in a Higher Order Function.
You can also create multiple loggers with different signature:
logging/online-shop-logger.js
const log = Log()
log.start()
export const Logger = (source) => log.Logger(source, { application: 'Web application', workstationId: getWorkstaionId() })
export const ModuleLogger = (source, module) => log.Logger(source, { module, application: 'Web application' })
export const CategoryLogger = (source, category) => log.Logger(source, { category })
LogLevel
Log level is a number.
LogLevel object provides predefined log levels:
- LogLevel.ALL = 0
- LogLevel.DEBUG = 100
- LogLevel.INFO = 200
- LogLevel.WARN = 300
- LogLevel.ERROR = 400
- LogLevel.FATAL = 1000
Logger
Once you instantiated the Log you can use the Logger:
const log = Log()
const logger = log.Logger('LogSource')
Logger
function takes 2 arguments:
- mandatory
source
(function or string) - optional
parameters
(object)
source
If you pass a function as source, the logger will log its name. Be aware that if you minify your code, function names may be minified too and a string may be more reliable.
parameters
This can be anything. If you would like to log more information and don't want to pass them every time you log a message,
you can pass them as parameters
to the Logger:
const logger = Logger(ShoppingCart, { module: 'eCommerce'})
NOTE
OK, it can't be anything...
You can give your parameters any valid JavaScript name except date, logNumber, level, error, message, source
.
These are standard fields which describe every log entry.
Logger methods
Here are all available methods:
const logEntry = logger.log(customLevel, 'Message')
const logEntry = logger.fatal('Message')
const logEntry = logger.error('Message')
const logEntry = logger.warn('Message')
const logEntry = logger.info('Message')
const logEntry = logger.debug('Message')
With error:
const logEntry = logger.log.withError(error, LogLevel.ERROR, 'Message')
const logEntry = logger.fatal.withError(error, 'Message')
const logEntry = logger.error.withError(error, 'Message')
const logEntry = logger.warn.withError(error, 'Message')
const logEntry = logger.info.withError(error, 'Message')
const logEntry = logger.debug.withError(error, 'Message')
LogEntry
Every method returns a log entry, which of the following structure:
{
logNumber: number | string,
level: string,
message: [],
error?: {*},
date: Date,
source: function | string
}
This is very useful, if after an action, you need to give the user a feedback with some technical details, like the log number or error:
const logEntry = logger.error.withError(error, 'Parsing error')
showAlert(`An error occurred while opening the document (${logEntry.errorMessage}). For technical details go to the log No.${logEntry.logNumber}`)
TemplateMessageFormatter
import { TemplateMessageFormatter, ConsoleTarget } from '@inttermattory/logging'
const consoleMessageFormatFunction = (entry, formattedEntry) => `#${formattedLogEntry.logNumber} [${formattedLogEntry.level}][${formattedLogEntry.time}]${formattedLogEntry.parameters ? `[${formattedLogEntry.parameters}]` : ''} ${formattedLogEntry.source}: ${formattedLogEntry.message}${logEntry.error ? ` ${formattedLogEntry.errorName}
stack trace:
${formattedEntry.stackTrace}
` : ''}`
const consoleMessageTemplate = TemplateMessageFormatter(consoleMessageFormatFunction)
const consoleTarget = ConsoleTarget(consoleMessageTemplate)
The default formatter is stored in TemplateMessageFormatter.DEFAULT_MESSAGE_TEMPLATE
and here is how it looks like:
(logEntry, formattedLogEntry) => `#${formattedLogEntry.logNumber} [${formattedLogEntry.level}][${formattedLogEntry.time}]${formattedLogEntry.parameters ? `[${formattedLogEntry.parameters}]` : ''} ${formattedLogEntry.source}: ${formattedLogEntry.message}${logEntry.error ? ' ' + formattedLogEntry.error: ''}`
Creating custom message formatter
A message formatter has a very simple interface:
It must implement a format
method and return an array. format
takes one argument: a log entry
Example log entry:
{
date: [object Date],
logNumber: 11,
error: [object Error],
level: 300,
message: [object Array],
source: 'Demo',
...parameters
}
Your formatter may look like this:
const MyMessageFormatter = {
format(entry) {
let message = [];
// your implementation
return message;
}
}
It is important that the format
method returns an array, even if the formatted message is a string.
Creating custom log target
Every log target must have log
method, which takes 1 argument: log entry object.
Example log entry:
{
date: [object Date],
logNumber: 11,
error: [object Error],
level: 300,
message: [object Array],
source: 'Demo',
...parameters
}
Your log target may have the following shape:
const MyLogTarget = {
log(entry) {
// your implementation
}
}
You may want to use a message formatter. If so, please refer to ConsoleTarget
Demo
https://intermattory.bitbucket.io/logging/
What's included?
- ArrayTarget
- ConsoleTarget
- TemplateMessageFormatter
What's not included (yet)?
- Colors
- FileTarget
Extending
You can easily extend package features:
Tests
Run tests with npm test
License
MIT