Logger

About

This class is a fairly complete implementation of the winston v3open in new window 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:

LevelPriorityTypical usage
error0An unexpected condition from which there may be no recovery
warn1Something to pay attention to, but isn’t critical
info2General information about the workings of the application, audit trail
verbose3Like "info", but more detailed
debug4Detailed 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:

PropertyTypeDescription
.defaultMetaObjectDefault meta info to add to every log message
.formatObjectLogger.formats function that operates on the output
.levelTextThe maximum log level that will be logged
.silentBooleanIf true, logging is disabled
.transportsCollection<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).

Last Updated:
Contributors: Aparajita