Custom Logger doesn't retain information before redirect

2 views
Skip to first unread message

Mike Rogers

unread,
Oct 26, 2009, 2:31:32 PM10/26/09
to Mach-II for CFML
I'm writing a custom logger that outputs errors to an IRC channel via
the Java Martyr IRC library. It's pretty much done and working, but
getLog().error("messages") that are done before a redirect are lost in
the shuffle. I used the MachIILog logger as a template, which includes
the following:

<cffunction name="preRedirect" access="public" returntype="void"
output="false"
hint="Pre-redirect logic for this logger.">
<cfargument name="data" type="struct" required="true"
hint="Redirect persist data struct." />

<cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter
().isLoggingDataDefined()>
<cfset arguments.data[getLoggerId()] = getLogAdapter
().getLoggingData() />
</cfif>
</cffunction>

<cffunction name="postRedirect" access="public" returntype="void"
output="false"
hint="Post-redirect logic for this logger.">
<cfargument name="data" type="struct" required="true"
hint="Redirect persist data struct." />

<cfset var loggingData = StructNew() />

<cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter
().isLoggingDataDefined()>
<cftry>
<cfset loggingData = getLogAdapter().getLoggingData() />
<cfset loggingData.data = arrayConcat(arguments.data[getLoggerId
()].data, loggingData.data) />
<cfcatch type="any">
<!--- Do nothing as the configuration may have changed between
start of
the redirect and now --->
</cfcatch>
</cftry>
</cfif>
</cffunction>

Does anyone have a clue as to what I'm missing? I can post more code/
configuration if necessary.

Thanks,

-Mike Rogers

Peter J. Farrell

unread,
Oct 26, 2009, 3:04:16 PM10/26/09
to mach-ii-for...@googlegroups.com
Mike,

I would suspect there is a problem with how your logger is being
registered with the framework or possible an logic error somewhere in
your code that is causing the preRedirect and postRedirect callback
points to not be invoked.

I'd suggest in a view for your to do a cfdump of something to see if
this callback points are registered with the framework. On any view do this:

<cfdump
var="#getAppManager().getRequestManager().getPreRedirectCallbacks()#"/>
<cfdump
var="#getAppManager().getRequestManager().getPostRedirectCallbacks()#"/>

The array of available callbacks for both dumps should be equal to the
number of loggers you have defined in your application that *have* those
callback points defined (for example the CFLogLogger does not implement
the pre/postRedirect callback points). For example if your IRC logger
is the only defined logger, you should have an array with 1 element in
it. The number of elements in the array is not equal the number of
defined loggers that implement these points -- it would indicate these
points are not being registered with the framework and therefore not
being called.

Another option is do a <cflog> in this points to see if they are being
called.

Are you defining the logger using the LoggingProperty.cfc? Or are you
doing it manually.

I assume you are using the ScopeAdapter to log to the messages to a
scope first? (just like the MachIILogger). And oh, this wouldn't work
if you have your config mode in your Application.cfc set to 1 (always
reload) because the ScopeAdapter uses some generated IDs in order to put
it into an unique key. Setting it to 1 will cause the framework to
reload an that key to change between requests.

