-
Notifications
You must be signed in to change notification settings - Fork 3
Debugging
Debugging is an integral part of developing software, and it should be made as easy as possible. CFlow includes a nice debug output, that makes a few things available for you to gain insight quickly. It includes:
- The task hierarchy of the request, grouped into the execution phases (start, before, event, after, and end). If an event is canceled or dispatched, the corresponding tasks are displayed as children of the originating task. Laid out from top to bottom, you can follow the order of task execution easily.
- Execution times of all tasks, making locating bottlenecks easy. Since well-designed tasks are atomic, this gives you a very detailed idea of the performance of your application.
- Exception details, displayed within the task where the error occurred. Exceptions within threads are displayed if the thread is joined by the page thread later.
- Clear indication where an event is canceled.
- Custom messages, with or without some data you want to see displayed.
- All of the above also applies to threads (using
<thread>in the configuration), as long as the thread is joined.
This output is enabled if you use the debug context object. So in onApplicationStart(), you write:
application.context = new cflow.debug.Context();
Often you will want to display intermediate results. To display those results, you put a message on the event. In debug mode, the event object exposes an additional method record(). It accepts two arguments: the data you want to log, and optionally a message. In the debug output, your message will appear at the position where you invoked record(). The data you passed in will be dumped at the same spot.
An advantage of this approach is that you don't have to output those results in your view template, or even stop execution altogether, using <cfabort>. Displaying data in a loop used to be a hassle. This feature ensures that this information is now nicely laid out.
Unfortunately, views have no access to the event object and can therefore not put messages in the debug output. The need for that should be little though, because views shouldn't contain much logic. However, debugging a view will be necessary sometimes, and you may have to resort to old tactics here. As a tip, use <cfexit> instead of <cfabort> when you need to stop the rendering process for some reason. This will leave the debug output available.
The example on the configuration page would result in debug output that looks something like this.
By default, debug output is generated for every request. This includes requests that end with a redirect (defined with <redirect>): the redirect is delayed and instead a link is provided to the next page, so that you can inspect the application flow. This information would be lost if the redirect was actually issued.
Especially in the redirect case, but also in general, you probably don't want the debug output to appear all the time. The debug context provides you with some control over when debug output should appear, by means of the generateOutput property. Its setter accepts a string:
- always: debug output is always generated (default).
- noredirect: debug output is always generated, except when the request ends with a redirect.
- exception: debug output is only generated in the case of an exception.
- never: debug output is never generated (this is not very useful at the moment).
- [a number]: debug output is only generated if the event cycle takes longer to process than the given number of milliseconds.
In the case of a timeout argument, debug output will always be generated if an exception occurs, even if that happens within the set timeout. In other words, exceptions are never suppressed, unless you specify 'never'. If the timeout is exceeded, redirects will be displayed (delayed).
By default, debug information is displayed at the end of the generated content (in case of html output, within the body). This is not the only thing you can do with it. It is in fact quite easy to extend the default functionality so that instead of displaying the output, it is sent to a dedicated inbox. Another use case is storing the debug information in a database for later analysis.
The default functionality is implemented in DefaultOutputStrategy (in the debug folder). This component implements the OutputStrategy interface. You can write your own strategy and tell the context to use it instead:
application.context.setOutputStrategy(myOutputStrategy);
The generate() method receives an array of debug messages, which are structs. The method returns a string. This string is actually appended to the response. So, for example, in order to see what's in the array, you could write the following function inside your strategy component:
component implements="cflow.debug.OutputStrategy" {
public string function generate(required array messages) {
savecontent variable="local.output" {
WriteDump(arguments.messages);
}
return local.output;
}
}
Now you know what's inside, you can write other logic against this array.
If you just want to send the output to an e-mail address, it's easier to extend the default implementation and call super like this:
component extends="cflow.debug.DefaultOutputStrategy" {
public string function generate(required array messages) {
var output = super.generate(arguments.messages);
// send e-mail (Railo specific)
mail from="app@mycompany" to="inbox@mycompany" subject="Debug info" type="html" {
WriteOutput(output);
}
return "";
}
}
The method returns an empty string, so no debug output is actually displayed on the page itself. This is nice if you want to receive reports of usage by non-technical users.
Combined with the generateOutput property, this can be quite powerful. For instance:
application.context.setGenerateOutput(200);
will now send you an e-mail for every exception and every request that took more than 200 ms. Nice!
The overhead in collecting debug information is (probably) not very big, but it's there. So don't use the debug context in production without thinking.
