Awesome
hexLog
hexLog contains logging system inspired by log4j written in Haxe
Find more information about hexMachina on hexmachina.org
Architecture
Main components
Applications using hexLog API will request a logger with a specific name from the LogManager. The LogManager will locate the appropriate LoggerContext and then obtain the Logger from it. If the Logger must be created it will be associated with the LoggerConfig that contains either: a) the same name as the Logger, b) the name of a parent package, or c) the root LoggerConfig. LoggerConfig objects are created from Logger declarations in the configuration. The LoggerConfig is associated with the LogTargets that actually deliver the LogEvents.
Logger hierarchy
The biggest advantage of using hexLog over standard trace
is the ability to disable certain log statements while allowing other to be printed without any change. This capability assumes that the logging space, that is, the space of all possible logging statements, is categorized according to some developer-chosen criteria.
Loggers and LoggerConfigs are named entities. Logger names are case-sensitive and they follow hierarchical naming rule.
Naming hierarchy
A loggerConfig is said to be an ancestor of another LoggerConfig if its name followed by a dot (
.
) is a prefix of a descendant logger name. A LoggerConfig is said to be a parent of a child LoggerConfig if there are no ancestors between itself and the descendant LoggerConfig.
For example, the LoggerConfig named "com.foo"
is a parent of the LoggerConfig named "com.foo.Bar"
. Similarly, "hex"
is a parent of "hex.log"
and an ancestor of "hex.log.Logger"
. This naming scheme should be familiar to most developers.
The root LoggerConfig resides at the top of the hierarchy. It's exceptional in that it always exists and is part of every hierarchy. A Logger that is directly linked to the roo LoggerConfig can be obtained as follows:
var rootLogger = LogManager.getLogger("");
// or more simply
var rootLogger = LogManager.getRootLogger();
All other Loggers can be retrieved by using the Logmanager.getLogger
static method by passing the name of the desired Logger.
LoggerContext
The LoggerContext acts as the anchor point of the logging system. Currently it's not possible to have multiple LoggerContexts but you can change the LoggerContext implementation by changing the value of a static property LogManager.context
.
In case you would like to use logging within the macro context you can use already prepared MacroLoggerContext
which has preconfigured message factory and messages designed specifically for printing expressions.
Configuration
LoggerContext has an active Configuration
. The Configuration contains all LogTargets, context-wide filters and LoggerConfigs.
Logger
As stated previously, Logger can be created by calling LogManager.getLogger
. The logger itself performs no direct actions. It simply has a name and is associated with a LoggerConfig. It extends AbstractLogger and implements the required methods. As the configuration is modified Loggers may become associated with a different LoggerConfig, thus causing their behavior to be modified.
Retrieving Loggers
Calling LogManage.getLogger
method with the same name will always return a reference to the exact same Logger object.
That means:
var x = LogManager.getLogger("something");
var y = LogManager.getLogger("something");
x
and y
refer to exactly the same Logger object. (x == y
is always true
)
Configuration of the hexLog environment is always done at application initialization.
hexLog makes it easy to name Loggers by software component. This can be accomplished by instantiating Logger in each class, with the Logger name equal to the fully qualified name of the class. This is a useful and straightforward method of defining loggers. As the log output bears the name of the generating Logger, this naming strategy makes it easy to identify the origin of the log message. However this is only one possible strategy of naming loggers and hexLog does not restrict the possible set of loggers. The developer is always free to name the loggers as desired.
For convenience hexLog provides a set of functions which you can import to generate getLogger
calls automatically. It's especially handy when using import.hx
file. (See example)
Additional convenience methods are getLoggerByClass
and getLoggerByInstance
which are useful in macro context where you can't use HexLog convenience class.
LoggerConfig
LoggerConfig objects are created when Loggers are declared in the logging configuration. The LoggerConfig contains set of Filters that must allow the LogEvent to pass before it will be passed to any LogTargets. It also contains reference to the set of LogTargets that should be used to process the event.
Filter
In addition to automatic LogLevel filtering hexLog provides Filters that can be applied:
- before control is passed to any LoggerConfig
- after control is passed to a LoggerConfig but before calling any LogTargets
- after control is passed to a LoggerConfig but before calling a specific LogTarget
- on each LogTarget
More information about filters and filtering
LogTarget
The ability to selectively enable or disable logging requests based on their logger is only part of the picture. hexLog allows logging requests to be printed to multiple destinations. In hexLog speak, an output destination is called LogTarget.
More information about log targets
Layout
More often than not, users wish to customize not only the output destination but also the output format. This is accomplished by associating a Layout with a LogTarget. The Layout is responsible for formatting the LogEvent to the user's wishes, whereas a log target takes care of sending the formatted output to its destination.
More information about layouts
Message
Internally in the system every log statement is represented by a Message object.
More information about messages
Simple examples
Logging
// Getting logger
var logger = LogManager.getLogger("LoggerName");
// Logging simple message
logger.debug("Some message");
// Logging message with parameters
var name = "World";
logger.debug("Hello {}", [name]);
Using convenience HexLog
class
If you're planning to log extensively it's a good practice to first get looger fro your class and then use it to send your messages
import hex.log.HexLog.*; // Import all convenience functions
// If you're planning to use logger extensively, first get logger for your class
var logger = getLogger();
// Gets replaced with:
//var logger = hex.log.LogManager.getLogger("my.current.class");
logger.debug("Hello world!"); // you can now use logger as usual
If you just need a simple debug statement and you're not worried about performance you can use even more abstracted functions.
import hex.log.HexLog.*; // Import all convenience functions
debug("Hello world");
info("Hello world");
warn("Hello world");
error("Hello world");
fatal("Hello world");
// Gets replaced with:
//hex.log.LogManager.getLogger("my.current.class").debug("Hello world");
// ... etc
Configuration
// -- You can see this working in hex.log.ConfigurationTest.hx
// Create a new configuration
var configuration = new BasicConfiguration();
// Create log targets
var traceTarget = new TraceLogTarget("Trace", null, new DefaultTraceLogLayout());
// Create a logger config and add targets
//(at this point we can also add filters to the configuration etc.)
var lc1:LoggerConfig = LoggerConfig.createLogger("hex", LogLevel.WARN, null, null); // Logger will only forward warnings and higher
lc1.addLogTarget(traceTarget, LogLevel.ALL, null); // Target will accept every event that arrives (in this case only warnings+ will be forwarded from the logger anyway)
configuration.addLogger(lc1.name, lc1); //Add logger config to the configuration
// Apply the configuration
LoggerContext.getContext().setConfiguration(configuration);
// Now you can request loggers and log as much you want and they will follow the rules set above
var logger = LogManager.getLogger("hex");
logger.debug("test"); // Fitered by config -> nothing will happen
logger.warn("test"); // will be logged
var logger2 = LogManager.getLogger("hex.log");
logger2.debug("test"); // Fitered by parent config -> nothing will happen
logger2.warn("test"); // will be logged
//NOTE: By default root logger is set to LogLevel.ERROR
var logger3 = LogManager.getLogger("something");
logger3.debug("test"); // Filtered by root logger -> nothing will happen
logger3.error("test"); // will be logged