Customizing the ColdBox Logger Plugin with Method Injection

2009 January 09
tags: Coldbox · ColdFusion · Ideas
by Paul Marcotte

I've started using Coldbox recently and really like the tooling it provides. I don't always have access to the ColdFusion logs for apps on shared servers and although I have worked with log4j, I didn't like property file configuration. The simplicity of the ColdBox logger is great, but I wanted to be able to set log levels and work with the standard debug(), info(), warn(), error() and fatal() methods. One way to achieve this is to mixin the desired behaviour. Another ColdBox plugin, methodInjector, allowed me to do just that.

First, I created a component for the mixin methods. Here's the component code.

<cfcomponent displayname="loggerMixin" output="false" hint="I contain methods to be mixed in to the CB logger plugin">

<cffunction name="getLogLevel" access="public" output="false" returntype="string" hint="I return the current log level.">
<cfreturn variables.logLevel>
</cffunction>

<cffunction name="debug" access="public" output="false" returntype="void" hint="I log a debug message.">
<cfargument name="Message" type="string" required="yes" hint="The message to log.">
<cfargument name="ExtraInfo" type="string" required="no" default="" hint="Extra information to append.">
<cfscript>
arguments.Severity = "debug";
logMessage(argumentCollection=arguments);
</cfscript>
</cffunction>

<cffunction name="info" access="public" output="false" returntype="void" hint="I log an information message.">
<cfargument name="Message" type="string" required="yes" hint="The message to log.">
<cfargument name="ExtraInfo" type="string" required="no" default="" hint="Extra information to append.">
<cfscript>
arguments.Severity = "information";
logMessage(argumentCollection=arguments);
</cfscript>
</cffunction>

<cffunction name="warn" access="public" output="false" returntype="void" hint="I log a warning message.">
<cfargument name="Message" type="string" required="yes" hint="The message to log.">
<cfargument name="ExtraInfo" type="string" required="no" default="" hint="Extra information to append.">
<cfscript>
arguments.Severity = "warning";
logMessage(argumentCollection=arguments);
</cfscript>
</cffunction>

<cffunction name="error" access="public" output="false" returntype="void" hint="I log an error message.">
<cfargument name="Message" type="string" required="yes" hint="The message to log.">
<cfargument name="ExtraInfo" type="string" required="no" default="" hint="Extra information to append.">
<cfscript>
arguments.Severity = "error";
logMessage(argumentCollection=arguments);
</cfscript>
</cffunction>

<cffunction name="fatal" access="public" output="false" returntype="void" hint="I log a fatal message.">
<cfargument name="Message" type="string" required="yes" hint="The message to log.">
<cfargument name="ExtraInfo" type="string" required="no" default="" hint="Extra information to append.">
<cfscript>
arguments.Severity = "fatal";
logMessage(argumentCollection=arguments);
</cfscript>
</cffunction>

<cffunction name="logMessage" access="public" output="false" returntype="void" hint="I log a message when the current log level is gte the message severity.">
<cfargument name="Severity" type="string" required="yes" hint="The severity of the message.">
<cfargument name="Message" type="string" required="yes" hint="The message to log.">
<cfargument name="ExtraInfo" type="string" required="no" default="" hint="Extra information to append.">
<cfscript>
if (getLogLevel() gte variables.levels[arguments.Severity])
{
logEntry(argumentCollection=arguments);
}
</cfscript>
</cffunction>

</cfcomponent>

Then, in the onAppInit event handler, I mixin properties and methods to the logger.

<cffunction name="onAppInit" access="public" returntype="void" output="false">
<cfargument name="Event" type="any">
<cfscript>
var logger = getPlugin("logger");
var injector = getPlugin("methodInjector");
var mixin = CreateObject("component","includes.mixin.loggerMixin");
var mixinMethods = StructKeyArray(mixin);
var i = 1;
var levels = structNew();
levels["debug"] = 4;
levels["information"] = 3;
levels["warning"] = 2;
levels["error"] = 1;
levels["fatal"] = 0;
logger.removeLogFile(true);
injector.start(logger);
logger.injectPropertyMixin("levels",levels);
logger.injectPropertyMixin("logLevel",getSetting("logLevel"));
for (;i lte ArrayLen(mixinMethods); i=i+1)
{
logger.injectMixin(mixin[mixinMethods[i]]);
}
injector.stop(logger);
logger.info("methods injected");
</cfscript>
</cffunction>

This allows me to set different log levels using the environmentControl interceptor.

So far it's working nicely, though I believe that extending the logger would be preferable to injecting behaviour.