Determining Which Function Called This Function (Using ColdFusion)

Posted November 16, 2006 at 9:21 AM by Ben Nadel

Tags: ColdFusion

I was just looking through my CF-Talk emails, when Tom Chiverton dropped a bomb shell on my brain. Someone had asked how to find out which function was calling a given function. Sounds complicated right (to me it does, so back off!)? But Tom was all like, oh, that's mad easy, just thrown an error and catch it. What?!? It's that easy?

I, of course, had to immediately test this, and yes, it is that easy. First, I set up a method called StackTrace:

  • <cffunction name="StackTrace" access="public" returntype="void" output="false">
  •  
  • <!--- Define local variables. --->
  • <cfset var Trace = StructNew() />
  •  
  • <cftry>
  • <!--- Throw custom error. --->
  • <cfthrow
  • message="This is thrown to gain access to the strack trace."
  • type="StackTrace"
  • />
  •  
  • <!--- Catch custom error so that page doesn't crap out. --->
  • <cfcatch>
  •  
  • <!---
  • Create the trace object. The automatically
  • generated CFCATCH object (from CFTry/CFCatch)
  • should now have key elements that we can get
  • our hands on.
  • --->
  • <cfset Trace.StackTrace = CFCATCH.StackTrace />
  • <cfset Trace.TagContext = CFCATCH.TagContext />
  •  
  • <!--- Add stack trace to request. --->
  • <cfset ArrayAppend(
  • REQUEST.StackTraces,
  • Trace
  • ) />
  •  
  • </cfcatch>
  • </cftry>
  •  
  • <!--- Return out. --->
  • <cfreturn />
  • </cffunction>

This code throws a custom error and catches it. The CFCATCH struct that is auto-generated by the CFTry/CFCatch block has the environmental context at the point the error was thrown. This includes the Stack Trace and the Tag Context. I wrap these up in a structure and append them to a REQUEST-scoped array.

Then, I created a few functions to test this out:

  • <cffunction name="A">
  • <!--- Get stack trace. --->
  • <cfset StackTrace() />
  •  
  • <!--- Return the return of B(). --->
  • <cfreturn B() />
  • </cffunction>
  •  
  •  
  • <cffunction name="B">
  • <!--- Get stack trace. --->
  • <cfset StackTrace() />
  •  
  • <!--- Return the return of C(). --->
  • <cfreturn C() />
  • </cffunction>
  •  
  •  
  • <cffunction name="C">
  • <!--- Get stack trace. --->
  • <cfset StackTrace() />
  •  
  • <cfreturn "C is the bomb!" />
  • </cffunction>

Then, I went ahead and tested it:

  • <!--- Create the stack trace array. --->
  • cfset REQUEST.StackTraces = ArrayNew( 1 ) />
  •  
  • <!--- Call all the methods. --->
  • #A()#<br />
  • #B()#<br />
  • #C()#<br />
  •  
  • <!--- Output the stack traces. --->
  • <h2>
  • Stack Traces
  • </h2>
  •  
  •  
  • <cfloop
  • index="intI"
  • from="1"
  • to="#ArrayLen( REQUEST.StackTraces )#"
  • step="1">
  •  
  • <h4>
  • Trace #intI#
  • </h4>
  •  
  • <cfdump var="#REQUEST.StackTraces[ intI ]#" />
  •  
  • </cfloop>

This gives us some great information. Take a look at the THIRD stack trace (the one produced by A()->B()->C()):

