What is tracing and why it is important?
There is a distinction between logging and tracing. Where logging provides a discrete view of important application events, tracing provides continuous insight into application internal state. While logging is usually used for auditing of, for example, calls to services or log-in attempts, tracing is usually used for debugging. Below is a trace example as printed in the trace.log file:
Trace commonly provides the following information: trace entry timestamp, method input parameters and return value, in-method trace information and thread id which processed a method invocation (i.e. thread 000003ac); from the provided information it is possible to understand¬†the program flow and execution duration.
But why is tracing important? Consider the following software design questions first:
- How will I troubleshoot production problems for my application?
- How long are my clients be prepared to wait for the problem’s root cause to be discovered and the problem fixed?
- How can my application be supported with limited involvement of the development team?
Often, the exception stack trace will not provide enough information for deducing the reason why an exception occurred, or an application may behave in unexpected ways without throwing an exception:
- Some activities are slower than expected,
- results are not as expected,
- sporadic errors occur.
Consider how will the above production situations, which will definitively occur, be solved for your application; I have found that most often a solution to a problem is found by:
- Discussing a problem with the users,
- searching logs for exceptions,
- enabling trace and examining input parameters and return values of methods involved in the problem.
If an application is not designed for tracing, the third option above will not be available, so your problem will be harder to solve. Therefore, tracing is important because it is an important troubleshooting tool for resolving production problems.
Let us examine a traditional approach to Java tracing concern implementation.
Traditional approach to Java tracing concern implementation
Traditional tracing concern implementation approach is highlighted in the following example:
Testing result as printed in the trace.log file:
To be useful, the above approach must be implemented consistently¬†and with¬†the same¬†coding style¬†for basically every method of¬†an application; manual implementation thus quickly becomes costly and time-consuming. Tracing code by itself will result in undiscovered bugs that will crash the application when it is enabled, or implementation may not be consistent¬†and will require frequent development team involvement for problem resolution.
Modern approach to Java tracing concern implementation
Let’s adopt the traditional tracing example for modern approach using AspectJ and enterprise-aspects¬†AOP Java library. First we extend the TracingAspect by specifying the packages which will be included for tracing – this is done once per code library or application:
And continue by simplifying the traditional tracing approach example:
Testing result as printed in the trace.log file:
We can quickly see that most of the tracing code has now been eliminated¬†with the remainder being simpler than the traditional approach. Simplicity¬†usually results in fewer bugs, better consistency and performance, and increased code coverage.
Java tracing concern design considerations
Tracing concern design should consider the following design aspects:
- Enabling and¬†disabling¬†tracing in production runtime,
- optimization of logger instantiation,
- pretty-printing objects without relying on any specific interface methods,
- performance impact of tracing code when tracing is not enabled,
- development effort to support tracing in custom code,
- ensuring tracing implementation consistency across the entire portfolio.
The enterprise-aspects AOP Java library implements the listed tracing concern design aspects as follows:
- The default logger uses the standard java.util.logging.Logger functionality, but can be customized to use other logging libraries. This ensures that production runtime tracing functionality used is the one provided by the hosting runtime components,
- one instantiated named logger instance per class, used directly and without list or array search,
- a default stringifier for transforming any Java object to JSON can be replaced with a custom one,
- performance impact when tracing is not enabled is limited to testing whether tracing is enabled,
- development effort to support tracing in custom code is reduced to extending the tracing aspect and defining the package or type pattern to which it applies. Debugging advised code remains supported,
- tracing consistency is guaranteed by the tracing aspect’s advice implementation.