You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-dev@logging.apache.org by ps...@apache.org on 2007/06/25 08:59:20 UTC

svn commit: r550391 - in /logging/sandbox/jul-to-log4j-bridge/src: main/java/org/apache/log4j/jul/ test/java/org/apache/log4j/jul/

Author: psmith
Date: Sun Jun 24 23:59:19 2007
New Revision: 550391

URL: http://svn.apache.org/viewvc?view=rev&rev=550391
Log:
Bug 42664 Addition of JULAppender provided by Sagin Mann.

* package structure changed slightly
* Mapping of jul<->log4j levels handled by the JULLevelConverter interface structure

NOTE: one test is currently failing under maven, but all tests appear to pass in Eclipse 
(even when run as a group).



Added:
    logging/sandbox/jul-to-log4j-bridge/src/main/java/org/apache/log4j/jul/JULAppender.java
    logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/AssertionHandler.java
    logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderCustomLevelTest.java
    logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderTest.java

Added: logging/sandbox/jul-to-log4j-bridge/src/main/java/org/apache/log4j/jul/JULAppender.java
URL: http://svn.apache.org/viewvc/logging/sandbox/jul-to-log4j-bridge/src/main/java/org/apache/log4j/jul/JULAppender.java?view=auto&rev=550391
==============================================================================
--- logging/sandbox/jul-to-log4j-bridge/src/main/java/org/apache/log4j/jul/JULAppender.java (added)
+++ logging/sandbox/jul-to-log4j-bridge/src/main/java/org/apache/log4j/jul/JULAppender.java Sun Jun 24 23:59:19 2007
@@ -0,0 +1,225 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jul;
+
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.logging.julbridge.JULLevelConverter;
+import org.apache.logging.julbridge.JULLog4jEventConverter;
+
+/**
+ * A Log4j appender for Java Logging API (aka JUL). This appender allows
+ * existing log4j-enabled applications running inside a JUL-enabled environment
+ * (like an application server) to correctly log events in the proper log level
+ * and JUL format.<br>
+ * <br>
+ * The need for this appender comes from several facts:<br>
+ * <br>
+ * 
+ * 1. JUL alone does not provide features like layouts, patterns, NDC, etc; so
+ * using JUL takes away important features from developers.<br>
+ * <br>
+ * 
+ * 2. Log4j alone does not correctly map Log4j log levels to JUL levels. The
+ * best one can do at the moment is use the all-purpose ConsoleAppender, which
+ * causes all log4j events to appear as either INFO or WARNING, depending on the
+ * appender's target (out/err) and NOT depending on the logging method
+ * (debug/trace/error/etc). Proper mapping to JUL levels (INFO, FINE, FINEST,
+ * etc) is expected to depend on the logging method being called.<br>
+ * <br>
+ * 
+ * 3. The fact that there is no appropriate log4j-JUL level mapping also means
+ * that the JUL environment mistreats events generated by the application; for
+ * example, the administrator's level-dependent rules may mistreat log4j events,
+ * or a simple query for all FATAL-level events using standard JUL interfaces
+ * would not show log4j events, making maintenance and troubleshooting
+ * difficult.<br>
+ * <br>
+ * 
+ * This appender effectively satisfies those needs:<br>
+ * <br>
+ * 
+ * 1. It fully supports layouts like ConsoleAppender<br>
+ * <br>
+ * 
+ * 2. It appropriately maps Log4j-levels to JUL levels, depending on the logging
+ * method being called. The mapping is as follows (log4j --> JUL):<br>
+ * all --> ALL<br>
+ * trace --> FINER<br>
+ * debug --> FINE<br>
+ * info --> INFO<br>
+ * warn --> WARNING<br>
+ * error --> SEVERE<br>
+ * fatal --> SEVERE<br>
+ * <br>
+ * Note: there is no *standard* JUL level between WARNING and SEVERE, so both
+ * error and fatal log4j events are mapped to SEVERE.<br>
+ * <br>
+ * 
+ * Usage and configuration is identical to using the {@link ConsoleAppener}.
+ * Any application currently configured to use the ConsoleAppender, and wishes
+ * to switch to JulAppender simply has to replace "ConsoleAppender" with
+ * "JulAppender" in its log4j configuration; for example:<br>
+ * <br>
+ * <tt>
+ * log4j.appender.julAppender=org.apache.log4j.JulAppender
+ * log4j.appender.julAppender.layout=org.apache.log4j.PatternLayout
+ * log4j.appender.julAppender.layout.ConversionPattern=%d %-5p %c - %m%n
+ * </tt><br>
+ * <br>
+ * It may even be sensible to reduce the number of elements in the pattern since
+ * JUL usually provides some context information for each logged event (JUL
+ * implementation dependent).<br>
+ * <br>
+ * 
+ * Other features:<br>
+ * <br>
+ * 
+ * 1. Automatic mapping between log4j logger hierarchy and JUL logger hierarchy;
+ * For example, a log4j logger for class com.mycompany.service.MyWebService will
+ * automatically log JUL events as if it is a JUL logger by the same name, using
+ * the respective JUL configuration and rules for that logger. If such a JUL
+ * logger is not available by the JUL environment, it will attempt to log as if
+ * it is the JUL logger com.mycompany.service, and so on.<br>
+ * <br>
+ * 
+ * 2. If a log4j event is being dropped by the JUL environment because its level
+ * is too fine, a warning will be sent to LogLog, indicating a mismatch between
+ * log4j configuration and JUL configuration.<br>
+ * <br>
+ * 
+ * 3. If, for some reason, the appropriate JUL logger cannot be obtained
+ * (usually because of a JUL-environment issue), a warning will be sent to
+ * LogLog.<br>
+ * <br>
+ * 
+ * @author Sagi Mann (sagimann@gmail.com)
+ * @author psmith
+ */
+public class JULAppender extends AppenderSkeleton {
+
+    private JULLevelConverter levelConverter = JULLog4jEventConverter.DEFAULT_LEVEL_CONVERTER;
+    private String customLevelConverterClass = null;
+
+    /** Creates a new appender with no special layout */
+    public JULAppender() {
+    }
+
+    /** Creates a new appender with the specified layout */
+    public JULAppender(Layout layout) {
+        setLayout(layout);
+    }
+
+    public void activateOptions() {
+
+        if (customLevelConverterClass != null) {
+            try {
+                Class clazz = Class.forName(customLevelConverterClass);
+                levelConverter = (JULLevelConverter) clazz.newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException(
+                        "Failed to create custom Level Converter class '" +
+                                customLevelConverterClass + "'", e);
+            }
+        }
+        
+        super.activateOptions();
+    }
+
+    /**
+     * This appender requires a layout. However, if the appender is created
+     * programatically without a layout, it will simply log the event as-is.
+     */
+    public boolean requiresLayout() {
+        return true;
+    }
+
+    public void close() {
+    }
+
+    /**
+     * Append a log event at the appropriate JUL level, depending on the log4j
+     * level.
+     */
+    protected void append(LoggingEvent loggingEvent) {
+        java.util.logging.Logger logger = java.util.logging.Logger
+                .getLogger(loggingEvent.getLoggerName());
+        if (logger == null) {
+            LogLog
+                    .warn("Cannot obtain JUL " +
+                            loggingEvent.getLoggerName() +
+                            ". " +
+                            "Verify that this appender is used while an appropriate LogManager " +
+                            "is active.");
+            return;
+        }
+
+        // Layout is optional. No layout --> the message is written without
+        // formatting.
+        // In practice, since the requiresLayout method of this appender returns
+        // true,
+        // layout should never be null at this point, however, it is possible
+        // that
+        // the logging service will support optional layouts in the future...
+
+        String msg;
+        if (layout != null) {
+            msg = layout.format(loggingEvent);
+        } else {
+            msg = loggingEvent.getRenderedMessage();
+        }
+
+        // Split the level mapping logic into 2 sections for performance reasons
+        // -
+        // All low-level messages (debug, trace, etc) are separated from the
+        // more common-level messages (a complete binary tree is an
+        // overkill...):
+
+        Level level = loggingEvent.getLevel();
+
+        java.util.logging.Level jullevel = levelConverter
+                .convertLog4jLevel(level);
+        logger.log(jullevel, msg);
+    }
+
+    /**
+     * Returns the customized {@link JULLevelConverter} implementation class
+     * that will be used in place of the default.
+     * 
+     * @return
+     */
+    public final String getCustomLevelConverterClass() {
+        return customLevelConverterClass;
+    }
+
+    /**
+     * Sets the name of the customized {@link JULLevelConverter} class
+     * implementation that will be used to map log4j and JUL Logging Levels.
+     * 
+     * A non-null value will indicate a custom implementation, otherwise a
+     * default is chosen (see {@link JULLog4jEventConverter}.
+     * 
+     * @param customLevelConverterClass
+     */
+    public final void setCustomLevelConverterClass(
+            String customLevelConverterClass) {
+        this.customLevelConverterClass = customLevelConverterClass;
+    }
+}

