5 ms·
> but I have never seen anybody complain that "I switched to logback and slf4j and my jndi substitutions stopped working." SLF4J was created to replace Apache
by unscaled 5y ago
> but I have never seen anybody complain that "I switched to logback and slf4j and my jndi substitutions stopped working."
SLF4J was created to replace Apache Commons Logging and Logback was created to replace Log4j 1.x. Both were created Ceki Gülcü, the original author of Log4j 1.x [1].
Logback came out in 2006. The first beta version of Log4j 2.x was only released 6 years later in 2012, and the JNDI lookup feature was added in 2.0-beta9[2] in 2013!
Obviously nobody complained when switching from Log4j 1.x to SLF4J+Logback that a feature from a completely different library (with the same name) that would be created 7 years into the future was not supported.
> Somewhere in this mess, someone hooked up the pattern capabilities into the formatting system.
That's not what happened. The lookup mechanism (which includes "${jndi:}" lookups) is completely unrelated to the message formatting subsystem.
The way formatting and pattern lookups work in log4j2 is:
1. logger.info("Hello {}", "world") creates a FormattedMessage instance with the "Hello {}" format string and a single parameter, "world".
2. The FormattedMessage is wrapped in a LogEvent and routed to the correct appender(s).
3. Most appenders will format the LogEvent with a Layout. In our case, it's PatternLayout we care about[3].
4. PatternLayout will pre-calculate a set of PatternConverters based on your pattern, so it doesn't have to keep parsing the pattern on every invocation. "%m" will map to MessagePatternConverter.
5. (grossly simplifying zero-garbage and streaming optimizations) Each pattern converter is executed and appends to the final layout text's StringBuilder.
6. (grossly simplifying oh so many things) MessagePatternConverter will first call event.getMessage().getFormattedMessage(). The logic for formatting the message is entirely encapsulated by Message and its subclasses. MessagePatternConverter has no way to distinguish the format string from the user-provided parameters!
7. MessagePatternConverter finally applies the pattern lookups to the formatted message text. The pattern lookup mechanism is completely separate from and orthogonal to the message formatting mechanism.
---
That was long-winded, but I had to fight these annoying misconception about "log4j not implemented format strings properly".
Now, there are several things I'm not saying here:
1. I don't think more than a handful of people ever relied on lookups working on the log message (formatted or otherwise), as opposed to the pattern in the configuration file.
2. I don't think Log4j should have kept compatibility here. The moment the maintainers implemented "%m{nolookups}" (on version 2.7), they should have made it the default. That being said, I know this is very hard to do in the Java ecosystem. But I think it is time that the Java developer community changes its extremist position regarding compatibility at all costs.
3. I don't think that Log4j should have implemented pattern lookups for text messages to begin with. Even if was just the format string part (which is impossible to do with Log4j's current architecture anyway).
4. I don't think any kind of string formatting should be included in a logging library. If you want to format log messages, use an external formatting function or string interpolation (if you're lucky enough to be using Kotlin or Scala). If it is added, it should only be used as a convenience, and shouldn't do anything more than formatting (like lookups). Relying on developers to always remember that log.info("Hello {}", world) is safe and log.info("Hello {}" + world) gives the entire internet full control of your server is beyond stupid. Even if Log4j went with this silly distinction, I would say it was a horrible design.
[1] https://techblog.bozho.net/the-logging-mess/ https://techblog.bozho.net/the-logging-mess/
[2] https://logging.apache.org/log4j/2.x/changes-report.html#a2.0-beta9 https://logging.apache.org/log4j/2.x/changes-report.html#a2....
[3] It seems like PatternLayout is the only layout vulnerable to this bug in log4j2, but it is hard to tell, the implementation being a classic Java mess of deep class hierarchy, liberal use of reflection to control everything and some heroic attempts to break SOLID principles at least 4 times on a single line of code. Take my analysis with a grain of salt. It's a gross simplification of what is unfortunately par for the course in many Java libraries.*
- jameshart 5y agoThanks for bravely diving deeper into the class hierarchy than I did. Your analysis of what should have been done in 2.7 is spot on. Regarding my point about migration compatibility - I would not assume that the only time anyone has moved from log4j to slf4j was when slf4j first came out. Slf4j+log back is also a drop in replacement for log4j 2, up to a point.
- unscaled 5y agoThank you for your kind words! > Regarding my point about migration compatibility - I would not assume that the only time anyone has moved from log4j to slf4j was when slf4j first came out. Slf4j+log back is also a drop in replacement for log4j 2, up to a point. In a way, you're right. By themselves, SLF4J+Logback are not drop-in replacement for Log4j 2.x (or Log4j 1.x for that matter), but Log4j 2.x does provide an adapter for that sends all the calls to log4j-api interfaces to whatever is implementing SLF4J (e.g. logback). It also provides an API that does the reverse (implementing SLF4J through whatever is implementing the log4j-api). To top that up are also adapters which converts Log4j 1.x API, the JUL APIs and Apache Logging Commons (yet another facade, like SLF4J or log4j-api 2.x) to SLF4J or and log4j. Accounting for all the permutations there, there are probably thousands of slightly different migration paths that you could take, and they're all slightly different. This makes the situation a lot more complex than it seems, at first hand. I think you're imagining a project which started with Log4J 2.x and moved to SLF4J+Logback. You're right that such projects may exist, but to be more accurate: 1. By the time Log4J 2.x has started, SLF4J was already established as the standard facade for Java logging libraries. Log4J provided an SLF4J binding from the get go, and I think many (if not most) projects which ended up using Log4J are using it through the the SLF4J binding. 2. By the time Log4J 2.x started getting popular (around 2014-5?), Logback Development slowed down. It was abandoned per se. There were still or or two minor releases a year until 2018, so it didn't die out, but showed slow progress. At the same time Log4J 2.x was adding new features quickly and making some impressive performance gains in multithreaded workloads[1]. So while there were some reasons to move from Logback to Log4j 2.x, there were no strong reasons to do the reverse. In short, I don't think many people every migrated between them. There is a better argument you can make of course: Log4j 2.x just removed supports for message lookups completely and no one complained. It shows that they could have just done it years ago with little worries. But we need to work harder to change the "compatibility über alles" mindset that prevails in Java and other ecosystem. It's perfectly ok to break compatibility for 0.001% of your users when you've got a serious security issue. Punishing 99.999% of the other users with an RCE because 0.001% MIGHT rely on some back is not good engineering! [1] https://logging.apache.org/log4j/2.x/manual/async.html https://logging.apache.org/log4j/2.x/manual/async.html