Logger
About
This class is a fairly complete implementation of the winston v3 logger. As such, it is completely configurable and incredibly powerful.
Why log?
In the world outside of 4D, logging is a normal and critically important tool both during development and at runtime. For example:
While an interactive debugger is great to have, it is quite cumbersome if you need to track the changes to internal state at several points of code execution. With logging, you can visually see the change of state all in one place, all at once.
If you need to debug code that is time sensitive, you can’t easily stop execution in the debugger without potential side effects. With logging, you can not only debug such code without side effects, you can see how long the execution took.
If there is a race condition between processes, good luck tracking it down with the debugger! Logging is the only way to easily view interleaved execution of processes.
Most non-4D mission-critical server applications write an audit trail of information about the inner workings of the application to a log file on disk. This is an invaluable tool to monitor and debug the application.
Logging
A log message consists of at minimum two parts: a log .level
and a log .message
.
Log levels
There are five named log levels in Logger
, numbered from 0 to 4, prioritized in importance from highest to lowest, with "info" being the default:
Level | Priority | Typical usage |
---|---|---|
error | 0 | An unexpected condition from which there may be no recovery |
warn | 1 | Something to pay attention to, but isn’t critical |
info | 2 | General information about the workings of the application, audit trail |
verbose | 3 | Like "info", but more detailed |
debug | 4 | Detailed inner state |
Setting the level for your log message can be accomplished in one of two ways:
- Use one of the level-specific functions
- Pass the name of the level to the generic
.log()
function
// These are equivalent
$logger.info(_.format("deleting ${1} row(s)"; $rows.length))
$logger.log("info"; _.format("deleting ${1} row(s)"; $rows.length))
// These are equivalent
$logger.warn("Danger, Will Robinson!")
$logger.log("warn"; "Danger, Will Robinson!")
TIP
Typically you will want to use the level-specific functions, unless the log message level is dynamically determined.
Logger
allows you to define a .level
property on itself and on each transport. The .level
property specifies the maximum level of messages that a logger or a transport should log. For example, if the .level
property is "info", only "info", "warn", and "error" level messages will be logged.
Creating a custom logger
If you just want to log to a terminal (which is really only useful on macOS during development), you can use the preconfigured console
logger, which you access via the console()
method.
Most of the time, however, you will want to create your own custom logger. Because loggers are usually meant to be global, you will usually want to use Logger.create()
to create and register a named logger.
var $logger : cs.js.Logger
$logger:=Logger.create("logger"; $config)
Note that to be usable, a logger must be configured with at least one Transport
, and you will usually want to set a format as well. Configuration can either be done by passing a config object to the .create()
function (or Logger
constructor), or by calling the .configure()
function on a logger instance.
API
.addTransport()
.addTransport(transport: cs.js.Transport) : cs.js.Logger
Adds a shared copy of the non-shared transport to this logger’s list of transports. If an existing transport with the same name exists, the .transportWillClose()
function of the transport’s delegate (if defined) is called.
This logger is returned for call chaining.
.clear()
.clear()
Call this function when you want to “clear” the log.
This function calls the delegate .clear()
function (if defined) of all transports in this logger’s list of transports. Each transport’s delegate is responsible for determining what “clearing” means for that transport. For example, the ConsoleTransport
clears the terminal screen.
.clearTransports()
.clearTransports() : cs.js.Logger
Removes all transports from this logger’s list of transports, calling .transportWillClose()
(if defined) on each transport’s delegate.
Returns this logger for call chaining.
.close()
.close()
Call this function when you are done with a logger. The .transportWillClose()
function (if defined) of each transport’s delegate will be called. This gives you a chance to clean up system resources (such as files) that transport delegates might use.
This function is called for you by Logger.remove()
.
.configure()
.configure(config : Object) : cs.js.Logger
Configures (or reconfigures) the behavior of this logger, which is returned for call chaining.
IMPORTANT
This function can only be called on a shared global instance of cs.js.Logger
, after it has been added to the global registry, for example by using Logger.create()
.
The possible properties in config are:
Property | Type | Description |
---|---|---|
.defaultMeta | Object | Default meta info to add to every log message |
.format | Object | Logger.formats function that operates on the output |
.level | Text | The maximum log level that will be logged |
.silent | Boolean | If true, logging is disabled |
.transports | Collection<Transport> | The targets of log messages |
If
.defaultMeta
is in config, a shared copy is set for this logger. If.defaultMeta
is not in config, default meta for this logger is set to null.If
.silent
is not in config,This.silent
is set to false.For all other properties, if they are not in config, the corresponding properties of this logger are left untouched.
.debug()
.debug(message : Text { ; meta : Object })
A convenience function that calls This.log("debug"; message; meta)
.
.error()
.error(message : Text { ; meta : Object })
A convenience function that calls This.log("error"; message; meta)
.
.format
.format : Object
.format := format : Object
As a getter, returns the current format for this logger.
As a setter, sets the current format for this logger to a shared copy of format. If you only want to set the format, this is more convenient than using .configure()
.
.info()
.info(message : Text { ; meta : Object })
A convenience function that calls This.log("info"; message; meta)
.
.level
.level : Text
.level := level : Text
Gets or sets the maximum log level of messages that will be logged. If level is an invalid level name, the level remains unchanged.
.log()
.log(info : Object)
.log(level : Text; info : Object)
.log(level : Text; message : Text)
.log(level : Text; message : Text; meta : Object)
Logs a message to transports. If .silent
is true, the message is skipped.
In the first form, info should be an object with at least .level
and .message
properties. level should be a valid log level name, and message should be the string to be logged.
In the second form, level should be a valid log level name, and info should be an object with a .message
property, containing the string to be logged.
In the third form, level should be a valid log level name, and message should be the string to be logged.
The fourth form is like the third form, with meta being merged with the info
that is passed to formats. If meta contains a .message
property, it is assumed to be a string and is concatenated to message with a space in between.
TIP
If you configure the logger with the errors format, info in the first two forms may be an Error
instance.
Usually you will use one of the level-specific logging functions (.info()
, .warn()
, etc.) instead of this function. Those functions actually just call this function, passing level.
A message is only logged if the level is a valid name and the numeric value of the level is <= the current log level. For example, if the current log level is "info", and the supplied level is "verbose", the message will not be logged. Conversely, if the current log level is "warn", it will be logged.
If the message passes the level test, the .log()
function of each transport’s delegate is called.
NOTE
To ensure logging is an atomic operation and operates in a FIFO basis, this function uses the global worker method js_logWorker()
.
.removeTransport()
.removeTransport(transport : cs.js.Transport) : cs.js.Logger
.removeTransport(name : Text) : cs.js.Logger
Removes transport or the transport with name from this logger’s list of transports. If the transport exists in this logger’s transport list, its delegate’s .transportWillClose()
function (if defined) is called.
.silent
.silent : Boolean
.silent := silent : Boolean
Gets or sets the silent flag for this logger. If silent is true, no log messages are output.
.transports
.transports : Object
Returns an object whose keys are transport names and whose values are the transports belonging to this logger. You can use this to iterate over and access the transports in this logger, but modifying the object will not change the list of transports for this logger.
This property is read-only.
.verbose()
.verbose(message : Text { ; meta : Object })
A convenience function that calls This.log("verbose"; message; meta)
.
.warn()
.warn(message : Text { ; meta : Object })
A convenience function that calls This.log("warn"; message; meta)
.