Added: logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/AssertionHandler.java
URL: http://svn.apache.org/viewvc/logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/AssertionHandler.java?view=auto&rev=550391
==============================================================================
--- logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/AssertionHandler.java (added)
+++ logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/AssertionHandler.java Sun Jun 24 23:59:19 2007
@@ -0,0 +1,94 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jul;
+
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/**
+ * Helper class - a JUL handler that will verify the logging activity. It is
+ * provided with an expected message to verify. Once the logger logs an event,
+ * it may test the handler using the activated() and passed() methods:<br>
+ * activated() : did the logger attempt to log anything?<br>
+ * passed(): did the logger log the correct message?<br>
+ */
+public class AssertionHandler extends Handler {
+
+    private String expectedMessage;
+    private Level expectedLevel = null;
+
+    private boolean passed = false;
+    private boolean activated = false;
+    private LogRecord lastObservedLogRecord;
+
+    public AssertionHandler() {
+        this.activated = false;
+        this.passed = false;
+    }
+
+    public final Level getExpectedLevel() {
+        return expectedLevel;
+    }
+
+    public final void setExpectedLevel(Level expectedLevel) {
+        this.expectedLevel = expectedLevel;
+    }
+
+    public void close() throws SecurityException {
+    }
+
+    public void flush() {
+    }
+
+    public void publish(LogRecord record) {
+        lastObservedLogRecord = record;
+        activated = true;
+        passed = expectedMessage.equals(record.getMessage());
+        if(expectedLevel != null) {
+            passed = passed && (expectedLevel.intValue() == record.getLevel().intValue());
+        }
+    }
+
+    public void setExpectedMessage(String expectedMessage) {
+        this.expectedMessage = expectedMessage;
+    }
+
+    public boolean passed() {
+        return passed;
+    }
+
+    public boolean activated() {
+        return activated;
+    }
+
+    public final LogRecord getLastObservedLogRecord() {
+        return lastObservedLogRecord;
+    }
+
+    public String getLastObservedLogRecordAsString() {
+        
+        LogRecord record = getLastObservedLogRecord();
+        if(record == null ) {
+            return "{no LogRecord seen}";
+        }
+        StringBuffer buf = new StringBuffer();
+        buf.append(record.getLoggerName()).append(",").append(record.getMessage());
+        return buf.toString();
+    }
+
+}
\ No newline at end of file

