Visibility into your Java application is key to understanding how it currently works, and how it worked in the past, and how it might work in the future. Hence, by reading this article, you will understand what and where Java errors logged and how to get more value from your java error logs and make them easier to use.
What Are Java Error Logs?
Java error log is an essential part of any application as it enables developers to more easily track the root cause of errors and poor performance and fix issues before they impact end-users.
Where are Java Errors Logged?
Java log messages are usually stored on the console, but they can be transferred to a longer-term location as well. With these messages, you can easily monitor what’s going on in your application and troubleshoot issues more quickly.
Typically, developers call System.out.println() to print log messages on the console. If the console is closed, these messages are lost since they cannot be stored permanently. In order to solve this problem, developers use Java logging frameworks to store data to other locations such as a file or a database.
Java Logging frameworks
In Java, there are several frameworks for logging, and you can customize where your logs are sent. Logback and Log4j are two easy-to-use Java logging frameworks.
As a replacement for Log4j, Logback was developed. Log back provides faster implementation than Log4j, more configuration options, and greater flexibility in archiving old logs.
Log4j 2 is the newest, but adoption is still lagging.
Therefore, we shall first focus on Logback as it offers many improvements and is very common. Logback consists of three main classes: logger, appender, and layout.
- Loggers are classes that applications use to create log messages.
- Appenders transfer log messages to their final destinations as well as determine where logs are sent.
- A logger can have more than one appender such as ConsoleAppender, FileAppender, RollingFileAppender, etc. Whereas, Layouts prepare messages for outputting.
A Java logging framework such as Logback is declared as a dependency. It will be added to the pom.xml file when using Maven.
Log levels in Java
The logger class has five different levels. Each level has a different priority; from lowest to highest there are debug, info, warn, error, and fatal.
- Fatal:
It is the highest priority log message, a critical issue that causes premature termination. Java console users should be able to see these issues immediately. To display this type of message, logger.fatal(“Fatal Message”) is used.
- Error:
The application will exit if these errors are not caught in time, or when unexpected conditions occur. Defects in dependencies are often indicated by these errors. Using the logger.error(“Error Message”) method, Java error messages can be displayed.
- Warning:
Java logs of this type are below the error level. This occurs when programs use deprecated APIs or runtime situations that are undesirable, yet the program is still able to run. These messages are displayed by the logger.warn(“Warning Message”) method.
- Info:
Using the logger.info(“Information Message”) method, you can display the informational messages to indicate the progress of an application. It displays the Java runtime events such as startup or shutdown. Informational events usually provide clues to the cause of these errors.
- Debug:
Logger.debug(“Debug Message”) displays the most detailed information, which is useful for debugging Java programs. A lot of noisy data can be generated if this level is left on all the time.
Java Log formats
Log files are typically unstructured text data by default. It is therefore difficult to query them for any useful information. As a developer, it would be easy to filter all logs based on the application name or severity. The goal of log formats is to solve these problems and allow for additional analytics.
Depending on the logging framework you use, Java logs have specific formats. Let’s first look at the log4j log format.
public class TestClass {
private static final Logger logger = LoggerFactory.getLogger(TestClass.class);
public static void main(String[] args) {
logger.debug("This is debug");
}
}
Typically, the Java log starts with the date and time of the log followed by the log level, whether it is INFO, ERROR, WARNING, FATAL, or DEBUG. It also contains the method name and source class. The following contains the log message giving a brief description of the log. This additional information tells us when and where the message was generated.
You are more likely to see structured logging in JSON as it is the most popular format. Using logback, you can specify a JSON layout along with the required logging fields. Here is a sample JSON log:
{ "time": "2021-08-04 16:42:20", "level": "DEBUG ", "message": "This is debug" }
Best 16 Tips to Java Logging- Get Most Out of Your Java Logs
When you’re working with your Java application, here are some tips that you can follow to get more value out of your logs and make it easier to use them.
1.Use a Standard Logging Library
There are different ways to log: you can use a dedicated library, an API, or even just write logs to a file or directly to a logging system. In choosing your logging library, however, make sure to take into account its performance, flexibility, new log-centralization appenders, and so forth.
Switching to a newer library can take a lot of work and time if you are tied to a single framework. So, don’t forget this and choose the API that will allow you to swap logging libraries in the future. When using the SLF4J API, all you have to do is change the dependency, not the code, just as when switching from Log4j to Logback and Log4j 2.
2.Choose Your Appenders Wisely
Your log events will be delivered to your appenders. The most common appenders include Console and File Appenders.
Although useful and widely known, they may not meet your needs. For instance, you might want to write your logs asynchronously or you might want to send them over the network using Syslog appenders like this:
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d %level [%t] %c - %m%n"/>
</Console>
<Syslog name="Syslog" host="logsene-syslog-receiver.sematext.com"
port="514" protocol="TCP" format="RFC5424"
appName="11111111-2222-3333-4444-555555555555"
facility="LOCAL0" mdcId="mdc" newLine="true"/>
</Appenders>
3. Use Meaningful Messages
Using appenders such as the one shown above, however, makes your logging pipeline susceptible to network errors and communication disruptions. In that case, logs may not be delivered to their destination, which may not be acceptable. You also need to avoid blocking your system if the appender has been designed that way.
Ensure that you include messages that are unique to the given situation, describe them clearly, and inform the person reading them. You might do it like this:LOGGER.warn("Communication error");
You could also create a message like this:LOGGER.warn("Error while sending documents to events Elasticsearch server, response code %d, response message %s. The message sending will be retried.", responseCode, responseMessage);
Don’t log too much information. Keeping such data for a longer period of time will be problematic and also cost more in the end.
4. Logging Java Stack Traces
Java stack traces are an important part of Java logging. Have a look at the following code:
package com.sematext.blog.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class Log4JExceptionNoThrowable {
private static final Logger LOGGER = LogManager.getLogger(Log4JExceptionNoThrowable.class);
public static void main(String[] args) {
try {
throw new IOException("This is an I/O error");
} catch (IOException ioe) {
LOGGER.error("Error while executing main thread");
}
}
}
The above code will result in an exception being thrown and a log message will be printed on the console as follows with our default configuration:
11:42:18.952 ERROR - Error while executing main thread
As you can see, there is not much information there.
Now look at the same code with a slightly modified logging statement:
package com.sematext.blog.logging;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class Log4JException {
private static final Logger LOGGER = LogManager.getLogger(Log4JException.class);
public static void main(String[] args) {
try {
throw new IOException("This is an I/O error");
} catch (IOException ioe) {
LOGGER.error("Error while executing main thread", ioe);
}
}
}
LOGGER.error(“Error while executing main thread”, ioe);In our log message, you can see that we have included the actual exception object this time:
With our default configuration, that would result in the following error log in the console:
11:30:17.527 ERROR - Error while executing main thread java.io.IOException: This is an I/O error at com.sematext.blog.logging.Log4JException.main(Log4JException.java:13) [main/:?]
It contains important information such as the name of the class, the method where the problem occurred, and finally, the line number where the issue occurred.
The stack traces in real-life scenarios will be longer, but you should include them so you have enough information for proper debugging.
5. Logging Java Exceptions
When dealing with Java exceptions and stack traces, you shouldn’t only consider the whole stack trace, but also the lines where the error appeared, etc. At the same time, you should also think about how not to deal with exceptions.
Silently ignoring exceptions is a bad practice. Don’t ignore something important. For instance, do not do this:
try {
throw new IOException("This is an I/O error");
} catch (IOException ioe) { }
Don’t just log an exception and throw it away. Therefore, you simply pushed the problem higher in the execution stack. You should also avoid things like this:
try {
throw new IOException("This is an I/O error");
} catch (IOException ioe) {
LOGGER.error("I/O error occurred during request processing", ioe);
throw ioe;
}
6. Use Appropriate Log Level
Use the appropriate logging levels consistently – information of a similar type should be on a corresponding severity level.
Using SLF4J and Java logging frameworks, you will be able to set the appropriate log level. For example:
LOGGER.error("I/O error occurred during request processing", ioe);
7. Log in JSON
If you intend to log and examine the data manually in a file or the standard output, then logging will be more than sufficient.
Another advantage of logging in JSON is the easy handling of stack traces. It’s also possible to simply log to a Syslog destination. Assume we have the following log message in our code:LOGGER.info("This is a log message that will be logged in JSON!");
We would configure Log4J 2 to write log messages as JSON by including the following configuration:
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<JSONLayoutcompact="true" eventEol="true"></JSONLayout>
</Console>
</Appenders><Loggers> <Root level="info"> <AppenderRef ref="Console"/> </Root>
</Loggers>
</Configuration>
8. Have the Log Structure Consistent
Your log events should have a consistent structure. This applies not just to a single application or set of microservices, but to your entire application stack.
With similarly structured log events, it will be easier to analyze, compare, correlate, or even store them in a dedicated data store.
Keeping the structure is not always possible, since your full stack consists of externally developed servers, databases, search engines, queues, etc., all with their own set of logs and formats.
In order to maintain consistency, you should use the same pattern for all log files, at least those that use the same logging framework. For example, you can use a pattern like this if you are using Log4J 2:
%d %p [%t] %c{35}:%L - %m%n</Pattern>
</PatternLayout>
9. Include Context to Your Logs
For developers and DevOps, log messages are an important information context. Take a look at the following log entry:
[2020-06-29 16:25:34] [ERROR ] An error occurred
!
There was an error in the application. The only thing we know is when it happened; we don’t know where it happened or what kind of error it was.
When it comes to the code that generates the log message, you don’t need to do much to include context information. Using Log4J 2’s PatternLayout allows you to include context information. You can use a very simple pattern like this:
"%d{HH:mm:ss.SSS} %-5level - %msg%n"/>
This will result in a log message similar to the following:
17:13:08.059 INFO - This is the first INFO level log message!
However, you can also include a pattern that contains much more information:
"%d{HH:mm:ss.SSS} %c %l %-5level - %msg%n"/>
10. Java Logging in Containers
To get more value out of your log, it is important to think about the environment your application is going to be running in.
You need to decide what approach you want to take to set up logging in a containerized environment. You can use one of the logging drivers – like journald, logagent, Syslog, or JSON.
You would use the following appender configuration with Log4J 2:
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} - %m %n"/>
</Console>
</Appenders>
11. Make Sure You Don’t Log Too Much or Too Little
As you write your code and add logging, consider what will be important to be able to see if the application is working well and to identify a wrong application state and fix it. You can use this to decide what and where to log. Remember, if you add too many logs you will have information fatigue, and if you don’t have enough information you can’t troubleshoot.
12. Don’t Forget Your Audience
The logs are usually not looked at by only you. Always remember, there are multiple people looking at the logs.
During debugging sessions, the developer might look at the logs for troubleshooting. In such cases, log files can be very detailed and technical, containing details about the operation of a system.
Your users may also access your application logs. In such a case, the logs should be descriptive enough to help resolve the issue if that is even possible, or provide adequate information to the support team helping the user.
13. Avoid Logging Sensitive Information
Information that could be considered sensitive should not appear in log files or should be masked. For example, Passwords, credit card numbers, social security numbers, access tokens, and so on are considered as sensitive information. Remember, if all of that is leaked or accessed by those who shouldn’t see that may be dangerous.
Therefore, think about whether it is really necessary to have sensitive information in order to troubleshoot.
14. Use a Log Management Solution to Centralize & Monitor Java Logs
As your applications become more complex, the volume of your logs will also grow. When the volume of data grows it becomes difficult and slow to troubleshoot this way, even with logging to a file and using logs only when troubleshooting is needed. For companies who face this situation, log management solutions can help centralize and monitor log data.
A fully managed log centralization solution eliminates the need to manage yet another, usually quite complex, part of your infrastructure.
15. Log in Machine Parseable Format
Log entries are excellent for humans, but poor for machines. In some cases, reading log files manually is not enough; you need to automate the process (for instance, for alerting or auditing). Logs need to be stored centrally, and you need to be able to search them.
16. Logging isn’t just for troubleshooting purposes
Log messages can be written for different audiences as well as it can even be used for different reasons, too. Troubleshooting is certainly the most obvious use of log messages, but you can also use them very efficiently for many other things as well. For example,
For Auditing: The idea is to capture significant events of importance to the management or legal people.
For Profiling: Since logs are timestamped (sometimes to the millisecond), it can become a good tool to profile sections of a program.
For Statistics: By logging each time a certain event occurs (such as an error) you can calculate interesting statistics about the running program (or the user behaviors).
Conclusion
We understand that logging Java errors and exceptions in your code are difficult. Furthermore, deploying production code is a nerve-wracking process. But with the ability to track, analyze, and manage Java errors in real-time, you can proceed with more confidence.
That’s why here comes Seagence– the debugging tool where Java errors are automatically tracked and triaged, making troubleshooting simpler. Try it out today!