As developers, we all should know the need for logging the code we create. Logging is essential to understand the behaviour of the application and to debug unexpected issues or for simply tracking events. In the production environment, we can’t debug issues without proper log files as they become the only source of information to debug some intermittent or unexpected errors.
Advantages of Using Loggers
- Appenders, Rolling files, new log files when a size limit is reached
- Choice of contextual information which is crucial.
- Which class
- What timestamp
- Which user
- Filename
- Right formatting
- Separate log files for different components.
- Log levels(like DEBUG, WARN, INFO) which can be very handy when you want to track the path followed by your program or log queries, etc.
As a matter of fact, all this functionality comes for free without recompiling the code. Moreover, there are other Pros as well when we deploy microservices in the cloud (AWS). If you are using CloudWatch, you can filter logs using contextual information provided by loggers. With centralized logging in place, it becomes imperative to use this mechanism.
Common Issues When Using Loggers
Keeping aside the case where we choose not to add logs, a bad code day for me already and is a completely different scenario altogether. However, the following steps can be followed for:
Logging Large Objects or Collections
Java Code logger.error("results"+resultset) or logger.error(" usermap "+map)
While this may look simple especially when we are developing and not testing with large amounts of data, however, above two logging statements it would do just fine. In production, imagine a resultset with 100,000 records or Map with 200,000 entries, even worse what if the data is hierarchical? This would be fatal!
Logging Sensitive Data
While we use loggers, we can be carefree about what to log information like:
- User personal data hidden in POJOS or DB query results
- User request data which may contain passwords
- Financial or credit card information
Its very easy to overlook this while doing development as some of this data is either not available or sometimes is not created yet. Performance Hits while Logging Many of us end up writing a simple log statement as the following:
Java code logger.debug("mydata"+map)
While we are logging it as ‘DEBUG’ so as we may think, above logging has a big performance impact depending upon what kind of object you are logging. If Map is composed of complex objects, Java ends up calling to String() on every single element even though it will not log this data because log level is set to “ERROR”. To avoid this performance issue, it is sometimes recommended to use a pre-check for existing allowed logging level like this:
Java code if (logger.isDebugEnabled()) { logger.debug("My data: " + map); }
At times, this technique helps but it also clutters the code when you have too many logging statements. So instead, you should use the following code.
Java code logger.debug("My data is : {} ", map);
This ensures the map elements are not iterated for String expression evaluation until the “DEBUG” level logging is enabled. This feature is available in the latest Log4j libraries.
What Next?
This write-up scratches the surface on the best practices for logging and it rightly intends to highlight why it is important. Choice of writing a good code is primarily a matter of intent and a habit. Moreover, we at BlazeClan adhere to great code quality practices as debugging in the cloud becomes difficult if you are not doing proper logging in your application. Stay tuned for my next post on logging on AWS.