coldfusion.runtime.CustomException: This is thrown to gain access to the strack trace. at
coldfusion.tagext.lang.ThrowTag.doStartTag(ThrowTag.java:124) at
coldfusion.runtime.CfJspPage._emptyTag(CfJspPage.java:1915) at
cfstrack_trace2ecfm1605070124$funcSTACKTRACE.runFunction(D:\...\strack_trace.cfm:10) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:348) at
coldfusion.filter.SilentFilter.invoke(SilentFilter.java:47) at
coldfusion.runtime.UDFMethod$ReturnTypeFilter.invoke(UDFMethod.java:294) at
coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:258) at
coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:56) at
coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:211) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:173) at
coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:1807) at
cfstrack_trace2ecfm1605070124$funcC.runFunction(D:\...\strack_trace.cfm:54) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:348) at
coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:258) at
coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:56) at
coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:211) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:173) at
coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:1807) at
cfstrack_trace2ecfm1605070124$funcB.runFunction(D:\...\strack_trace.cfm:47) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:348) at
coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:258) at
coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:56) at
coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:211) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:173) at
coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:1807) at
cfstrack_trace2ecfm1605070124$funcA.runFunction(D:\...\strack_trace.cfm:38) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:348) at
coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:258) at
coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:56) at
coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:211) at
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:173) at
coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:1807) at
cfstrack_trace2ecfm1605070124.runPage(D:\...\strack_trace.cfm:65) at
coldfusion.runtime.CfJspPage.invoke(CfJspPage.java:152) at
coldfusion.tagext.lang.IncludeTag.doStartTag(IncludeTag.java:349) at
coldfusion.filter.CfincludeFilter.invoke(CfincludeFilter.java:65) at
coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:210) at
coldfusion.filter.RequestMonitorFilter.invoke(RequestMonitorFilter.java:51) at
coldfusion.filter.PathFilter.invoke(PathFilter.java:86) at
coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:69) at
coldfusion.filter.BrowserDebugFilter.invoke(BrowserDebugFilter.java:52) at
coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28) at
coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:38) at
coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38) at
coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22) at
coldfusion.filter.RequestThrottleFilter.invoke(RequestThrottleFilter.java:115) at
coldfusion.CfmServlet.service(CfmServlet.java:107) at
coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:78) at
jrun.servlet.FilterChain.doFilter(FilterChain.java:86) at
com.seefusion.Filter.doFilter(Filter.java:49) at
com.seefusion.SeeFusion.doFilter(SeeFusion.java:1432) at
jrun.servlet.FilterChain.doFilter(FilterChain.java:94) at
jrun.servlet.FilterChain.service(FilterChain.java:101) at
jrun.servlet.ServletInvoker.invoke(ServletInvoker.java:91) at
jrun.servlet.JRunInvokerChain.invokeNext(JRunInvokerChain.java:42) at
jrun.servlet.JRunRequestDispatcher.invoke(JRunRequestDispatcher.java:257) at
jrun.servlet.ServletEngineService.dispatch(ServletEngineService.java:541) at
jrun.servlet.jrpp.JRunProxyService.invokeRunnable(JRunProxyService.java:204) at
jrunx.scheduler.ThreadPool$DownstreamMetrics.invokeRunnable(ThreadPool.java:318) at
jrunx.scheduler.ThreadPool$ThreadThrottle.invokeRunnable(ThreadPool.java:426) at
jrunx.scheduler.ThreadPool$UpstreamMetrics.invokeRunnable(ThreadPool.java:264) at
jrunx.scheduler.WorkerThread.run(WorkerThread.java:66)

It's not the easiest thing in the world to read, but you can certainly tell what functions call what functions. Even easier would be to look at the TagContext, but I am not in the mood to take screen shots... just trust me on that one. Anyway, Tom, you the man. Awesome suggestion!



Reader Comments

Nov 16, 2006 at 10:30 AM // reply »
4 Comments

hmm, not sure if I understood your post correctly, but wouldn't targetted cfdumps work just the same, and probably less code?

I do a lot of my deguggin with a cfdump followed by a cfabort. Then a cut and paste into the next part of my code till I find it.

Everybody does it differently I guess, whatever works for you!


Nov 16, 2006 at 10:31 AM // reply »
11 Comments

You could also just dump the cf catch in the cf catch block.

<cf catch>
<cf dump var="#cf catch#">
</cf catch>

That's assuming the original question was directed towards debugging.

Let's hope the original question wasn't based on an idea of trying to build a function that works differently depending on the caller. Someone used to working with the custom tags and functions like GetBaseTagList() might think along those lines.


Nov 16, 2006 at 10:38 AM // reply »
10,640 Comments

@Christopher,

Good point. The only issue is that would only work in code that allows output to the page. Anything inside of a CFSilent tag or an output="false" function would not allow for this.

But as the guy above states, there are lots of ways to do this, I just had never thought of this one (the one I posted about). Whatever works for you, works :)


