CFLogger is a simple logger library for ColdFusion, loosely based around a Log4J-style interface. Allows multiple listeners for each logger, all configurable at different log levels, to send messages to multiple destinations depending on log level.
<cfset variables.logger = createobject("component", "cflogger.core.Logger").init()> <cfset variables.logger.register_listener( createobject("component", "cflogger.listeners.FileListener").init( variables.logger.levels.DEBUG, "test", "/tmp" ) )> <cfset variables.logger.debug("Test")>
Simply place the
cflogger directory either in your webroot or within a custom tag path, or create a mapping called
/cflogger to the location of the directory.
Logger component is the heart of this library.
The only argument for the
init() function of the
Logger component is
name; if not provided, this will default to
application.applicationname if it exists, otherwise the name will be randomly assigned.
Useable functions for this component are:
register_listener-- register a listener instance with this logger
unregister_listener-- remove a listener instance from this logger
get_listeners-- returns the array of listeners registered with this logger
has_listeners-- does this logger have and listeners registered?
fatal-- log a given message at this log level
You can register multiple listeners with the same
Logger instance; they do not all have to be at the same log level, and you can register multiple instances of the same listener type.
The default log levels are defined in
cflogger.core.LogLevels and are available by name through a public variable in the instantiated
cflogger.core.Logger object, e.g.
variables.logger.levels['DEBUG']. Each log level has a function provided in the
Logger component to write a message at that log level, e.g.
In increasing severity the defined levels are:
Each listener registered with the logger can be configured with a different log level; this will be the minimum level a message needs to be to be logged (so a listener configured at the WARN level will also receive ERROR and FATAL messages). Setting the log level on the listener, rather than the logger itself, allows the flexibility to do something like log at the WARN level to database, but then have FATAL messages emailed or sent by SMS.
You can log any type of data: it doesn't just have to be a string. If you pass a non-simple value to one of the logging functions (
info etc), it will be serialised into a string before being passed to the registered listeners. The default serialisation type is JSON, but you can use YAML instead by instantiating the
Logger as follows:
<cfset variables.logger = createobject("component", "cflogger.core.Logger").init( serialiser = createobject("component", "cflogger.serialisers.YAML") )>
You can implement your own serialiser by creating a component that extends
cflogger.serialisers.Base and implements the
There are four default log listeners which can be registered with a logger in any combination, at different log levels should it be required.
Registration of a listener with the logger is via the
register_listener function as shown above. Most of the provided listeners use the
on_register function to do some sanity checking (such as permissions, checking database tables exist etc) and may unregister themselves should a problem occur; the unregistration of any listener will be logged in any other, previously added listeners.
The listeners below are all contained within
Simply logs messages to a file (appending the messages if the file already exists). Initialisation arguments are:
stringfilename to write to (
.logwill automatically be appeneded)
stringpath to the directory in which to write the log file.
If the log file cannot be written to during the
on_register phase, the logger will be unregistered.
Note: due to changes introduced in CF 9.0.1, if you're using this version or above and are using the
initchecksfeature, you'll also need to provide a valid CF Admin username and password, because accessing the datasources service can now only be performed via the authenticated Admin API.
Logs messages to a database table. Currently MySQL-only if the
initchecks argument is specfied. Initialisation arguments are:
stringdatasource name to write to
stringtable name to log messages to
cols-- the list of columns to write to, in the following order: logger name, log level, message, time stamp (default is
leveltype-- should we store the level as a string or a numeric value? (default is
booleanspecifying if initialisation checks be run, such as checking the DSN exists and can be written to (default is
booleanspecifying if we should try and automatically create the appropriate table if it doesn't already exist? Also requires
initchecksto be true (default is
stringgiving a CF Admin username which has access to the datasources Admin API (only required if
TRUEand CF server version is >= 9.0.1)
stringpassword for the above Admin API username
initchecks is specified, the following checks are performed (and the listener unregistered if any fail):
- Does the datasource exist?
- Does the datasource have
- Does the table exist?
If the final table check fails, but
TRUE then an attempt to
CREATE the table is made; if this fails, the listener is unregistered.
Stores log messages within a given CF built-in scope structure, such as the
session. Initialisation arguments are:
scope-- the name of the required global CF scope: one of
request; note that this is a
string, not a reference to the actual scope
key-- the structure key name under which to store log messages in the given scope
limit-- a maximum number of messages to retain in the given scope (LIFO-style)
Although not necessarily as useful for standard logging, this can be really useful in situations like when you need to store temporary messages to show to the user, that may need to persist over multiple requests (for example, the user completes an action, is redirected and a success message related to the initial action is shown).
First we create an
application-scoped logger and then add a scope-listener to that (logging to the
<cfset application.messenger = createobject("component", "cflogger.core.Logger").init()> <cfset application.messenger.register_listener( createobject("component", "cflogger.listeners.ScopeListener").init( variables.logger.levels.INFO, "session", "messages", 10 ) )>
The textual representation of the scope is evaluated on every call to write to the log (rather than just on initialisation), so it's safe to store the messenger object in a persistent scope in this way; in other words, the
session will be the current request session on every call.
Once this has been set up, messages to display to the user can be added to this logger. For example, during the validation of a form submit you might have a mandatory
name field which wasn't filled in:
<cfset application.messenger.error("Please enter your name")>
In your global template file, you can then have something akin to the following to output any messages for the current session:
<cfparam name="session.messages" default="#arraynew(1)#"> <cfloop array="#session.messages#" index="message"> <cfoutput>#message.msg#<br></cfoutput> </cfloop> <cfset structkeydelete(session, "messages")>
The messages are stored in the appropriate scope as an array of structures with four keys:
app-- the logger name
date-- when the message was logged
level-- the text representation of log level (e.g.
msg-- the actual message content
You could use the log level to differentiate between error messages and success messages (by using the
INFO types respectively) and displaying them to the user with different styling.
Sends each message to a waiting socket on a remote machine. Initialisation arguments are:
host-- the host to connect to and send messages
port-- the port number to connect to and send messages
numerictimeout in seconds to wait for a socket connection
booleanas to whether we should use a persistent socket connection or reconnect for every messages
stringline terminator (defaults to
Sends each message as a status update to a Twitter account. Initialisation arguments are:
user-- the Twitter username to post as
pass-- the password of the Twitter user to post as
Writing Your Own Listener
None of the loggers provide exactly what you're after? You can easily write your own to fit the job perfectly. Maybe you'd like those important messages sent to you via email or SMS? What about logging to IRC, or even Jabber?
To create your own logger, just create a component that extends
cflogger.core.AbstractListener and implement the following functions:
init-- should be a
publicfunction that calls
super.init(level)at a minimum
write-- takes two arguments:
You may also create an
on_register function which will be called when the listener is registered with a logger. This function takes no arguments, but you can use it to do any sanity checking before starting to write logs (for example, checking that a log file can actually be written to).
All the provided loggers are built in exactly this way, so just have a dig around in them to see what you can do, exactly what the arguments are etc.
Licensing and Attribution
CFLogger is released under the MIT license as detailed in the LICENSE file that should be distributed with this library; the source code is freely available.
CFLogger was developed by Tim Blair when working on White Label Dating, while employed by Global Personals Ltd. Global Personals Ltd have kindly agreed to the release of this software under the license terms above.