Added: logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderCustomLevelTest.java
URL: http://svn.apache.org/viewvc/logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderCustomLevelTest.java?view=auto&rev=550391
==============================================================================
--- logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderCustomLevelTest.java (added)
+++ logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderCustomLevelTest.java Sun Jun 24 23:59:19 2007
@@ -0,0 +1,93 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.jul;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.logging.julbridge.JULLevelConverter;
+import org.apache.logging.julbridge.JULLog4jBridge;
+
+import junit.framework.TestCase;
+
+public class JULAppenderCustomLevelTest extends TestCase{
+
+    java.util.logging.Logger julLogger;
+    Logger log;
+    AssertionHandler testHandler;
+    
+    protected void setUp() throws Exception {
+        // Prepare the JUL logger to simulate a JUL environment
+        // The logger is set to log only INFO-level events or higher.
+        // During testing, a JUL handler will verify the activity of the logger.
+        String julLoggerName = JULAppenderTest.class.getName();
+        julLogger = java.util.logging.Logger.getLogger(julLoggerName);
+        julLogger.setLevel(java.util.logging.Level.INFO);
+        //julLogger.setUseParentHandlers(false);
+        testHandler = new AssertionHandler();
+        julLogger.addHandler(testHandler);
+        
+        // Prepare the Log4j logger that will be tested using the JulAppender.
+        // It is set to log ALL events on purpose, in order to test JUL-Log4j
+        // level inconsistencies.
+        log = Logger.getLogger(JULAppenderTest.class);
+        log.setLevel(Level.ALL);
+        JULAppender appender = new JULAppender();
+        appender.setCustomLevelConverterClass(SimpleCustomLevelConverter.class.getName());
+        appender.activateOptions();
+        log.addAppender(appender);
+        
+        /*
+         * We expect ALL levels to be INFO in this case.
+         */
+        testHandler.setExpectedLevel(java.util.logging.Level.INFO);
+    }
+    
+    
+    
+    protected void tearDown() throws Exception {
+        JULLog4jBridge.repatriate();
+    }
+    
+    public void testCustomLevelTests() {
+        String message;
+        
+        message = "this is a trace message";
+        testHandler.setExpectedMessage(message);
+        log.trace(message);
+        
+        assertTrue(testHandler.passed());
+        
+        message = "this is a debug message";
+        testHandler.setExpectedMessage(message);
+        log.debug(message);
+        
+        assertTrue(testHandler.passed());
+    }
+    
+    public static class SimpleCustomLevelConverter implements JULLevelConverter{
+
+        public Level convertJuliLevel(java.util.logging.Level juliLevel) {
+            return Level.INFO;
+        }
+
+        public java.util.logging.Level convertLog4jLevel(Level log4jLevel) {
+            return java.util.logging.Level.INFO;
+        }
+        
+    }
+    
+}