Nov 16, 2006 at 11:34 AM // reply »
110 Comments

Ben, actually even if the code is inside cfsilent, etc., all you need to do is add a cfabort after the cfdump of the cfcatch object and you'll see it on the screen.


Nov 16, 2006 at 11:46 AM // reply »
10,640 Comments

Brian,

I am huge fan of the CFDump / CFAbort combo. The problem with it is that it haults the rest of the page. The beauty of CFTry/CFCatch solution is that you can execute the entire page and then get a full list of chained method calls / template executions.

Of course, this is only for use if you need the chained feature I suppose. If not, I whole heartedly stand behind the use of CFDump / CFAbort.


Nov 16, 2006 at 3:48 PM // reply »
13 Comments

Couldn't you just use cftrace?


Nov 16, 2006 at 3:53 PM // reply »
10,640 Comments

Qasim,

You could use CFTrace, however, it must log stuff and the user might not want to mess with the log files. Also, and I am not too familiar with CFTrace, but I think it outputs to the screen when it's called... where as this solution allows you to output to the screen at will.

Two valid solutions, two different solutions.


Nov 17, 2006 at 3:47 AM // reply »
1 Comments

Ben, nice work (and kudos to Tom). You shouldn't have to jump to the defence of it though, any aditional method of debugging can only help and shouldn't be disgarded so quickly for preffered means. There's usually a time and place where debugging methods you notmally rely on don't help much, any additional tools can only be a good thing!


Nov 17, 2006 at 7:32 PM // reply »
2 Comments

Good Job Ben...

hmmm...now I wonder who sparked that thread....oh wait...that's me ;-)

Well I guess my ramblings had to spawn a blog post somewhere sometime ;-)

Cheers


Nov 17, 2006 at 9:47 PM // reply »
10,640 Comments

Ryan,

Most definately. The more tools in the toolbelt, the better the building.


Nov 17, 2006 at 9:58 PM // reply »
10,640 Comments

Bryan,

The more conversations we (as a community) get going, the more we are all gonna start learning :)


Nov 20, 2006 at 11:32 AM // reply »
2 Comments

Absolutely Ben!! ;-)


Apr 15, 2008 at 9:23 AM // reply »
34 Comments

Excellent post Ben. Just what I was looking for.

I have a piece of code in my application (it's a HUGE webapp) that is being called somewhere in the system and we just can not figure out what the hell is calling this function.

So I will most certainly be using this immediately!! :)
Shotto!


Apr 15, 2008 at 9:25 AM // reply »
10,640 Comments

@Paolo,

I think debugging is the perfect place for this.


Nov 7, 2008 at 8:48 AM // reply »
2 Comments

You can find a stack trace tool that will throw a complete stack trace of your entire request to an external file at http://ketanjetty.com/coldfusion/monitor/


Nov 7, 2008 at 9:06 AM // reply »
10,640 Comments

@Ketan,

Does this help at all with the ability to determine at run-time, programmatically, the method that called the currently executing method?


Jan 13, 2010 at 3:31 AM // reply »
4 Comments

Hi Ben

You would not believe how many time Google refers me to your site for answers - thanks for all the effort you put into your blogs.

Based on this article, I have come up with the following to email me a stack trace

<cfmail to="#APPLICATION.sendEmailTo#" from="#APPLICATION.sendEmailTo#" subject="DEBUG #TimeFormat(Now(),'HH:mm:ss L')#" type="html">
<cftry>
<cfthrow type="Application" />
<cfcatch>
<cfloop array="#cfcatch.TagContext#" index="stTagContext" >
#stTagContext.RAW_TRACE#<br />
</cfloop>
</cfcatch>
</cftry>
</cfmail>


Jan 13, 2010 at 9:57 AM // reply »
10,640 Comments

@AJ,

Looks good; may I ask why you need the stack trace? Typically, you would need that inside an error-handler; but in that case, you would already have an exception object to work with.

If it's a proprietary algorithm, however, no pressure.


Jan 13, 2010 at 10:04 AM // reply »
4 Comments

@Ben

I was trying to work out what was calling a certain function deep within a framework (Farcry CMS). I didn't want to bomb out as the function was getting called 5 times and I wanted to see the other 4 calls. The emailed stack trace shows me all the files called to get to the function.


