diff --git a/README.md b/README.md
index f2e3f249..c7f4e846 100644
--- a/README.md
+++ b/README.md
@@ -288,12 +288,12 @@ logging system, configure `jruby.rack.logging` as follows:
- `servlet_context` (default): Sends log messages to the servlet context.
- `stdout`: Sends log messages to the standard output stream `System.out`.
- `slf4j`: Sends log messages to SLF4J. SLF4J configuration is left up to you,
- please refer to http://www.slf4j.org/docs.html .
-- `log4j`: Sends log messages to log4J. Again, Log4J configuration is
- left up to you, consult http://logging.apache.org/log4j/ .
+ please refer to https://www.slf4j.org/manual.html .
+- `log4j`: Sends log messages through Log4j. Only Log4j 2.x is supported, for
+- configuration please consult https://logging.apache.org/log4j/2.x/index.html .
- `commons_logging`: Routes logs to commons-logging. You still need to configure
- an underlying logging implementation with JCL. We recommend using the logger
- library wrapper directly if possible, see http://commons.apache.org/logging/ .
+ an underlying logging implementation with JCL.
+ We recommend rather using the logger library wrapper directly when possible.
- `jul`: Directs log messages via Java's core logging facilities (util.logging).
For those loggers that require a specific named logger, set it with the
diff --git a/pom.xml b/pom.xml
index dcba07d3..1c285c76 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,6 +23,7 @@
3.0.6
${project.build.directory}/rubygems
2.0.17
+ 2.22.1
5.3.39
@@ -128,6 +129,18 @@
${slf4j.version}
test
+
+ org.apache.logging.log4j
+ log4j-api
+ ${log4j.version}
+ provided
+
+
+ org.apache.logging.log4j
+ log4j-core
+ ${log4j.version}
+ test
+
org.springframework
spring-web
diff --git a/src/main/java/org/jruby/rack/DefaultRackConfig.java b/src/main/java/org/jruby/rack/DefaultRackConfig.java
index 677c0757..410ddb24 100644
--- a/src/main/java/org/jruby/rack/DefaultRackConfig.java
+++ b/src/main/java/org/jruby/rack/DefaultRackConfig.java
@@ -414,6 +414,7 @@ private static Map getLoggerTypes() {
final Map loggerTypes = new HashMap<>(8);
loggerTypes.put("commons_logging", "org.jruby.rack.logging.CommonsLoggingLogger");
loggerTypes.put("clogging", "org.jruby.rack.logging.CommonsLoggingLogger");
+ loggerTypes.put("log4j", "org.jruby.rack.logging.Log4jLogger");
loggerTypes.put("slf4j", "org.jruby.rack.logging.Slf4jLogger");
loggerTypes.put("jul", "org.jruby.rack.logging.JulLogger");
return loggerTypes;
diff --git a/src/main/java/org/jruby/rack/logging/Log4jLogger.java b/src/main/java/org/jruby/rack/logging/Log4jLogger.java
new file mode 100644
index 00000000..f7ded7b0
--- /dev/null
+++ b/src/main/java/org/jruby/rack/logging/Log4jLogger.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2025 Karol Bucek LTD.
+ * This source code is available under the MIT license.
+ * See the file LICENSE.txt for details.
+ */
+package org.jruby.rack.logging;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jruby.rack.RackLogger;
+
+public class Log4jLogger extends RackLogger.Base {
+
+ private Logger logger;
+
+ public Log4jLogger() {
+ this(LogManager.ROOT_LOGGER_NAME);
+ }
+
+ public Log4jLogger(String loggerName) {
+ setLoggerName(loggerName);
+ }
+
+ public Logger getLogger() {
+ return logger;
+ }
+
+ public void setLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ public void setLoggerName(String loggerName) {
+ logger = LogManager.getLogger(loggerName);
+ }
+
+ @Override
+ public boolean isEnabled(Level level) {
+ if ( level == null ) return logger.isInfoEnabled();
+ switch ( level ) {
+ case DEBUG: return logger.isDebugEnabled();
+ case INFO: return logger.isInfoEnabled();
+ case WARN: return logger.isWarnEnabled();
+ case ERROR: return logger.isErrorEnabled();
+ case FATAL: return logger.isFatalEnabled();
+ }
+ return logger.isTraceEnabled();
+ }
+
+ @Override
+ public void log(Level level, CharSequence message) {
+ if ( level == null ) { logger.info(message); return; }
+ switch ( level ) {
+ case DEBUG: logger.debug(message); break;
+ case INFO: logger.info(message); break;
+ case WARN: logger.warn(message); break;
+ case ERROR: logger.error(message); break;
+ case FATAL: logger.fatal(message); break;
+ }
+ }
+
+ @Override
+ public void log(Level level, CharSequence message, Throwable ex) {
+ if ( level == null ) { logger.error(message, ex); return; }
+ switch ( level ) {
+ case DEBUG: logger.debug(message, ex); break;
+ case INFO: logger.info(message, ex); break;
+ case WARN: logger.warn(message, ex); break;
+ case ERROR: logger.error(message, ex); break;
+ case FATAL: logger.fatal(message, ex); break;
+ }
+ }
+
+ @Override
+ public Level getLevel() {
+ if ( logger.isDebugEnabled() ) return Level.DEBUG;
+ if ( logger.isInfoEnabled() ) return Level.INFO ;
+ if ( logger.isWarnEnabled() ) return Level.WARN ;
+ if ( logger.isErrorEnabled() ) return Level.ERROR;
+ if ( logger.isFatalEnabled() ) return Level.FATAL;
+ return null;
+ }
+
+}
diff --git a/src/spec/ruby/rack/config_spec.rb b/src/spec/ruby/rack/config_spec.rb
index 6039d71d..1d809f10 100644
--- a/src/spec/ruby/rack/config_spec.rb
+++ b/src/spec/ruby/rack/config_spec.rb
@@ -41,6 +41,11 @@
logger.should be_a(org.jruby.rack.logging.Slf4jLogger)
end
+ it "constructs a log4j logger from the context init param" do
+ @servlet_context.should_receive(:getInitParameter).with("jruby.rack.logging").and_return "log4j"
+ logger.should be_a(org.jruby.rack.logging.Log4jLogger)
+ end
+
it "constructs a commons logging logger from system properties" do
java.lang.System.setProperty("jruby.rack.logging", "commons_logging")
logger.should be_a(org.jruby.rack.logging.CommonsLoggingLogger)
@@ -50,13 +55,19 @@
@servlet_context.should_receive(:getInitParameter).with("jruby.rack.logging.name").and_return "/myapp"
@servlet_context.should_receive(:getInitParameter).with("jruby.rack.logging").and_return "JUL"
logger.should be_a(org.jruby.rack.logging.JulLogger)
- logger.logger.name.should == '/myapp'
+ logger.getLogger.name.should == '/myapp'
end
it "constructs a slf4j logger with default logger name" do
java.lang.System.setProperty("jruby.rack.logging", "slf4j")
logger.should be_a(org.jruby.rack.logging.Slf4jLogger)
- logger.logger.name.should == 'jruby.rack'
+ logger.getLogger.name.should == 'jruby.rack'
+ end
+
+ it "constructs a log4j logger with default logger name" do
+ java.lang.System.setProperty("jruby.rack.logging", "log4j")
+ logger.should be_a(org.jruby.rack.logging.Log4jLogger)
+ logger.getLogger.name.should == 'jruby.rack'
end
it "constructs a logger from the context init params over system properties" do