Showing posts with label Log. Show all posts
Showing posts with label Log. Show all posts

Jar with dependencies through Maven

I'm creating a Java desktop app, a very simple one, see the previous post if you don't believe me, using Maven as build manager. The second step here is adding a dependency, and let Maven build a "fat" jar including them, to ease its deployment.

Say that I just want to use Logback classic in my application. I search for it on MvnRepository, I stay away from the alpha version so I go for 1.2.3.

Given that, modify my pom.xml is mostly a matter of copy and paste. Just remember that any dependency belongs to the dependencies element.
<project ...>
    <!-- ... -->
    <dependencies>
     <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
     </dependency>
    </dependencies>
</project>
Now I can Logback, and I'm going to do it through SLF4J.
import org.slf4j.Logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) {
        // ...
        LOG.info("Hello");
    }
}
I run my application in Eclipse, everything goes as expected, getting as console output the required log
14:32:12.986 [main] INFO simple.Main - Hello
Now I run a maven build, as done in the previous post, setting the goals to clean package, and generate a jar.

I run it from the shell in the usual way
java -cp simple-0.0.1-SNAPSHOT.jar simple.Main
And I get a nasty feedback
Exception in thread "main" java.lang.NoClassDefFoundError:
    org/slf4j/LoggerFactory at simple.Main.(Main.java:7)
Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
    at java.base/jdk.internal.loader....
    ...
    ... 1 more
However, this is an expected behavior. Maven generates by default a "slim" jar, avoiding to include in it the dependencies. The user has to specify in the classpath where Java has to find them.

When we want to create a "fat" jar, we should say to Maven explicitly to do it. We do that with the Maven Assembly Plugin.

Maven, please, when you build this project, use the maven-assembly-plugin, version 3.3, when packaging. And, remember to build also a "fat" jar with all the dependencies!
<build>
 <plugins>
  <plugin>
   <artifactId>maven-assembly-plugin</artifactId>
   <version>3.3.0</version>
   <executions>
    <execution>
     <phase>
      package
     </phase>
     <goals>
      <goal>single</goal>
     </goals>
    </execution>
   </executions>
   <configuration>
    <descriptorRefs>
     <descriptorRef>jar-with-dependencies</descriptorRef>
    </descriptorRefs>
   </configuration>
  </plugin>
 </plugins>
</build>
Now, when I run the Maven build step, I get both slim and fat jar, here is how I run the latter
java -cp simple-0.0.1-SNAPSHOT-jar-with-dependencies.jar simple.Main
It works fine, so I push the changes on github.

Go to the full post

Logging in Spring

Spring has been designed to use the Apache Commons Logging, often called JCL from its previous name, Jakarta Commons Logging. It acts as a wrapper (more technically, bridge) hiding the actual log library used by the application. By default, Logback is assumed.

JCL provides six level of logging, from trace to fatal. To show how it works, I have modified the greeting method from my simple Web Service I am playing with:
private static final Log log = LogFactory.getLog(GreetingController.class);

public String greeting() {
 log.trace("trace hello");
 log.debug("debug hello");
 log.info("info hello");
 log.warn("warn hello");
 log.error("error hello");
 log.fatal("fatal hello");
 return "Hello";
}
LogFactory is the apache.commons.logging class that acts as a factory to create a log object.

If I consume the service, I get something like this:
I see some logging in the console window generated by my app, with a couple of surprises. I miss trace and debug messages, and the fatal one is showed as a plain error.

The latter is a feature, not a bug. Since Logback has no fatal level, JCL maps a fatal request to the error level.
The first one is the default behaviour common to many loggers. If I don't specify which level of logging I want to display, only messages from info level onward are shown.
Here I want to see the full logging, so I add this line to the Spring application.properties file:
logging.level.dd.manny=trace
I restart my Spring application, consume the service, and I get the expected changes in the log.

Log to file

By default, when I ask Spring to log to file, it sends the messages to a file named spring.log, it is usually a good idea to keep this name, however it make sense to place this file in a specific folder. To do that, I add this line to the Spring properties file:
logging.path=/tmp
Last thing. I want to log _only_ to file. To do that I have to mess a bit with the actual logger (once again, in this case Logbackis assumed) configuration file. I am simply using the default one provided by Spring, specifying FILE as appender in the root element:
<root level="INFO">
 <appender-ref ref="FILE" />
</root>
Only the Spring banner is printed to standard output, the log goes straight to file.

The full Spring Boot project is on github. The relevant files are GreetingController.java, application.properties, and logback.xml.

Go to the full post