Feel to share code with us (or you can email me separately if you want)
and the more eyeballs looking at it should help. In all honesty, I
think it's just a configuration issue. I'd love to see the code. I
think you might be the first person to do a custom logger (at least to
my knowledge -- we don't always know how people use the framework).

Best,
.Peter

P.s. Welcome to the list -- it was nice to meet you in person at
BFusion / BFlex yesterday!

Mike Rogers said the following on 10/26/2009 01:31 PM:

Mike Rogers

unread,
Oct 26, 2009, 3:52:00 PM10/26/09
to Mach-II for CFML
Responses inline

On Oct 26, 3:04 pm, "Peter J. Farrell" <pe...@mach-ii.com> wrote:
> I'd suggest in a view for your to do a cfdump of something to see if
> this callback points are registered with the framework. On any view do this:
>
> <cfdump
> var="#getAppManager().getRequestManager().getPreRedirectCallbacks()#"/>
> <cfdump
> var="#getAppManager().getRequestManager().getPostRedirectCallbacks()#"/>

I did this and got the expected number of logger objects. They seem to
be registering correctly.

> Another option is do a <cflog> in this points to see if they are being
> called.

This also indicated that the pre/postRedirect functions were being
registered and called correctly.

> Are you defining the logger using the LoggingProperty.cfc?  Or are you
> doing it manually.

<property name="logging" type="MachII.logging.LoggingProperty">
<parameters>
<parameter name="IRCLog">
<struct>
<key name="type" value="@cf.app.root@.utils.loggers.IRCLogger"/>
<key name="loggingEnabled" value="true" />
<key name="loggingLevel" value="warn" />
<key name="server" value="stinkbase.dyndns.org"/>
<key name="channel" value="#amedco"/>
<key name="nick" value="orgapp[dev]"/>
<key name="filter" value="gorgon.*"/>
</struct>
</parameter>
</parameters>
</property>

Pardon the @cf.app.root@, as I'm using Ant to dynamically configure my
deployments.

> I assume you are using the ScopeAdapter to log to the messages to a
> scope first?  (just like the MachIILogger).  And oh, this wouldn't work
> if you have your config mode in your Application.cfc set to 1 (always
> reload) because the ScopeAdapter uses some generated IDs in order to put
> it into an unique key.  Setting it to 1 will cause the framework to
> reload an that key to change between requests.

Yep, using the ScopeAdapter, with code lifted directly from
MachIILog.Logger. I'm not modifying MACHII_CONFIG_MODE at all so it
should be the default of 0, shouldn't it?

> Feel to share code with us (or you can email me separately if you want)
> and the more eyeballs looking at it should help.  In all honesty, I
> think it's just a configuration issue.  I'd love to see the code.  I
> think you might be the first person to do a custom logger (at least to
> my knowledge -- we don't always know how people use the framework).

I've attached the code at the bottom of this email. You'll need to
have martyr.jar in ${COLDFUSION}/lib, and I haven't implemented
anything about terminating the IRC process (it will likely involve
storing the IRCConnection as an Application-scoped variable).
Hopefully the code isn't too embarrassing.

> P.s.  Welcome to the list -- it was nice to meet you in person at
> BFusion / BFlex yesterday!

The pleasure was all mine!
<cfcomponent
displayname="IRCLogger.Logger"
extends="MachII.logging.loggers.AbstractLogger"
output="false"
hint="A logger for sending logging information to an IRC channel.">

<!---
PROPERTIES
--->
<cfset variables.instance.loggerTypeName = "IRC" />
<cfset variables.instance.server = "" />
<cfset variables.instance.channel = "" />
<cfset variables.instance.nick = "" />
<cfset variables.instance.bot = "" />

<!---
INITIALIZATION / CONFIGURATION
--->
<cffunction name="configure" access="public" returntype="void"
output="false"
hint="Configures the logger.">

<cfset var filter = CreateObject("component",
"MachII.logging.filters.GenericChannelFilter").init(getParameter
("filter", "")) />
<cfset var adapter = CreateObject("component",
"MachII.logging.adapters.ScopeAdapter").init(getParameters()) />

<!--- Set the filter to the adapter --->
<cfset adapter.setFilter(filter) />

<!--- Configure and set the adapter --->
<cfset adapter.configure() />
<cfset setLogAdapter(adapter) />

<!--- Configure the remaining parameters --->
<cfif isParameterDefined("server")>
<cfset setServer(getParameter("server")) />
<cfelse>
<cfthrow type="Gorgon.utils.loggers.IRCLogger"
message="A parameter named 'server' is required. The IRC server to
which the logger will connect.">
</cfif>

<cfif isParameterDefined("channel")>
<cfset setChannel(getParameter("channel")) />
<cfelse>
<cfthrow type="Gorgon.utils.loggers.IRCLogger"
message="A parameter named 'channel' is required. The IRC channel
which the logger will join.">
</cfif>

<cfif isParameterDefined("nick")>
<cfset setNick(getParameter("nick")) />
<cfelse>
<cfthrow type="Gorgon.utils.loggers.IRCLogger"
message="A parameter named 'nick' is required. The nick of the bot
that will represent the logger.">
</cfif>

<cfset setBot(createObject("java",
"f00f.net.irc.martyr.IRCConnection").init())/>
<cfset createObject("java",
"f00f.net.irc.martyr.services.AutoRegister").init(getBot(), getNick(),
getNick(), getNick())/>
<cfset createObject("java",
"f00f.net.irc.martyr.services.AutoResponder").init(getBot())/>
<cfset createObject("java",
"f00f.net.irc.martyr.services.AutoJoin").init(getBot(), getChannel())/
>
<cfset createObject("java",
"f00f.net.irc.martyr.services.AutoReconnect").init(getBot()).go
(getServer(), 6667)/>
</cffunction>

<!---
PUBLIC FUNCTIONS
--->
<cffunction name="onRequestEnd" access="public" returntype="void"
output="false"
hint="Sends an email for this logger.">

<cfset var body = "" />
<cfset var data = ArrayNew(1) />
<cfset var local = StructNew() />
<cfset var message = "" />
<cfset var messageString = ""/>

<!--- Only display output if logging is enabled --->
<cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter
().isLoggingDataDefined()>

<cfset data = getLogAdapter().getLoggingData().data />

<cfif ArrayLen(data)>
<!--- Send to the channel --->
<cfloop index="i" from="1" to="#ArrayLen(data)#">
<cfset messageString = javaCast("string", data[i].channel & ": "
& data[i].message)/>
<cfset message = createObject("java",
"f00f.net.irc.martyr.commands.MessageCommand").init(getChannel(),
messageString)/>
<cfset getBot().sendCommand(message)/>
</cfloop>
</cfif>
</cfif>
</cffunction>

<cffunction name="preRedirect" access="public" returntype="void"
output="false"
hint="Pre-redirect logic for this logger.">
<cfargument name="data" type="struct" required="true"
hint="Redirect persist data struct." />

<cflog file="ircLog" text="hit preRedirect method"/>

<cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter
().isLoggingDataDefined()>
<cfset arguments.data[getLoggerId()] = getLogAdapter
().getLoggingData() />
</cfif>
</cffunction>

<cffunction name="postRedirect" access="public" returntype="void"
output="false"
hint="Post-redirect logic for this logger.">
<cfargument name="data" type="struct" required="true"
hint="Redirect persist data struct." />

<cfset var loggingData = StructNew() />

<cflog file="ircLog" text="hit postRedirect method"/>

<cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter
().isLoggingDataDefined()>
<cftry>
<cfset loggingData = getLogAdapter().getLoggingData() />
<cfset loggingData.data = arrayConcat(arguments.data[getLoggerId
()].data, loggingData.data) />
<cfcatch type="any">
<!--- Do nothing as the configuration may have changed between
start of
the redirect and now --->
</cfcatch>
</cftry>
</cfif>
</cffunction>

<!---
PUBLIC FUNCTIONS - UTILS
--->
<cffunction name="getConfigurationData" access="public"
returntype="struct" output="false"
hint="Gets the configuration data for this logger including adapter
and filter.">

<cfset var data = StructNew() />

<cfset data["Server"] = getServer() />
<cfset data["Channel"] = getChannel() />
<cfset data["Nick"] = getNick() />
<cfset data["Logging Enabled"] = YesNoFormat(isLoggingEnabled()) />

<cfreturn data />
</cffunction>

<!---
PROTECTED FUNCTIONS
--->
<cffunction name="arrayConcat" access="private" returntype="array"
output="false"
hint="Concats two arrays together.">
<cfargument name="array1" type="array" required="true" />
<cfargument name="array2" type="array" required="true" />

<cfset var result = arguments.array1 />
<cfset var i = 0 />

<cfloop from="1" to="#ArrayLen(arguments.array2)#" index="i">
<cfset ArrayAppend(result, arguments.array2[i]) />
</cfloop>

<cfreturn result />
</cffunction>

<!---
ACCESSORS
--->
<cffunction name="setServer" access="private" returntype="void"
output="false">
<cfargument name="server" type="string" required="true" />
<cfset variables.instance.server = arguments.server />
</cffunction>
<cffunction name="getServer" access="public" returntype="string"
output="false">
<cfreturn variables.instance.server />
</cffunction>

<cffunction name="setChannel" access="private" returntype="void"
output="false">
<cfargument name="channel" type="string" required="true" />
<cfset variables.instance.channel = arguments.channel />
</cffunction>
<cffunction name="getChannel" access="public" returntype="string"
output="false">
<cfreturn variables.instance.channel />
</cffunction>

<cffunction name="setNick" access="private" returntype="void"
output="false">
<cfargument name="nick" type="string" required="true" />
<cfset variables.instance.nick = arguments.nick />
</cffunction>
<cffunction name="getNick" access="public" returntype="string"
output="false">
<cfreturn variables.instance.nick />
</cffunction>

<cffunction name="setBot" access="private" returntype="void"
output="false">
<cfargument name="bot" type="any" required="true"/>
<cfset variables.instance.bot = arguments.bot/>
</cffunction>
<cffunction name="getBot" access="private" returntype="any"
output="false">
<cfreturn variables.instance.bot/>
</cffunction>

</cfcomponent>
Reply all
Reply to author
Forward
0 new messages