logbox
LogBox: The Enterprise ColdFusion Logging Library
Covers up to version 1.4
Introduction
LogBox is an enterprise ColdFusion logging library designed to give you flexibility, simplicity and power when logging or tracing is needed in your applications. LogBox is part of the ColdBox Platform 3.0 suite of services and libraries. LogBox allows you to easily build upon its logging framework in order to meet any logging or reporting needs your applications has. ColdFusion has the very basic cflog tag that you can use in your applications to leverage file logging but it has many limitations. LogBox allows you to create multiple destinations for your loggings and even be able to configure them or change them at runtime.
Almost every application needs some kind of logging or tracing capabilities and we believe that LogBox fills that void. Although you always have to remember that over-use of logging can slow down an application. However, LogBox offers you the capabilities to filter out or be able to cancel logging noise a-la-carte. LogBox was inspired by the original logging capabilities in ColdBox and in the Log4j project.
Some Resources
Need for Logging
Almost every single application needs some kind of logging and tracing capabilities. One can usually use the now famous cflog or cftrace tags but you can reach a limitation very fast. What if you needed to log only certain severity levels for a particular cfc or piece of code? What if you needed that severity to advice you via SMS or Twitter, yes Twitter? Well, you would have to build all of these advising and logging capabilities yourself. Also, inserting log statements is tedious, time consuming and sometimes it just pollutes our real code. What if you wanted to turn it off easily, or reconfigure it? Well, you can obviously feel the pain now as we all have dealt with these situations. Therefore, LogBox has been built.
What can LogBox do?
- LogBox can handler the inserting of logging or tracing statements in your applications with a very easy to use API and gives you the ability to manage it externally without touching application code.
- You can configure LogBox via the following means:
- A programmatic configuration file (cfc)
- An xml configuration file
- LogBox categorizes your logging or tracing statements according to user-defined categories that can be configured at runtime or pre-runtime. All of these categorizations can have their own logging level ranges and even their own destination points or what we refer to as LogBox Appenders.
- LogBox Appenders are the different destination points you can configure for your logging or tracing statements. LogBox also offers a basic extensible API so you can build and extend upon the Appender framework according to your logging or tracing needs. This gives you complete control and flexibility of how to expand LogBox. Some appenders included in LogBox can log to the following destinations: File, Database, Twitter, Sockets, Email, ColdFusion logging, System Console, and much more.
- LogBox also facilitates the creation of your very own customized message formats via Layouts. You can create a Layout component that can be configured in to ANY LogBox appender so it can spit out your very own customized messages.
- LogBox can be instantiated as many times as you want and used as many times as you like in a single application. There are no restrictions upon its usage.
- LogBox allows for category inheritance according to component and package conventions.
How does LogBox work?
LogBox has four main components: LogBox, Logger, Appenders and Layouts. These 4 components work in unison to deliver the logging and tracing of messages and to control how they are logged. You will mostly be dealing with the Logger component as through it you will send your statements. Users can extend LogBox and build their own appenders and layouts.
LogBox
LogBox is the core framework you need to instantiate in order to work with logging in your application. You have to instantiate it with a LogBoxConfig object that will hold your logging configurations. To configure the library you can either do it programmatically or via an xml file, the choice is yours. After the library is instantiated and configured you can ask from it a named Logger object so you can start logging or tracing messages.
You have two ways to use LogBox:
- Standalone Framework
- Within a ColdBox application
If you have downloaded LogBox as a standalone framework, then the initial namespace for the core is logbox.system. If you are using it from within the ColdBox core then the initial namespace is coldbox.system. This allows you to use logbox as a standalone framework if you so desire. So please verify your installation before proceeding. Also note that ColdBox application users already have an instance of LogBox created for you in every application and it is stored in the main application controller: controller.getLogBox()
Note: Most of the examples shown in this documentation refer the default framework namespace of coldbox.system. However, if you are using LogBox as a standalone framework, then the namespace to use is logbox.system. Happy Coding!
// Create the config object with an XML configuration file config = createObject("component","coldbox.system.logging.config.LogBoxConfig").init(expandPath('logbox.xml')); // Create logbox instance logBox = createObject("component","coldbox.system.logging.LogBox").init(config); //Standalone Example // Create the config object with an XML file config = createObject("component","logbox.system.logging.config.LogBoxConfig").init(expandPath('logbox.xml')); // Create logbox logBox = createObject("component","logbox.system.logging.LogBox").init(config);
Appender
An appender is an object that LogBox uses in order to log statements to a destination repository. All appenders act as destinations that can be from databases, JMS, Twitter, files, consoles, sockets, etc. Their job is to take the logged message and store it or send it somewhere. LogBox comes bundled with the following appenders that can be found in the package coldbox.system.logging.appenders:
| Appender | Description |
|---|---|
| AsyncFileAppender | An Asynchronous file appender |
| AsyncRollingFileAppender | An Asynchronous file appender that can do file rotation and archiving |
| CFAppender | Will deliver messages to the coldfusion logs |
| ColdBoxTracerAppender | Will deliver messages to the ColdBox Tracer Panel in the ColdBox debugger |
| ConsoleAppender | Will deliver messages to the console via system.out |
| DBAppender | Will deliver messages to a database table. It can even auto create the table for you. |
| EmailAppender | Will deliver messages to any email address |
| FileAppender | Will deliver messages a file |
| RollingFileAppender | A file appender that can do file rotation and archiving |
| ScopeAppender | Will deliver messages to any ColdFusion scope you desire |
| SocketAppender | Will connect to any server socket and deliver messages |
| TwitterAppender | Can either send direct messages to a twitter user or update a status of a twitter user. |
| TracerAppender | Will deliver messages to the coldfusion tag cftrace. |
You can define 1 or all of these appenders in LogBox at any point in time. You can even register as many instances of any appender by just naming them differently. Here are some samples on how to configure them programmatically and via the simple configuration CFC into the configuration object:
Programmatic Approach
//Adding appenders props = {filePath=expandPath("/coldbox/testing/cases/logging/tmp"),autoExpand=false,fileMaxArchives=1,fileMaxSize=3}; config.appender(name='MyAsyncFile',class="coldbox.system.logging.appenders.AsyncRollingFileAppender",properties=props); //twitter props = {username="myapp",password="",logType="status"}; config.appender(name='TwitterAppender',class="coldbox.system.logging.appenders.TwitterAppender",properties=props); props = {username="coldbox",password="",logType="dm",dmUser="coldbox"}; config.appender(name='DMTwitter',class="coldbox.system.logging.appenders.TwitterAppender",properties=props); //socket props = {host="localhost",timeout="3",port="444",persistConnection=false}; config.appender(name='SocketAppender',class="coldbox.system.logging.appenders.SocketAppender",properties=props);
Configuration CFC approach
function configure(){ logBox = { // Register Appenders appenders = { MyAsycFile = { class="coldbox.system.logging.appenders.AsyncRollingFileAppender", properties={ filePath=expandPath("/coldbox/testing/cases/logging/tmp"),autoExpand=false,fileMaxArchives=1,fileMaxSize=3 } }, TwitterAppender = { class="coldbox.system.logging.appenders.TwitterAppender", properties = { username="myapp",password="",logType="status" } } DMTwitter = { class="coldbox.system.logging.appenders.TwitterAppender", properties = { username="coldbox",password="",logType="dm",dmUser="coldbox" } }, SocketAppender = { class="coldbox.system.logging.appenders.SocketAppender", properties = { host="localhost",timeout="3",port="444",persistConnection=false } } } }; }
Another important fact about appenders is that you can extend them or create new ones by just leveraging our basic API. This means that just by extending our core appender class: coldbox.system.logging.AbstractAppender and implementing the init() and logMessage() methods, you are on your way of appender development. We will cover this in detail in the next few sections.
Logger
The logger component is a named entity that you will use to log or trace messages to. The logger will then be responsible for deciding if the severity level of the message is adequate for redirecting to a destination via its attached appenders. If it is, then the logger will mediate the message(s) to the configured appender(s) for delivery. There are two types of loggers in LogBox:
- Root Logger - The default configured logger (mandatory)
- Category or Named Loggers - A logger that is created with a specific category name. You can define as many as you want.
LogBox also requires you to configure what we call a Root Logger. A root logger is the default logger that all named category loggers inherit from. This means that if you request for a logger with a name you have NOT configured before hand, then you will be basically using the configured root logger. The root logger is very easy to define as it only needs a range of severity levels that it can respond to and a list of appenders. However, remember that defining the root logger is mandatory.
// Configuring some appenders and the root logger config.appender(name="Console",class="coldbox.system.logging.appenders.ConsoleAppender"); // root logger config.root(levelMin=config.logLevels.FATAL, levelMax=config.logLevels.INFO, appenders="Console"); // Or using simple CFC logBox = { appenders = { Console = { class="coldbox.system.logging.appenders.ConsoleAppender" } }, root = {levelMin="FATAL", levelMax="INFO", appenders="*"} } //Asking logbox for the root logger Logger = logBox.getRootLogger();
As we said before, loggers are named entities. This means that the name you provide for a logger is what we call the logger's category name. This category can be the name of the component or class you are logging about or it can be anything you would like to distinguish uniquely.
Important: It is best practice to name your categories with the exact path notation of the component. Ex: coldbox.system.plugins.BeanFactory. For simplicity, you can just pass in the object reference and LogBox will figure out the name for you.
Let's say that I want to log messages in the SES interceptor whose class is coldbox.system.interceptors.SES. Then I would do the following to request a logger for usage in my ses interceptor:
// writing out the category logger = logBox.getLogger('coldbox.system.interceptors.SES'); //log an info message logger.info("Hello from info land");
However, you can simply the code above by passing the instance of where you are logging from. LogBox will then use the objec'ts fully qualified name, via inspection, and use that instead. This approach is much better and our PREFERRED approach as this approach will support refactorings or object name changes.
logger = logBox.getLogger(this); //log an info message logger.info("Hello from info land");
As you can see, it is very easy to get a named logger from LogBox. One important question is: What severity messages will the logger log and also to which destinations? Well, since we did not define in our configuration a coldbox.system.interceptors.SES category, LogBox will use the root logger for operation. Thus it will use the root logger's severity level range and configured appenders. So if we wanted to define a category we can define it in various ways via programmatic approach or xml. Let's look at the programmatic approach first:
//register a list of categories that respond to FATAL messages //only using the root logger's appenders config.fatal("coldbox.system.controller","mycfc","com.model.mycfc"); // log for errors only using the root logger's appenders config.error("mycfc","com.model.mycfc"); //log for info only using the root logger's appenders config.info("org.model.component"); //register a more granular category with levels and appenders // Log messages that are from severity 0-1 (fatal - error) only to the twitter appender config.category(name="com.model.myservice",levelMax=config.logLevel.ERROR,appenders="MyTwit"); //log all email service messages to the MyLogFileAppender and the Console. config.category(name="org.model.EmailService",appenders="MyLogFileAppender,Console");
This is the same but using the simplified Data CFC Approach:
logBox = {
//register a list of categories that respond to FATAL messages
fatal = ["coldbox.system.controller","mycfc","com.model.mycfc"],
// log for errors only using the root logger's appenders
error = ["mycfc","com.model.mycfc"],
//log for info only using the root logger's appenders
info = ["org.model.component"],
//Register categories granularly:
categories = {
"com.model.myservice" = {levelMax="ERROR",appenders="MyTwit" },
"org.model.EmailService" = {appenders="MyLogFileAppender,Console"}
}
};
As you can see, you have the option to create very granular categories where you can choose a level range and/or appenders, or just very easily tag a category to listen to only one specific type of severity using the severity methods in the config object. The methods available are the same as the Severity column in the severity levels chart.
Logger Category Inheritance
What in the world is this? Well, since we have a convention that category names should be in dot-notation form according to component or functionality, we can use a category pseudo-inheritance for logging levels and appenders. Ok, that doesn't really tell you much, I know, but the following example will clarify this.
The overall premise is that when you request a logger with a category name, LogBox will search for its configuration, if it does not find it it will try to locate its closest ancestor for logging levels and appenders, if it cannot find one, the it will rely on the root logger information. Let's say we define some categories like this:
// Configured Appenders: FileAppender, ConsoleAppender, TwitterAppender config.category(name="coldbox.system",levelMin=config.logLevels.INFO,appenders="console"); config.error("coldbox.system.plugins");
Then let's say we request the following logger objects:
logger = logBox.getLogger("coldbox.system.plugins.BeanFactory"); logger.info("hello info"); logger.error("wow and error occurred"); logger = logBox.getLogger("coldbox.system.interceptors.SES"); logger.info("hello info"); logger.debug("a cool debug message");
Note: All example code snippets are using a getLogger('categoryname') call instead of our preferred approach of getLogger(this) because we want to showcase which category we are talking about. Please take this into consideration.
| Category | Configured Levels | Assigned Levels | Appenders |
|---|---|---|---|
| root | FATAL-DEBUG | FATAL-DEBUG | console,file,twitter |
| coldbox.system | INFO-DEBUG | INFO-DEBUG | console |
| coldbox.system.plugins | ERROR | ERROR | * |
| coldbox.system.interceptors.SES | NONE | INFO-DEBUG from coldbox.system | console from coldbox.system |
| coldbox.system.plugins.BeanFactory | NONE | ERROR from coldbox.system.plugins | * |
Since we requested the category: coldbox.system.plugins.BeanFactory, LogBox tries to locate it, but it has not been defined, so it takes off the last item in the category name. Now it will search for a category of: coldbox.system.plugins via pseudo-inheritance. However, now coldbox.system.plugins HAS been found and it has been configured to only listen to error messages. Therefore, the coldbox.system.plugins.BeanFactory logger can ONLY log error messages according to its inherited category. So the info() message will be ignored.
The second logger is called coldbox.system.interceptors.SES, LogBox tries to match a category but fails, so it now searches for a logger called coldbox.system.interceptors. It still cannot find it so it continues up the package chain and finds the coldbox.system logger which has been set with a minimum of DEBUG level and ONLY the console appender. So the only message that get's logged is the logger.debug() message and into the console appender.
I hope these examples give you insight into category inheritance and the power it can give you because you can easily turn on/off logging for entire packages with one single category definition. However, this is great only if you follow the dot notation conventions. Below is a sample generic chart sample:
| Category | Configured Levels | Assigned Levels |
|---|---|---|
| root | FATAL-DEBUG | FATAL-DEBUG |
| x | NONE | FATAL-DEBUG from root |
| x.y | INFO | INFO |
| x.y.z | NONE | INFO from x.y |
Severity Levels
Each logger will be configured with an optional severity level range: LevelMin and LevelMax. These severities are integers from -1 to 4, each representing a severity:
| Severity | Integer Level |
|---|---|
| OFF | -1 |
| FATAL | 0 (Default LevelMin) |
| ERROR | 1 |
| WARN | 2 |
| INFO | 3 |
| DEBUG | 4 (Default LevelMax) |
As you can see from the chart above, the default minimum level is FATAL and the highest level is DEBUG. When you define a root logger or category logger, you will define them with these severity levels or they will default to the pre-selected levels. Once you have a logger instantiated you can dynamically change the logging levels by talking to the setLevelMin() and setLevelMax() methods of the logger.
//change min level of logging to warn only
logger = logBox.getRootLogger();
logger.setLevelMin(logger.logLevels.WARN);
As you can see, each logger object has a public property called logLevels that maps to the component: coldbox.system.loggging.LogLevels which is used as a static lookup of severity levels. If you know the numeric representations by heart, then by all means use them.
Dynamic Appenders
Each logger object has several methods that you can use in order to interact with the logger's appenders. You can add, remove, clear or list the appenders on a specific logger instance. Below are the methods you can use in the logger class to interact with appenders:
| Method | Return Type | Description |
|---|---|---|
| hasAppenders() | Boolean | Checks if the logger has any appenders attached to it |
| getAppenders() | Struct | Returns the map of registered appenders |
| getAppender(name) | Appender | Return a named appender if it is registered in the logger |
| appenderExists(name) | Boolean | Checks if a named appender exists in the logger |
| addAppender(Appender) | void | Register an appender with the logger at runtime |
| removeAppender(name) | Boolean | Will un-register an appender from this logger |
| removeAllAppenders() | void | Will try to un-register all appenders from this logger |
So you can easily add/remove/check the appenders on any logger at any time.
//Add your own appender at runtime jms = createObject("component","com.appender.JMSAppender").init("JMSAppender",properties); logger.addAppender(jms); //log a message to all appenders and to my jms appender: logger.fatal("I FAILED MAN!"); //remove it logger.removeAppender("JMSAppender");
Layout
The layout component defines the format of the message to store in an appender repository. Be default, each appender already has a pre-defined message format. However, if you do not like the format of the message you can easily change it by creating your own layout component and registering it with the appender. You can do this in the configuration object when you add appenders:
//add a FileAppender with my own formatting props = {filePath='/logs',fileName='Test'}; config.appender(name='Fileapp', class="coldbox.system.logging.appenders.FileAppender", properties=props, layout="model.logging.MyFileLayout");
So to create your very own layout object, you just need to extend the LogBox abstract layout object: coldbox.system.logging.Layout.
Configuring LogBox
We already have a taste of how to configure LogBox, but let's go into details. There are three approaches to configuring LogBox: two programmatic approaches or an XML approach. We definitely lean towards our programmatic approach as it provides much more flexibility and less verbosity. So let's cover it first.
Programmatic Configuration
No matter what configuration you decide to use, you will always have to instantiate LogBox with a LogBoxConfig object: coldbox.system.logging.config.LogBoxConfig. However you have the option of either talking directly to this CFC or creating a more portable configuration. This portable configuration we denote as a simple data CFC that contains the LogBox configuration data using what we call our LogBox DSL (Domain Specific Language). The cool thing about this LogBox DSL is that it is exactly the same whether you are using LogBox in ColdBox applications or in any other framework or non-framework ColdFusion application. So you can configure LogBox by:
- Creating a portabla data CFC using the LogBox DSL or
- Creating the LogBoxConfig object and interacting with its methods
LogBox DSL
In order to create a simple data CFC, just create a CFC with one required method on it called configure where you will define the logging configuration data:
** * A LogBox configuration data object */ component{ function configure(){ logBox = { }; } }
Once you have this shell, you will create a logBox variable in the variables scope that must be a structure with the following keys:
| Key | Description |
|---|---|
| appenders | A structure where you will define appenders |
| root | A structure where you will configure the root logger |
| categories | A structure where you can define granular categories (OPTIONAL) |
| DEBUG | An array that will hold all the category names to place under the DEBUG logging level (OPTIONAL) |
| INFO | An array that will hold all the category names to place under the INFO logging level (OPTIONAL) |
| WARN | An array that will hold all the category names to place under the WARN logging level (OPTIONAL) |
| ERROR | An array that will hold all the category names to place under the ERROR logging level (OPTIONAL) |
| FATAL | An array that will hold all the category names to place under the FATAL logging level (OPTIONAL) |
| OFF | An array that will hold all the category names to not log at all (OPTIONAL) |
So to define an appender you must define a key value which is the internal name of the appender with the following keys:
- class : The class path of the appender
- layout : The layout class path of the layout object to use (optional)
- properties : The structure of name-value pairs to configure this appender with (optional)
- levelMin : The numerical or English word of the minimal logging level (optional, defaults to 0)
- levelMax : The numerical or English word of the maximum logging level (optional, defaults to 4)
To define the root logger you can use the following keys:
- levelMin : The numerical or English word of the minimal logging level (optional, defaults to 0)
- levelMax : The numerical or English word of the maximum logging level (optional, defaults to 4)
- appenders : A string list of the appenders to use for logging
To define categories you can use the following keys in the categories structure and the key of the structure is the name of the category:
- levelMin : The numerical or English word of the minimal logging level (optional, defaults to 0)
- levelMax : The numerical or English word of the maximum logging level (optional, defaults to 4)
- appenders : A string list of the appenders to use for logging (optional, defaults to *)
As you might notice the name of the keys on all the structures match 100% to the programmatic methods you can also use to configure logBox. So when in doubt, refer back to the argument names.
logBox = {
// Appenders
appenders = {
appenderName = {
class="class.to.appender",
layout="class.to.layout",
levelMin=0,
levelMax=4,
properties={
name = value,
prop2 = value 2
}
},
// Root Logger
root = {levelMin="FATAL", levelMax="DEBUG", appenders="*"},
// Granualr Categories
categories = {
"coldbox.system" = { levelMin="FATAL", levelMax="INFO", appenders="*"},
"model.security" = { levelMax="DEBUG", appenders="console,twitter"}
}
// Implicit categories
debug = ["coldbox.system.interceptors"],
info = ["model.class", "model2.class2"],
warn = ["model.class", "model2.class2"],
error = ["model.class", "model2.class2"],
fatal = ["model.class", "model2.class2"],
off = ["model.class", "model2.class2"]
};
Once you have defined the configuration data in this object you can now use the same LogBox Config object to either instantiate it for you or you can pass a reference of it by using the init() method of the LogBoxConfig object:
init([XMLConfig,CFCConfig,CFCConfigPath])
- XMLConfig : The absolute path of the logbox XML configuration file
- CFCConfig : The object instance that has the logbox configuration data
- CFCConfigPath : The instantiation path of the object that has the logbox configuration data
// Using config path config = createObject("component","coldbox.system.logging.config.LogBoxConfig").init(CFCConfigPath"my.path.LogBoxConfig"); logBox = createObject("component","coldbox.system.logging.LogBox").init(config); //Using config object data = createObject("component","my.data.CFC"); config = createObject("component","coldbox.system.logging.config.LogBoxConfig").init(data); logBox = createObject("component","coldbox.system.logging.LogBox").init(config);
That's it! Using this DSL approach, your configurations are much more portable now and can even be shared in ANY framework, ColdBox or ColdFusion application. So now let's explore how to bypass this data CFC and use the LogBoxConfig object directly. It is important to understand these methods as they are called for you when you define your LogBox DSL data.
Adding Appenders
The first thing you need to do in your config object is add appenders. Each appender is added via the appender() method.
public void appender(string name, string class, [struct properties], string layout=)
Parameters:
- name - A unique name for the appender to register. Only unique names can be registered per instance.
- class - The appender's class to register. We will create, init it and register it for you.
- properties - The structure of properties to configure this appender with.
- layout - The layout class path to use in this appender for custom message rendering.
- levelMin : The numerical or English word of the minimal logging level (optional, defaults to 0)
- levelMax : The numerical or English word of the maximum logging level (optional, defaults to 4)
config.appender(name="CFConsole",class="coldbox.system.logging.appenders.ConsoleAppender"); config.appender(name="MyCF",class="coldbox.system.logging.appenders.CFAppender"); props = {host="localhost",timeout="3",port="444",persistConnection=false}; config.appender(name="SocketBaby",class="coldbox.system.logging.appenders.SocketAppender",properties=props); props = {filePath='/logs',fileName='Test'}; config.appender(name='Fileapp', class="coldbox.system.logging.appenders.FileAppender", properties=props, layout="model.logging.MyFileLayout");
Configuring the Root Logger
This is also mandatory if you will be using LogBox, you must add a root logger configuration. This is very easy and few arguments.
public void root([numeric levelMin='-1'], [numeric levelMax='4'], string appenders)
Parameters:
- levelMin - The default log level for the root logger, by default it is 0. Optional.
- levelMax - The default log level for the root logger, by default it is 4. Optional.
- appenders - A list of appenders to configure the root logger with. Use * to add all registered appenders
config.root(appenders="*"); config.root(levelMax=config.logLevels.WARN,appenders="console,files"); config.root(levelMin=config.logLevels.INFO,levelMax=config.logLevels.DEBUG,appenders="*");
Adding Categories To Specific Logging Levels
The methods shown below are used to add categories to specific severity levels only. Each method can receive 1 to* category arguments.
- public void DEBUG()
- public void INFO()
- public void WARN()
- public void ERROR()
- public void FATAL()
- public void OFF()
config.debug("com.model.myclass", "coldbox.system.controller"); config.info("com.model.otherclass", "coldbox.system.whatever"); config.fatal("com.model.otherclass", "coldbox.system.whatever"); config.error("com.model.otherclass", "coldbox.system.whatever"); config.OFF("com.model.otherclass", "coldbox.system.whatever");
Adding Categories Granularly
You can also add categories with very granular information using the category() method. This method will allow you to add a category definition with a range of severity levels and even a list of which appenders to respond to.
public void category(string name, [numeric levelMin='0'], [numeric levelMax='4'], [string appenders='*'])
Parameters:
- name - A unique name for the appender to register. Only unique names can be registered per instance.
- levelMin - The default min log level for this category. Defaults to the lowest level 0 or FATAL
- levelMax - The max default log level for this category. If not passed it defaults to the highest level possible
- appenders - A list of appender names to configure this category with else it will use all the appenders in the root logger. You can also use * to add all registered apenders.
//register a more granular category with levels and appenders // Log messages that are from severity 0-1 (fatal - error) only to the twitter appender config.category(name="com.model.myservice",levelMax=config.logLevel.ERROR,appenders="MyTwit"); //log all email service messages to the MyLogFileAppender and the Console. config.category(name="org.model.EmailService",appenders="MyLogFileAppender,Console");
XML Configuration
You can also configure LogBox with an XML file. All you need to do is create a LogBox xml file and instantiate the config object with the location of such config file:
config = createObject("component","coldbox.system.logging.config.LogBoxConfig").init(expandPath("/config/logbox.xml"));
However, if you have your XML in a variable already, maybe you read it from a database or other location, you can still use it by calling the config object's parseAndLoad() method.
//Get the xml document from somewhere. xmlDoc = dbService().getLogBoxConfig(); //create the log box config object config = createObject("component","coldbox.system.logging.config.LogBoxConfig").init(); config.parseAndLoad(xmlDoc);
Sample logbox.xml file:
<?xml version="1.0" encoding="UTF-8"?> <LogBox xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.coldbox.org/schema/LogBoxConfig_1.4.xsd"> <-- <Appender Definitions --> <Appender name="myconsole" class="coldbox.system.logging.appenders.ConsoleAppender" /> <Appender name="MyCF" class="coldbox.system.logging.appenders.CFAppender" layout="model.myLayout"> <Property name="fileName">MyAppLogName</Property> </Appender> <Appender name="FileAppender" class="coldbox.system.logging.appenders.AsyncRollingFileAppender" levelMax="DEBUG"> <Property name="filePath">/coldbox/testing/logging/tmp</property> <Property name="fileMaxSize">3</Property> <Property name="fileMaxArchives">2</Property> </Appender> <-- <Root Logger --> <-- <Root All Appenders <Root levelMin="0" levelMax="4" appenders="*"> --> <Root levelMin="0" levelMax="4"> <Appender-ref ref="myconsole" /> <Appender-ref ref="MyCF" /> <Appender-ref ref="FileAppender" /> </Root > <-- <Very advanced category --> <Category name="MySES" levelMin="3" levelMax="4"> <Appender-ref ref="myconsole" /> </Category> <Category name="com.model.services" levelMax="3" appenders="MyCF" /> <Category name="com.model.dao" levelMax="2" appenders="*" /> </LogBox>
As you can see, you need to create a root element called LogBox with the following child elements:
- <Appender> : an appender definition. Can be 1 or more elements.
- @name : The name of the appender (required)
- @class : The class of the appender (required)
- @layout : The layout class to use for rendering the messages (optional)
- @levelMin : The default minimum log level. (optional)
- @levelMax : The default maximum log level. (optional)
- <Root> : The root logger element. Can only be 1 defined.
- @levelMin : The default minimum log level. (optional)
- @levelMax : The default maximum log level. (optional)
- @appenders : An optional list of appenders for the root logger or* for all appenders. (optional)
- <Appender-ref> : If you do not like to use a list for the appenders, you can use this element to add appenders to the root logger. You can have 0 or more of these elements defined.
- @ref : The name of the appender to reference.
- <Category> : A category definition element. You can have 0 or more of these defined.
- @name : The name of the category
- @levelMin : The default minimum log level. (optional)
- @levelMax : The default maximum log level. (optional)
- @appenders : An optional list of appenders for this category or* for all appenders. (optional)
- <Appender-ref> : If you do not like to use a list for the appenders, you can use this element to add appenders to the category. You can have 0 or more of these elements defined.
- @ref : The name of the appender to reference.
Note: All the levelMin and levelMax attributes can either be the numeric representation of the severity or the fully qualified name you can see in the severity table.
XML Schema
We have also included a schema fileL coldbox/logging/config/LogBoxConfig.xsd that you can use to validate your XML and use cool tag insight and introspection. You can add the following header to your XML declaration file to enable it:
<?xml version="1.0" encoding="UTF-8"?> <LogBox xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.coldbox.org/schema/LogBoxConfig_1.4.xsd"> </LogBox>
If your IDE supports XML introspection, this will do the trick.
Using LogBox
Once you have created and configured the LogBox library, you can interact with it in order to get logger objects. The main methods you will use to interact with LogBox are the following, but I recommend you look at the CFC api in order to get a listing of all available methods.
- LogBoxConfig getConfig() : Get the config object registered
- Logger getLogger(any category) : Get a named logger object using a category string or the object you will log from
- Logger getRootLogger() : Get a reference to the root logger
- string getVersion() : Get the current version of LogBox
- string getCurrentAppenders() : Get a list of currently registered appenders
- string getCurrentLoggers() : Get a list of currently instantiated loggers
- void configure(LogBoxConfig config) : Dynamically re-configure the LogBox library
The two most important methods are getRootLogger() & getLogger(), which you will use to get the root or named logger objects.
Important: When you ask for a named category logger and LogBox cannot find its definition, it will create a logger that will inherit its logging levels and appenders from the root logger.
Using a Logger object
Once you retrieve a logger object from LogBox, you are ready to start sending messages. We already covered how to dynamically add/remove/list/check appenders from a logger, so let's look at the other methods we have available:
Utility Methods
- boolean canLog(numeric level) : Checks if this logger can log a certain type of severity
- string getCategory() : Get this logger's category name
- void setCategory(category) : Set the category name
- Logger getRootLogger() : Get the root Logger
- numeric getLevelMin() : Get the minimum severity level
- void setLevelMin(numeric level) : Set the minimum severity level
- numeric getLevelMax() : Get the maximum severity level
- void setLevelMax(numeric level) : Set the maximum severity level
Logging Methods
- fatal(string message, [any extraInfo=' ']) : Log a fatal message
- error(string message, [any extraInfo=' ']) : Log an error message
- warn(string message, [any extraInfo=' ']) : Log a warning message
- info(string message, [any extraInfo=' ']) : Log an information message
- debug(string message, [any extraInfo=' ']) : Log a debug message
- logMessage(string message, numeric severity, [any extraInfo=' ']) : Log any kind of message
As you can probably tell, all logging methods take in a message string an a second argument called extraInfo. This extraInfo argument can be anything from a string, a structure, a query or whatever. This way you can send in a complex structure that the appenders will serialize into message form or log into its appropriate channel. Thus, extraInfo can be very handy when you are building your own custom appenders.
// setting some messages myLogger = logBox.getLogger(this); //"com.model.dao" myLogger.info("I just created my first logger"); try{ data = dao.getDBData(); } catch(Any e){ myLogger.error("Something really died on my dbdata method: #e.message# #e.detail#",e.tagContext); }
I hope that by now you understand the basics of loggers and how easy it is to use them.
Instance Members
Every logger has access to the following public variables:
- this.logLevel : A reference to the coldbox.system.logging.LogLevels class
Creating Custom Appenders
In order to create your own appenders, you will have to create a cfc that extends coldbox.system.logging.AbstractAppender and implement the following methods:
- init() : Your constructor
- logMessage() : The method that is called when a message is received
- onRegistration() : An interceptor that fires when the appender gets created and initialized. It can be used for preparing the appender for operation.
- onUnRegistration() : An interceptor that fires when the appender is removed from a logger.
The signature of the init method is the following:
<--- Init ---> <cffunction name="init" access="public" returntype="AbstractAppender" hint="Constructor called by a Concrete Appender" output="false" > <--- ************************************************************* ---> <cfargument name="name" type="string" required="true" hint="The unique name for this appender."/> <cfargument name="properties" type="struct" required="false" default="#structnew()#" hint="A map of configuration properties for the appender"/> <cfargument name="layout" type="string" required="false" default="" hint="The layout class to use in this appender for custom message rendering."/> <cfargument name="levelMin" type="numeric" required="false" default="0" hint="The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN"/> <cfargument name="levelMax" type="numeric" required="false" default="4" hint="The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN"/> <--- ************************************************************* ---> </cffunction>
As you can see each appender receives a name, a structure of properties, a layout class, an optional levelMin and levelMax severity levels. The properties and layout are both optional, but you must call the super.init() method in order to have full ok operation on the appender. You can then do your own constructor as you see fit. Here is an example:
<--- Constructor ---> <cffunction name="init" access="public" returntype="FileAppender" hint="Constructor" output="false"> <---************************************************************** ---> <cfargument name="name" type="string" required="true" hint="The unique name for this appender."/> <cfargument name="properties" type="struct" required="false" default="#structnew()#" hint="A map of configuration properties for the appender"/> <cfargument name="layout" type="string" required="true" default="" hint="The layout class to use in this appender for custom message rendering."/> <cfargument name="levelMin" type="numeric" required="false" default="0" hint="The default log level for this appender, by default it is 0. Optional. ex: LogBox.logLevels.WARN"/> <cfargument name="levelMax" type="numeric" required="false" default="4" hint="The default log level for this appender, by default it is 5. Optional. ex: LogBox.logLevels.WARN"/> <---************************************************************** ---> <cfscript> super.init(argumentCollection=arguments); // Setup Properties if( NOT propertyExists("filepath") ){ $throw(message="Filepath property not defined",type="FileAppender.PropertyNotFound"); } if( NOT propertyExists("autoExpand") ){ setProperty("autoExpand",true); } if( NOT propertyExists("filename") ){ setProperty("filename",getName()); } if( NOT propertyExists("fileEncoding") ){ setProperty("fileEncoding","UTF-8"); } // Setup the log file full path instance.logFullpath = getProperty("filePath"); // Clean ending slash if( right(instance.logFullpath,1) eq "/" OR right(instance.logFullPath,1) eq "\"){ instance.logFullPath = left(instance.logFullpath, len(instance.logFullPath)-1); } instance.logFullPath = instance.logFullpath & "/" & getProperty("filename") & ".log"; // Do we expand the path? if( getProperty("autoExpand") ){ instance.logFullPath = expandPath(instance.logFullpath); } //lock information instance.lockName = getname() & "logOperation"; instance.lockTimeout = 25; return this; </cfscript> </cffunction>
The signature of the logMessage method is the following:
<--- logMessage ---> <cffunction name="logMessage" access="public" output="false" returntype="void"> <---************************************************************** ---> <cfargument name="logEvent" type="coldbox.system.logging.LogEvent" required="true" hint="The logging event to log."> <---************************************************************** ---> </cffunction>
As you can see it is a very simple method that receives a LogBox logging event object. This object keeps track of the following properties with its appropriate getters and setters:
- timestamp
- category
- message
- severity
- extraInfo
You can then use this logging event object to log to whatever destination you want. Here is a snippet from our scope appender:
<--- Log Message ---> <cffunction name="logMessage" access="public" output="true" returntype="void" hint="Write an entry into the appender."> <---************************************************************** ---> <cfargument name="logEvent" type="coldbox.system.logging.LogEvent" required="true" hint="The logging event"/> <---************************************************************** ---> <cfscript> var logStack = ""; var entry = structnew(); var limit = getProperty('limit'); var loge = arguments.logEvent; // Verify storage ensureStorage(); // Check Limits logStack = getStorage(); if( limit GT 0 and arrayLen(logStack) GTE limit ){ // pop one out, the oldest arrayDeleteAt(logStack,1); } // Log Away entry.id = createUUID(); entry.logDate = loge.getTimeStamp(); entry.appenderName = getName(); entry.severity = severityToString(loge.getseverity()); entry.message = loge.getMessage(); entry.extraInfo = loge.getextraInfo(); entry.category = loge.getCategory(); // Save Storage arrayAppend(logStack, entry); saveStorage(logStack); </cfscript> </cffunction>
Finally, both the onRegistration and onUnRegistration methods have to be void methods with no arguments.
<cffunction name="onRegistration" access="public" hint="Runs after the appender has been created and registered. Implemented by Concrete appender" output="false" returntype="void"> </cffunction> <cffunction name="onUnRegistration" access="public" hint="Runs before the appender is unregistered from LogBox. Implemented by Concrete appender" output="false" returntype="void"> </cffunction>
These are great for starting or stopping your appenders if they so need to. Here is a sample from our socket appender:
<--- onRegistration ---> <cffunction name="onRegistration" output="false" access="public" returntype="void" hint="When registration occurs"> <cfif getProperty("persistConnection")> <cfset openConnection()> </cfif> </cffunction> <--- onRegistration ---> <cffunction name="onUnRegistration" output="false" access="public" returntype="void" hint="When Unregistration occurs"> <cfif getProperty("persistConnection")> <cfset closeConnection()> </cfif> </cffunction>
Helper Methods
The abstract appender also has various cool methods that you can use when building appenders:
CF Utility Methods
- $abort()
- $dump(any var, boolean isAbort=[false])
- $log(string severity='INFO', string message=) : Log a cflog message just in case
- $rethrowit(any throwObject)
- $throw(string message, [string detail=], [string type='Framework'])
Properties Methods
- struct getProperties() : Get all the properties struct
- void setProperties(struct properties) : Override all properties
- any getProperty(string property) : Get a property
- void setProperty(string property, any value) : Set a property
- Boolean propertyExists(string property) : Checks if a property exists
Utility Methods
- isInitialized() : If the appender has been initialized
- getName() : Get the name of the appender
- getHash() : Get the appender's unique hash id
- severityToString(numeric severity) : Transforms a severity integer to it's human readable form
Layout Methods
- any getCustomLayout() : Get the custom layout object if defined.
- boolean hasCustomLayout() : Checks if the custom layout object is defined in this appender.
Instance Members
Every Appender has access to the following public variables:
- this.logLevel : A reference to the coldbox.system.logging.LogLevels class
Dealing with Custom Layouts
In order for an appender to deal with custom layouts, you must use the layout methods when preparing to log your messages. Below is a simple example from the console appender of how to do this:
if( hasCustomLayout() ){ entry = getCustomLayout().format(loge); } else{ entry = "#severityToString(loge.getseverity())# #loge.getCategory()# #loge.getmessage()# ExtraInfo: #loge.getextraInfoAsString()#"; } // Log message to system.out instance.out.println(entry);
As you can see, all you need to do is have an if statement that checks whether the appender has a custom layout or not and then assign the return of the layout as your message to log.
Creating a Custom Layout
You can easily create a custom layout object by creating a cfc that extends our abstract layout object: coldbox.system.logging.Layout and implementing a format() method. Below you can see the method signature:
<--- format ---> <cffunction name="format" output="false" access="public" returntype="string" hint="Format a logging event message into your own format"> <cfargument name="logEvent" type="coldbox.system.logging.LogEvent" required="true" hint="The logging event to use to create a message."> </cffunction>
All you need to do is inspect the logging event and create your very own message and then return it back. That's it! You thought there was more?
Instance Members
Every Layout has access to the following public variables:
- this.logLevel : A reference to the coldbox.system.logging.LogLevels class
- this.LINE_SEP : A line separator equal to chr(13) & chr(10)
Appender Properties
As we mentioned before, LogBox ships with over 10 different appenders for your logging and tracing needs. Some of them require configuration properties and some don't. We already discovered that when we configure an appender we can pass in a structure of properties much like how we configure ColdBox Interceptors. Each appender can implement as many properties as they see fit. Below we will digest all the included LogBox appenders and their configuration properties.
AsyncFileAppender & FileAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| filePath | string | true | --- | The location of where to store the log file |
| filename | string | false | Name of the Appender | The name of the file, if not defined, then it will use the name of this appender. Do not append an extension to it. We will append a .log to it |
| fileEncoding | string | false | utf-8 | The file encoding to use, by default we use UTF-8 |
| autoExpand | boolean | false | true | Whether to expand the file path or not. Defaults to true |
Note: Please remember to set the autoExpand property to FALSE if you will be using an absolute file path location.
AsyncRollingFileAppender & RollingFileAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| filePath | string | true | --- | The location of where to store the log file |
| filename | string | false | Name of the Appender | The name of the file, if not defined, then it will use the name of this appender. Do not append an extension to it. We will append a .log to it |
| fileEncoding | string | false | utf-8 | The file encoding to use, by default we use UTF-8 |
| autoExpand | boolean | false | true | Whether to expand the file path or not. Defaults to true |
| fileMaxSize | int | false | 2000 (2MB) | The max file size for log files. Defaults to 2000 (2 MB) |
| fileMaxArchives | int | false | 2 | The max number of archives to keep. Defaults to 2 |
Note: Please remember to set the autoExpand property to FALSE if you will be using an absolute file path location.
CFAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| logType | string(file or application) | false | file | The type of cflog to use: file or application. |
| fileName | string | false | Appender's name | The name of the file to log to if using file as the logType. If not set, it will use the appender's name |
This appender logs directly to the cflog tag by using a custom file or logging to the application logs.
ColdBoxTracerAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| coldbox_app_key | string | false | --- | The app key where ColdBox is stored in application scope because this appender talks to ColdBox via the ColdBox Factory. Don't set it to use the default |
This appender logs to the ColdBox Tracer Messages Panel in the ColdBox debugger.
DBAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| dsn | string | true | --- | The dsn to use for logging |
| table | string | true | --- | The table name to use for logging |
| columnMap | struct | false | --- | A column map for aliasing columns. (Optional) |
| autocreate | boolean | false | false | if true, then we will create the table. Defaults to false (Optional) |
| textDBType | string | false | text | The database type for the message and extended info fields. |
The columns needed or created in the table are
- id : UUID
- severity : string
- category : string
- logdate : timestamp
- appendername : string
- message : string
- extrainfo : string
If you are building a column mapper, the map must have the above keys in it that match to your own table columns.
Important: Please make sure you update the textDBType property to match your database capabilities for logging.
EmailAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| subject | string | true | --- | Get's pre-pended with the severity and category field. |
| from | string | true | --- | The from email address |
| to | string | true | --- | The to email(s) |
| cc | string | false | empty | The cc email(s) |
| bcc | string | false | empty | The bcc email(s) |
| mailserver | string | false | empty | The optional mail server |
| mailusername | string | false | empty | The optional mail username |
| mailpassword | string | false | empty | The optional mail password |
| mailport | int | false | 25 | The optional mail port |
| useTLS | boolean | false | false | Use the Transport level security setting in the cfmail tag. |
| useSSL | boolean | false | false | Use SSL or not |
ScopeAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| scope | string | false | request | The scope to persist to, any valid CF scope. |
| key | string | false | appender's name | The key to use in the scope |
| limit | numeric | false | 0 | a limit to the amount of logs to rotate. Defaults to 0, unlimited (optional) |
SocketAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| host | string | true | The host to connect to | |
| port | string | true | --- | The port to connect to |
| timeout | numeric | false | 5 | the timeout in seconds. defaults to 5 seconds |
| persistConnection | boolean | false | true | Whether to persist the connection or create a new one every log time. Defaults to true |
TracerAppender
This appender directs messages via the cftrace tag. It has no configuration properties.
TwitterAppender
| Property | Type | Required | Default | Description |
|---|---|---|---|---|
| username | string | true | The twitter username that will send the messages | |
| password | string | true | --- | The twitter password that will send the messages |
| logType | string | false | dm | Either status or dm, To either update status or a direct message. Defaults to direct message |
| dmuser | string | true (if logType=dm) | --- | The user to send the direct message to |
LogBox in a ColdBox Application
Every ColdBox application can use LogBox by default since the main engine already uses it. By default ANY ColdBox application will be configured with a LogBox instance with the following appenders:
- ConsoleAppender
<LogBox> <-- <Default Appender Definitions --> <Appender name="ConsoleAppender" class="coldbox.system.logging.appenders.ConsoleAppender" /> <-- <Root Logger: Will log anything by default --> <Root levelMin="FATAL" levelMax="INFO" appenders="*" /> </LogBox>
Also, the app will log on ANY severity by default up to INFO for the root logger and the ColdBox package. You can customize this default behavior by creating or modifying the <LogBox> element in your ColdBox configuration file and follow the same configuration approach as any normal LogBox configuration file; please refer to the configuring LogBox section. In ColdBox 3.0.0 applications and above you can either use the XML or configuration DSL approach.
Configuration Within ColdBox
There are several ways you can configure LogBox from within your ColdBox applications, to each its own. So we will start with the two ways you can configure a ColdBox application.
Programmatic
ColdBox 3.0.0 and above allows for a programmatic approach via the ColdBox configuration object. So let's look at how the loader looks at your configuration:
- Is there a logBox variable defined in the configuration?
- False:
- Does a LogBox.cfc exist in the application's config folder?
- True: Use that CFC by convention to configure LogBox
- False: Continue to next point
- Configure LogBox with default framework settings
- Does a LogBox.cfc exist in the application's config folder?
- True:
- Have you defined a configFile key?
- True: Then use that value to pass into the configuration object so it can load LogBox using that configuration file (xml or CFC)
- False: The configuration data is going to be defined inline here so process it
- Have you defined a configFile key?
- False:
So the configuration DSL is exactly the same as you have seen in before with the only distinction that you can add a configFile key that can point to an external configuration file (XML or CFC).
XML
The XML approach uses exactly the same configuration elements as the normal XML configuration file but with one extra element: ConfigFile. This serves the same purpose as in the programmatic approach, where you have defined the LogBox configuration somewhere and you are pointing to it via this setting:
<LogBox> <ConfigFile>shared.path.LogBoxConfig</ConfigFile> </LogBox>
Please also note that the application loader follows almost the same approach as above:
- Is there a <LogBox> element defined in the configuration?
- False:
- Does a LogBox.cfc exist in the application's config folder?
- True: Use that CFC by convention to configure LogBox
- False: Continue to next point
- Configure LogBox with default framework settings
- Does a LogBox.cfc exist in the application's config folder?
- True:
- Have you defined a <ConfigFile> element?
- True: Then use that value to pass into the configuration object so it can load LogBox using that configuration file (xml or CFC)
- False: The configuration data is going to be defined inline via the XML notation here so process it
- Have you defined a <ConfigFile> element?
- False:
Benefits of using LogBox in a ColdBox application
Just by building a ColdBox application, you get several key benefits when dealing with LogBox.
- First of all, the configuration, creation and instantiation is ALL done for you.
- The ColdBox configuraiton file accepts ${setting} placeholder syntax, so you can make your LogBox configuration dynamic and use your ColdBox settings. You can even make your properties to appenders to be complex variables as you have the JSON notation available in the ColdBox application already or forget about that nonsense and use the ColdBox Programatic approach.
- You can configure LogBox on a per-environment criteria as the ColdBox per-environment routines can use the LogBox configuration elements in its definitions.
- Every handler, interceptor and plugin already has a reference to the LogBox instance as a property called: logBox. So you have immediate access to it.
- Every handler, interceptor and plugin already has a configured logger instance as a property called: log. So you have immediate access to it.
- The ColdBox Logger plugin is already configured to use the configured LogBox instance and you can inject it anywhere you like.
- You can configure the logging of ColdBox on a per package level.
- You get the power of ColdBox MVC.
Where is LogBox stored in a ColdBox app?
The LogBox instance is stored in the ColdBox main controller object and you can retrieve it like so from any handler, plugin or interceptor.
logBox = getController().getLogBox();
LogBox from the ColdBox Factory
The ColdBox factory object also has three utility methods you can use to talk to LogBox:
- getLogBox() : Get a reference to the LogBox instance.
- getRootLogger() : Get a reference to the root logger.
- getLogger(category:any) : Get a named logger instance or reference.
LogBox from the ColdBox Proxy
The ColdBox proxy object also has three utility methods you can use to talk to LogBox from any remote proxy you create:
- getLogBox() : Get a reference to the LogBox instance.
- getRootLogger() : Get a reference to the root logger.
- getLogger(category:any) : Get a named logger instance or reference.
Can I still use the Logger plugin?
Yes, of course. The Logger plugin (v 3.0.0 > only) has been reconfigured to work with LogBox. You can use it like any normal Logger plugin with some added methods to it. In summary, the Logger plugin is configured to work via one logger who's category name is the name of your application.
The LogBox autowiring DSL
The ColdBox autowiring DSL can also talk to LogBox. This way you can easily use our dependency injection DSL for LogBox related objects:
| Type | Description |
|---|---|
| logbox | Get a reference to the application's LogBox instance |
| logbox:root | Get a reference to the root logger |
| logbox:logger:category | Get a reference to a named logger by its category name |
Below you can see the most common usage of this dependency DSL:
<--- LogBox wired in ---> <cfproperty name="logBox" type="logbox" /> <--- Root Logger ---> <cfproperty name="logger" type="logbox:root" /> <--- Named Category For an Object, will grab the category name from the object itself. ---> <cfproperty name="logger" type="logbox:logger:#getMetadata(this).name#" /> <--- Named Category ---> <cfproperty name="logger" type="logbox:logger:com.api.model" />
Summary
As you can see, LogBox is both a powerful and simple logging library for ColdFusion. You have great flexibility by being able to define more than 1 destination points and even building your own. The logger interface is incredibly easy to use and configure for any kind of custom severity levels or even destinations. LogBox is also incredibly friendly when dealing with messages as you can even customize them as you see fit.
Overall, LogBox is more than a simple logging library but an enterprise logging machine!
SideBar
User Login
Comments (