Especially since I've switched to using IntelliJ IDEA, this raises its head again just a little bit.
For configuration, there's a pecking order. Apparently, log4j2-test.xml is sought in the classpath before log4j2.xml. Hmmm... I guess the way to ensure that the first one isn't found is to put it into src/test/ somewhere because those paths don't get included bu the build (ordinarily).
The configuration file may be added just about anywhere, but if not added to an existing path already in classpath, then its location must be configured into classpath of the IDE. This is really bad. The less configuration there is, the better, say I.
I see a statement to the effect that if my project uses Maven to build, then put log4j configuration on the path src/main/resources (and, likely, log4j2-test.xml on src/test/resources). However, this seems to be disputed. Others say to put it at the root of the src folder. The problem is that there are many, knee-jerk Eclipse heads and Eclipse used to ignore proper Java pathing (preferring src/... to src/main/java/...). So, I think "the root of src" for IntelliJ IDEA and properly sourced Maven paths would be src/main/java. This should be the root they're talking of. But, that's not where I'd like it. Here are some links to this confusing discussion:
I put two files, log4j.xml and log4j-test.xml on src/main/resources and src/test/resources respectively. I find that the one for testing doesn't work. One answer said that I can only use a system property to differentiate, -Dlog4j.configuration=log4j[-test].xml.
Okay, I've sorted out all the nastiness of slf4j and log4j. It appears a) that slf4j doesn't play well with log4j2, so I stopped trying to use the newer version, b) I had to include the following dependencies in Maven. This consists of the latest slf4j and the latestf of log4j version 1 I can find without resorting to some alpha or beta stuff:
<properties> <slf4j.version>1.7.7</slf4j.version> <log4j.version>1.2.17</log4j.version> </properties> . . . <dependencies> . . . <!-- log4j support for slf4j: --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j.version}</version> <scope>test</scope> </dependency> <!-- slf4j proper: --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <!-- log4j proper: --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> . . . </dependencies>
...and here's what my log4j.xml file, which I'm placing on the path src/main/resources looks like. I've got more work to do on it because there's some brevity without sacrificing clarity I like to configure, but I haven't done that yet:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration debug="true"> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" /> </layout> </appender> <appender name="file" class="org.apache.log4j.RollingFileAppender"> <param name="append" value="false" /> <param name="maxFileSize" value="10MB" /> <param name="maxBackupIndex" value="10" /> <param name="file" value="${catalina.home}/logs/mdht-restlet.log" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" /> </layout> </appender> <root> <level value="TRACE" /> <appender-ref ref="console" /> <appender-ref ref="file" /> </root> </log4j:configuration>
I shamelessly ripped this off from the web for future use.
log4j.rootLogger=DEBUG,myAppender log4j.appender.myAppender=org.apache.log4j.RollingFileAppender log4j.appender.myAppender.file=/var/log/myapp.log log4j.appender.myAppender.maxFileSize=10MB log4j.appender.myAppender.maxBackupIndex=10 log4j.appender.myAppender.layout=org.apache.log4j.PatternLayout log4j.appender.myAppender.layout.ConversionPattern=%5p | %d | %F:%L | %m%n log4j.logger.com.acme=DEBUG log4j.logger.org.springframework=DEBUG log4j.logger.javax.faces=DEBUG log4j.logger.org.apache.myfaces=DEBUG log4j.logger.com.sun.faces=DEBUG
A service wrapper wraps around another program to allow it to live in a more useful context. For example, the Java service wrapper from Tanuki Software manages, among an array of useful solutions to problems, the logging of an application written in Java.
The Java service wrapper places an initial log line prefix prior to anything formally logged to log4j as specified in wrapper.conf. This can be confusing when attempting, without success, to tailor the lines logged to your log file. You quickly realize that there is a leftmost "column" over whose format you seemingly have no power. This is because it's defined in a different configuration file than log4j.properties, usually wrapper.conf.
See, primarily, http://wrapper.tanukisoftware.org/doc/english/introduction.html
For example,
wrapper.logfile.format=LPTM
...yields
INFO | wrapper | 2012/03/23 13:45:33.560 | INFO my message stuff...
You can turn lines logged into blank lines using
wrapper.logfile.format=
...or constrain the logfile to just that content you are trying to log via the usual log4j mechanisms:
wrapper.logfile.format=M
It's possible, and simple, to change the logging level at runtime:
import org.apache.log4j.Level; import org.apache.log4j.Logger; ... { ... Logger root = Logger.getRootLogger(); Level level = Level.toLevel( "DEBUG" ); root.setLevel( level ); ... }
...on some topics.
Please see important note on using ${catalina.home} here.
# This controls /var/log/tomcat6/catalina.out on our servers such that this file doesn't # grow beyond 550Mb, rolls each day and only 5 days back are kept. log4j.rootLogger=TRACE,console,tomcat,splunkable # Rolling-file appender for Tomcat, goes to catalina.out. ---------------------------- log4j.appender.tomcat.Threshold=INFO log4j.appender.tomcat=org.apache.log4j.DailyRollingFileAppender log4j.appender.tomcat.DatePattern='.'yyy-MM-dd log4j.appender.tomcat.File=${catalina.base}/logs/catalina.out log4j.appender.tomcat.file.MaxFileSize=500Mb log4j.appender.tomcat.file.MaxBackupIndex=5 log4j.appender.tomcat.layout=org.apache.log4j.PatternLayout log4j.appender.tomcat.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n # Splunkable log --------------------------------------------------------------------- log4j.appender.splunkable.Threshold=TRACE log4j.appender.splunkable=org.apache.log4j.RollingFileAppender log4j.appender.splunkable.File=${catalina.base}/logs/accountmgr.log log4j.appender.splunkable.MaxFileSize=1Gb log4j.appender.splunkable.MaxBackupIndex=7 log4j.appender.splunkable.layout=org.apache.log4j.PatternLayout log4j.appender.splunkable.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}: %m%n log4j.category.com.acme.web.uas.logging=TRACE,splunkable log4j.additivity.com.acme.web.uas.logging=false # This logs output to the Eclipse console when running in that mode. ----------------- log4j.appender.console.Threshold=TRACE log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.SimpleLayout log4j.appender.console.Target=System.out log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ## Legend ---------------------------------------------------------------------------- # Conversion- What's printed? # specifier # %d{ABSOLUTE} hh:mm:ss,mmm # %5p one of { INFO, DEBUG, TRACE, WARN, ERROR } # %c{1} (simple) class name # %L line number in Java file # - a hyphen # %m caller-supplied message # %n platform-specific line separation character (\n) # # E.g.: 17:12:40,013 DEBUG MongoDB:145 - Initializing Morphia for use on accountdb
I went over a complex logging scheme, such as the one illustrated above, and afterward noticed that all my JUnit tests were prefixed with the following.
log4j:ERROR setFile(null,true) call failed.
java.io.FileNotFoundException: /logs/catalina.out (No such file or directory)
at java.io.FileOutputStream.open(Native Method)
at java.io.FileOutputStream.(FileOutputStream.java:212)
at java.io.FileOutputStream.(FileOutputStream.java:136)
at org.apache.log4j.FileAppender.setFile(FileAppender.java:294)
at org.apache.log4j.FileAppender.activateOptions(FileAppender.java:165)
at org.apache.log4j.DailyRollingFileAppender.activateOptions(DailyRollingFileAppender.java:223)
.
.
.
This turned out to be quite simple. When I was running my JUnit tests, the very explicit treatment of Tomcat and other logging I put into log4j.properties was asking Log4j to open files that didn't exist in the environment.
The solution was simply to put a separate copy of log4j.properties under the test subdirectory of my project (parallel to the origin one under the src subdirectory). The final result was something like:
log4j.rootLogger=TRACE,console log4j.appender.console.Threshold=TRACE log4j.appender.console=org.apache.log4j.ConsoleAppender log4j.appender.console.layout=org.apache.log4j.SimpleLayout log4j.appender.console.Target=System.out log4j.appender.console.layout=org.apache.log4j.PatternLayout log4j.appender.console.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
See article, here.
This is different from how it was done in previous versions.
import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; . . . if( LOGGING ) { // turn on logging to INFO level... LoggerContext ctx = ( LoggerContext ) LogManager.getContext( false ); Configuration config = ctx.getConfiguration(); LoggerConfig loggerConfig = config.getLoggerConfig( LogManager.ROOT_LOGGER_NAME ); loggerConfig.setLevel( Level.INFO ); ctx.updateLoggers(); } . . .
...doesn't recognize log4j.properties but XML, JSON and other formats. And the name is by default, log4j2. Here's a working sample:
<?xml version="1.0" encoding="UTF-8"?> <Configuration xmlns="http://logging.apache.org/log4j/2.0/config"> <Properties> <Property name="LEVEL">trace</Property> <!-- Change to info in production --> <Property name="log-path">/var/log/search/</Property> <Property name="logfile-size">1 GB</Property> <Property name="cda-feeder">cda-feeder</Property> <!-- The console pattern gives calling method name and line numbers (%L). E.g.: TRACE | 2015-09-17 10:15:02 | [main] logging.Log4j2Example (Log4j2Example.java:38) - This will be printed on trace --> <Property name="console-pattern">%d{ABSOLUTE} %5p %c{1}:%L - %m%n</Property> <!-- The file pattern is shorter and more static; other differences are due simply to RollingFileAppender. E.g.: 10:15:02,987 TRACE Log4j2Example: This will be printed on trace --> <Property name="file-pattern"> %d{ABSOLUTE} %5p %c{1}: %m%n </Property> </Properties> <Appenders> <Console name="console" target="SYSTEM_OUT"> <PatternLayout pattern="%-5p | %d{yyyy-MM-dd HH:mm:ss} | [%t] %C{2} (%F:%L) - %m%n" /> </Console> <RollingFile name="rollingfile" fileName="${log-path}/${cda-feeder}.log" filePattern="${log-path}/${cda-feeder}-%d{yyyy-MM-dd}-%i.log" append="true"> <PatternLayout pattern="${file-pattern}" /> <Policies> <SizeBasedTriggeringPolicy size="${logfile-size}" /> </Policies> <DefaultRolloverStrategy max="4" /> </RollingFile> </Appenders> <Loggers> <Logger name="root" level="${LEVEL}" additivity="false"> <appender-ref ref="rollingfile" level="${LEVEL}" /> </Logger> <Root level="${LEVEL}" additivity="false"> <AppenderRef ref="console" /> <AppenderRef ref="rollingfile" /> </Root> </Loggers> </Configuration>
A list of don'ts...
if( logger.isTraceEnabled() ) ...
For a list of conversion specifiers to use in logback.xml, see LOGBack: Chapter 6: Layouts: PatternLayout.
For example, encoder pattern <pattern>%-5level [%thread] %date{ISO8601} %F:%L - %msg%n</pattern>
will output log statements with only filenames (including extensions) like:
TRACE [Native-Transport-Requests-2] 2018-02-06 11:03:03,126 IndexQueryHandler:237 - process() TRACE [Native-Transport-Requests-2] 2018-02-06 11:03:03,126 IndexQueryHandler:103 - processBatch() TRACE [Native-Transport-Requests-2] 2018-02-06 11:03:03,126 IndexQueryHandler:222 - execute() ERROR [Native-Transport-Requests-2] 2018-02-06 11:03:03,127 Message:625 - Unexpected exception during request
...whereas, pattern <pattern>%-5level [%thread] %date{ISO8601} %logger{5}:%L - %msg%n</pattern>
will output log statements with package paths (identified only by their initials) like:
TRACE [Native-Transport-Requests-2] 2018-02-06 11:03:03,126 c.p.c.s.IndexQueryHandler:237 - process() TRACE [Native-Transport-Requests-2] 2018-02-06 11:03:03,126 c.p.c.s.IndexQueryHandler:103 - processBatch() TRACE [Native-Transport-Requests-2] 2018-02-06 11:03:03,126 c.p.c.s.IndexQueryHandler:222 - execute() ERROR [Native-Transport-Requests-2] 2018-02-06 11:03:03,127 o.a.c.t.Message:625 - Unexpected exception during request
In the log immediately above, o.a.c.t.Message is an abbreviation for, org.apache.cassandra.transport.Message.class.
Consider %-5level at line 22 in the Java code below and line 15 in the XML.
package chapters.layouts; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.classic.encoder.PatternLayoutEncoder; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.ConsoleAppender; public class PatternSample { static public void main( String[] args ) throws Exception { Logger rootLogger = ( Logger ) LoggerFactory.getLogger( Logger.ROOT_LOGGER_NAME ); LoggerContext loggerContext = rootLogger.getLoggerContext(); // we are not interested in auto-configuration... loggerContext.reset(); PatternLayoutEncoder encoder = new PatternLayoutEncoder(); encoder.setContext( loggerContext ); encoder.setPattern( "%-5level [%thread]: %message%n" ); encoder.start(); ConsoleAppender< ILoggingEvent > appender = new ConsoleAppender< ILoggingEvent >(); appender.setContext( loggerContext ); appender.setEncoder( encoder ); appender.start(); rootLogger.addAppender( appender ); rootLogger.debug( "Message 1" ); rootLogger.warn( "Message 2" ); } }
<appender name="SYSTEMLOG" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>INFO</level> </filter> <file>${cassandra.logdir}/system.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> <fileNamePattern>${cassandra.logdir}/system.log.%i.zip</fileNamePattern> <minIndex>1</minIndex> <maxIndex>20</maxIndex> </rollingPolicy> <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> <maxFileSize>20MB</maxFileSize> </triggeringPolicy> <encoder> <pattern>%-5level [%thread] %date{ISO8601} %F:%L - %msg%n</pattern> </encoder> </appender>
Per PatternLayout Class, the conversion specifier %-5level simply means the level of the logging event should be left justified to a width of five characters. The conversion specifier is actually %level. For a list of conversion specifiers, see LOGBack: Chapter 6: Layouts: PatternLayout.
Here are some log4j layout ConversionPattern values and their effects (actual output forced by unit tests I've written for a product). The one marked below is my personal favorite, but it depends on what's being done, what's needed, etc.:
Note that removing the line number reduces in theory the amount of time logging takes since it obviates it from using reflection to determine it. According to Apache documentation, the following conversion specifiers will cause log4j to work harder (and run slower) in order to furnish the information requested:
C | Fully qualified name of logging class |
F | Java code filename |
l | Fully qualified methodname, source filename and line number |
L | Line number |
M | Methodname |
While L is popular, since it's going to change a lot with bug fixes and refactoring, why put it out? It would really only be helpful in code that's too long and making lots of logging calls. Choosing wording carefully, and maybe adding an identifying code to logged statements would be even more useful than the line number if it came to that.
DEBUG [MessagingService-Outgoing-/10.10.10.134-Gossip] 2018-01-30 09:20:15,181 OutboundTcpConnection.java:545 - Unable to connect to /10.10.10.134 java.net.ConnectException: Connection refused (Connection refused) at java.net.PlainSocketImpl.socketConnect(Native Method) ~[na:1.8.0_151] at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) ~[na:1.8.0_151] at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) ~[na:1.8.0_151] at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) ~[na:1.8.0_151] at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) ~[na:1.8.0_151] at java.net.Socket.connect(Socket.java:589) ~[na:1.8.0_151] at sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:673) ~[na:1.8.0_151] at sun.security.ssl.SSLSocketImpl.(SSLSocketImpl.java:451) ~[na:1.8.0_151] at sun.security.ssl.SSLSocketFactoryImpl.createSocket(SSLSocketFactoryImpl.java:137) ~[na:1.8.0_151] at org.apache.cassandra.security.SSLFactory.getSocket(SSLFactory.java:98) ~[cassandra-ss-index-plugin-3.11.0.13.jar:na] at org.apache.cassandra.net.OutboundTcpConnectionPool.newSocket(OutboundTcpConnectionPool.java:141) ~[cassandra-index-plugin-3.11.0.13.jar:na] at org.apache.cassandra.net.OutboundTcpConnectionPool.newSocket(OutboundTcpConnectionPool.java:132) ~[cassandra-index-plugin-3.11.0.13.jar:na] at org.apache.cassandra.net.OutboundTcpConnection.connect(OutboundTcpConnection.java:433) [cassandra-index-plugin-3.11.0.13.jar:na] at org.apache.cassandra.net.OutboundTcpConnection.run(OutboundTcpConnection.java:262) [cassandra-index-plugin-3.11.0.13.jar:na]
Before the JAR name in stack traces, there are tildes right before an opening square bracket. What does this mean?
At the end of each stack frame of the exception, a string consisting of the JAR file containing the relevant class followed by the "Implementation-Version" as found in that JAR's manifest will be added. If the information is uncertain, then the class packaging data will be preceded by a tilde, i.e. the '~' character.
logback and log4j go to great lengths to ensure that the class packaging information they display is correct. However, when they are unable to guarantee the absolute correctness of the information, then they will prefix the data with a tilde. Thus, it is theoretically possible for the printed class packaging information to differ from the real class packaging information.
This involves configuration in log4j.xml, that's going to hold for both production and testing, and pom.xml. First, $PROJECT_ROOT/src/main/resources/log4j.xml:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"> <log4j:configuration debug="true"> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.EnhancedPatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1.} - %m%n" /> </layout> </appender> <!-- When running unit tests, ${catalina.home} evaluates to nothing, then ${catalina.home}/logs/mdht-restlet.log becomes /log/mdht-restlet.log, which doesn't exist. --> <appender name="file" class="org.apache.log4j.RollingFileAppender"> <param name="append" value="false" /> <param name="maxFileSize" value="10MB" /> <param name="maxBackupIndex" value="10" /> <param name="file" value="${catalina.home}/logs/mdht-restlet.log" /> <layout class="org.apache.log4j.EnhancedPatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1.}:%L - %m%n" /> </layout> </appender> <root> <level value="TRACE" /> <appender-ref ref="console" /> <appender-ref ref="file" /> </root> </log4j:configuration>
The note in the comment above explains that, just because I've configured log4j carefully to work in production (which in this case is Tomcat), doesn't mean it's going to work when running JUnit under IntelliJ IDEA. What's going to happen initially is this:
log4j:ERROR setFile(null,false) call failed. java.io.FileNotFoundException: /logs/mdht-restlet.log (No such file or directory)
This is because, after evaluating ${catalina.home},/tt>/logs/mdht-restlet.log, what's left is /logs/mdht-restlet.log. Likely, that path does not exist or you do not have access to it (on Linux or Windows C:\).
The solution is to use the maven-surefire-plugin in your pom.xml. This can be used to set environment variables while running tests during your build and during execution of JUnit tests from the IDE. (I.e.: this might work also from Eclipse.)
First, however, using information from Echoing Maven property definitions using echoproperties, and running Maven from the command line, I find the following definitions that I might draw on to determine where, for testing and building, I could define ${catalina.home}. I decide to use project.build.directory as it looks very official and stable:
basedir=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet maven.multiModuleProjectDirectory=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet project.artifactId=mdht-restlet project.build.directory=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet/target project.build.outputDirectory=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet/target/classes project.build.sourceDirectory=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet/src/main/java project.build.testOutputDirectory=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet/target/test-classes project.build.testSourceDirectory=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet/src/test/java project.name=mdht-restlet user.dir=/home/russ/sandboxes/mdht-restlet.next_release.dev/code/mdht-restlet
...and then use this to define ${catalina.home} in the maven-surefire-plugin:
<build> <plugins> ... <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${maven-surefire-plugin.version}</version> <configuration> <systemPropertyVariables> <catalina.home>${project.build.directory}</catalina.home> </systemPropertyVariables> </configuration> </plugin> ... </plugins> </build>
Finally, when I run tests, I see logging statements appear on the path /home/russ/sandboxes/mdht-restlet.next_release/code/mdht-restlet/target/logs/mdht-restlet.log.
Yes, after target is erased (each build), target/logs/mdht-restlet.log will be conveniently recreated as soon as it's written to by log4j.
This information came from the Apache Maven Project page, Using System Properties.
If you see the following:
log4j: Class name: [org.apache.log4j.RollingFileAppender]
log4j:ERROR Category option "1." not a decimal integer.
java.lang.NumberFormatException: For input string: "1."
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
...
...it's likely because your using org.apache.log4j.PatternLayout in log4j.xml (or log4j.properties):
<layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1.}:%L - %m%n" /> </layout>
PatternLayout doesn't support a %c value, what's gathered by %c{1.} above, in the pattern string. Use EnhancedPatternLayout instead.
<layout class="org.apache.log4j.EnhancedPatternLayout"> <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1.}:%L - %m%n" /> </layout>
Logback is made to be used under slf4j, which is a façade for
When writing a library, it's often suggested that slf4j be used since then any logging solution (well, at least those listed above) can be put in at deploy time. Uh, this isn't true because when different versions, especially older ones, of slf4j are used, there are symbols asked for or missing. It creates a big nightmare in Maven to sort out:
I got this trying to run a unit test in something.
SLF4J: The requested version 1.5.6 by your slf4j binding is not compatible with [1.6, 1.7] SLF4J: See http://www.slf4j.org/codes.html#version_mismatch for further details. java.lang.AssertionError: java.lang.NoSuchMethodError: \ org.slf4j.helpers.MessageFormatter.arrayFormat(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String; etc.
Pulling my hair out because the only thing asking for slf4j 1.5.6 is Tika and I don't control that. However, I added to the root pomx.xml these dependencies, reasoning that they would preempt what Tika's using instead of defaulting to what Tika's using.
<properties> . . . <slf4j.version>[1.7.25]</slf4j.version> <slf4j-simple.version>${slf4j.version}</slf4j-simple.version> . . .
As a consumer of dependencies that themselves include slf4j, What you end up doing in Maven dependency statements is use the exclude element to force out older slf4j JARs from dependencies that include them. Moral of the story: do not specify slf4j's version—ever!
So, yeah, the advice is good, but be careful what you do to down-the-roaders which will happen if you specify (especially an old) Maven version: because your old one may very well show up in the link, and somebody's calling newer stuff, there will be symbols missing that are needed or consumed that aren't there.
Note that these crashes about missing/unsatisfied symbols are purely due to slf4j itself: these aren't symbols accessed by any, higher-level, consuming code.
Logback, compared to log4j is:
See a great article in Logback vs. Log4j Example.
<!-- log4j1 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <!-- log4j2 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.8.2</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.8.2</version> </dependency> <!-- logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-access</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency>
log4j, both versions, have 6 levels; logback only has 5:
Level | Used for | log4j | logback | java.util.logging | |
---|---|---|---|---|---|
TRACE | brute code tracing—über, brain-dead debugging | • | • | FINEST | |
DEBUG | putting out information useful in debugging | • | • | FINE | FINER |
INFO | commonly useful and widely desired information | • | • | INFO | CONFIG |
WARN | potentially bad happenings | • | • | WARNING | |
ERROR | error situations | • | • | SEVERE | |
FATAL | application-aborting situations | • |
// log4j: import org.apache.log4j.Logger; public class Demo { private static final Logger log = Logger.getLogger( Demo.class ); ... { if( log.isTraceEnabled() ) // (plus debug and info) log.trace( message ); log.warn( message ); // (plus error and fatal) } }
// log4j 2: import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; public class Demo { private static final Logger log = LogManager.getLogger( Demo.class ); ... { log.trace( message ); // (plus debug, info, error, warn and fatal) } }
// logback uses slf4j: import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo { private static final Logger log = LoggerFactory.getLogger( Demo.class ); ... { if( log.isTraceEnabled() ) // (plus debug, info, warn and error) log.trace( message ); } }
See article URL for full samples.