logbox

last edited byusericonlmajano on 12-Jul-2010

<< Back to Dashboard

Contents

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?

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:

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:

  1. Root Logger - The default configured logger (mandatory)
  2. 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:

  1. Creating a portabla data CFC using the LogBox DSL or
  2. 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:

To define the root logger you can use the following keys:

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:

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])
// 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:

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:

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.

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:

//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:

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.

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

Logging Methods

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:

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:

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:

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

Properties Methods

Utility Methods

Layout Methods

Instance Members

Every Appender has access to the following public variables:

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:

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

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:

<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:

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:

Benefits of using LogBox in a ColdBox application

Just by building a ColdBox application, you get several key benefits when dealing with LogBox.

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:

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:

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!

 
Download in other Formats:
markup Markup | pdf PDF | swf SWF | html HTML | word Word

comments Comments (0)