Jan 13, 2010 at 8:12 PM // reply »
10,640 Comments

@AJ,

Ah, gotcha. Glad it helped.


Jan 21, 2010 at 1:30 PM // reply »
1 Comments

@Ketan Jetty -

Thanks for posting your monitor code. That is exactly what I was looking for - especially the example of how to use the GetDebuggingService. I'll be modifying it to select only those pieces I need.

This could probably be used to determine which templates are in use for a website as long as you had a way to run a full coverage robot.


Jan 22, 2010 at 9:32 AM // reply »
2 Comments

@roger tobey,

Thanks for updating me on this. I am working on improving this monitoring service and hope to release a new version soon.


Oct 6, 2010 at 3:21 PM // reply »
1 Comments

Not to post way after the fact, but I added an extra bit of code to make the stack trace easier to read, and appended it to a struct key so I could add it to whatever I'm working with. HTH someone out there! Thanks for all your posts, I have learned a lot from you, keep it up, man!

  • <cffunction name="getStackTrace" access="public" returntype="struct" output="false">
  • <cfset var functionTrace = StructNew() />
  • <cfset var traceList = "">
  • <cftry>
  • <cfthrow message="I want to see you, stack trace!" type="stackTrace" />
  • <cfcatch>
  • <cfset functionTrace.stackTrace = cfcatch.stackTrace />
  • <cfset functionTrace.tagContext = cfcatch.tagContext />
  • <cfset traceList = replace(functionTrace.stackTrace, "$func", "!", "all")>
  • <cfset functionTrace.TraceIDs = listToArray(traceList, "!")>
  • <cfset arrayDeleteAt(functionTrace.traceIDs, "1")>
  • </cfcatch>
  • </cftry>
  • <cfreturn functionTrace/>
  • </cffunction>


Oct 10, 2010 at 4:30 PM // reply »
10,640 Comments

@Jessica,

Looks good. It's always nice to tie in more points of information in this kind of a situation. Certainly, I think have the tagContext is very nice as the stackTrace is... well, you see what the StackTrace is like to deal with.



Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 12, 2012 at 3:37 AM
Learning ColdFusion 8: CFImage Part III - Watermarks And Transparency
Hi Ben, Just to ask currently it is placed bottom right corner, if i need to replace the same rendered image on the bottom left side or in the bottom center, how that can be calculated. bottom ce ... read »
Feb 11, 2012 at 9:29 PM
Use jQuery's SlideDown() With Fixed-Width Elements To Prevent Jumping
I can't say how glad I am that I found your post. Thank you very much. ... read »
Feb 10, 2012 at 7:21 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
Update! Instead of $(eval(options.insertAfter)).after(data['insertData']); I now use: var ajaxNode = document.createElement('span'); var parent = $(eval(options.insertAfter))[0].parentNode; ... read »
Feb 10, 2012 at 6:18 PM
jQuery AJAX Strips Script Tags And Inserts Them After Parent-Most Elements
encountered this same, what I consider, jQuery bug last week. I'm building a site in which I load some content via AJAX. This content contains Linkedin share button placeholders which Linkedin API ne ... read »
Feb 10, 2012 at 11:30 AM
Cross-Origin Resource Sharing (CORS) AJAX Requests Between jQuery And Node.js
After you understand the concepts here, this is an awesome cheatsheet for enabling CORS in just about anything http://enable-cors.org/ ... read »
JM
Feb 10, 2012 at 9:10 AM
My Safari Browser SQLite Database Hello World Example
@Amy, Here is a very good tutorial on how to use JOIN: http://www.sqltutorial.org/sqljoin-innerjoin.aspx ... read »
Feb 10, 2012 at 4:42 AM
Building A Twitter-Inspired RESTful API Architecture In ColdFusion
This is great, very useful Ben. I spotted a small typo in the api.cgm listing: <cfthrow type="Unauthroized" /> Cheers Stefan ... read »
Feb 9, 2012 at 10:35 PM
CFDirectory Filtering Uses Pipe Character For Multiple Filters (Thanks Steve Withington)
I was wondering if there would be a filter you could apply so that you got everything but what you included in the filter. As in show me all docs that are not a .pdf. ... read »