Added: logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderTest.java
URL: http://svn.apache.org/viewvc/logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderTest.java?view=auto&rev=550391
==============================================================================
--- logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderTest.java (added)
+++ logging/sandbox/jul-to-log4j-bridge/src/test/java/org/apache/log4j/jul/JULAppenderTest.java Sun Jun 24 23:59:19 2007
@@ -0,0 +1,106 @@
+/*
+ * Copyright 1999-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.log4j.jul;
+
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.logging.julbridge.JULLog4jBridge;
+
+import junit.framework.*;
+
+/** JulAppender test case
+ *
+ * @author Sagi Mann (sagimann@gmail.com)
+ */
+public class JULAppenderTest extends TestCase {
+    
+    
+    java.util.logging.Logger julLogger;
+    Logger log;
+    AssertionHandler testHandler;
+    
+    
+    public JULAppenderTest(String testName) {
+        super(testName);
+    }
+    
+    
+    
+    protected void setUp() throws Exception {
+        // Prepare the JUL logger to simulate a JUL environment
+        // The logger is set to log only INFO-level events or higher.
+        // During testing, a JUL handler will verify the activity of the logger.
+        String julLoggerName = JULAppenderTest.class.getName();
+        julLogger = java.util.logging.Logger.getLogger(julLoggerName);
+        julLogger.setLevel(java.util.logging.Level.INFO);
+        //julLogger.setUseParentHandlers(false);
+        testHandler = new AssertionHandler();
+        julLogger.addHandler(testHandler);
+        
+        // Prepare the Log4j logger that will be tested using the JulAppender.
+        // It is set to log ALL events on purpose, in order to test JUL-Log4j
+        // level inconsistencies.
+        log = Logger.getLogger(JULAppenderTest.class);
+        log.setLevel(Level.ALL);
+        JULAppender appender = new JULAppender();
+        appender.activateOptions();
+        log.addAppender(appender);
+    }
+    
+    
+    
+    protected void tearDown() throws Exception {
+        JULLog4jBridge.repatriate();
+    }
+    
+    
+    
+    public void testAllLevels() {
+        String message;
+        
+        message = "this is a trace message";
+        log.trace(message);
+        assertFalse("log4j should not have received an event yet, handler should still be inactive: lastObservedEvent:" + testHandler.getLastObservedLogRecordAsString(), testHandler.activated());
+        
+        message = "this is a debug message";
+        testHandler.setExpectedMessage(message);
+        log.debug(message);
+        assertFalse(testHandler.activated());
+        
+        message = "this is an info message";
+        testHandler.setExpectedMessage(message);
+        log.info(message);
+        assertTrue(testHandler.activated() && testHandler.passed());
+        
+        message = "this is a warn message";
+        testHandler.setExpectedMessage(message);
+        log.warn(message);
+        assertTrue(testHandler.activated() && testHandler.passed());
+        
+        message = "this is an error message";
+        testHandler.setExpectedMessage(message);
+        log.error(message);
+        assertTrue(testHandler.activated() && testHandler.passed());
+        
+        message = "this is a fatal message";
+        testHandler.setExpectedMessage(message);
+        log.fatal(message);
+        assertTrue(testHandler.activated() && testHandler.passed());
+    }
+}



---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-dev-unsubscribe@logging.apache.org
For additional commands, e-mail: log4j-dev-help@logging.apache.org