/*
* Copyright 2005-2009 Niclas Hedhman.
*
* 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.ops4j.pax.logging.service.internal;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PaxLoggingConfigurator;
import org.knopflerfish.service.log.LogService;
import org.ops4j.pax.logging.EventAdminPoster;
import org.ops4j.pax.logging.PaxContext;
import org.ops4j.pax.logging.PaxLogger;
import org.ops4j.pax.logging.PaxLoggingService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.log.LogEntry;
public class PaxLoggingServiceImpl
implements PaxLoggingService, LogService, ManagedService, ServiceFactory
{
private LogReaderServiceImpl m_logReader;
private EventAdminPoster m_eventAdmin;
private BundleContext m_bundleContext;
private PaxContext m_context;
private ReadWriteLock m_configLock;
private LinkedList m_julLoggers;
private int m_logLevel = LOG_DEBUG;
private static final String DEFAULT_SERVICE_LOG_LEVEL = "org.ops4j.pax.logging.DefaultServiceLog.level";
public PaxLoggingServiceImpl( BundleContext context, LogReaderServiceImpl logReader, EventAdminPoster eventAdmin )
{
m_bundleContext = context;
m_logReader = logReader;
m_eventAdmin = eventAdmin;
m_context = new PaxContext();
m_configLock = new ReentrantReadWriteLock();
m_julLoggers = new LinkedList();
configureDefaults();
}
/**
* Shut down the Pax Logging service. This will reset the logging configuration entirely, so it should only be
* used just before disposing of the service instance.
*/
protected void shutdown() {
LogManager.resetConfiguration();
}
ReadWriteLock getConfigLock() {
return m_configLock;
}
public PaxLogger getLogger( Bundle bundle, String category, String fqcn )
{
Logger log4jLogger;
if( category == null )
{
// Anonymous Logger in JDK Util Logging will have a category of null.
log4jLogger = Logger.getRootLogger();
}
else
{
log4jLogger = Logger.getLogger( category );
}
return new PaxLoggerImpl( bundle, log4jLogger, fqcn, this );
}
public int getLogLevel()
{
return m_logLevel;
}
public void log( int level, String message )
{
log( level, message, null );
}
public void log( int level, String message, Throwable exception )
{
log( (ServiceReference) null, level, message, exception );
}
public void log( ServiceReference sr, int level, String message )
{
log( sr, level, message, null );
}
public void log( ServiceReference sr, int level, String message, Throwable exception )
{
log( null, sr, level, message, exception );
}
/**
* This method is used by the FrameworkHandler to log framework events.
*
* @param bundle The bundle that caused the event.
* @param level The level to be logged as.
* @param message The message.
* @param exception The exception, if any otherwise null.
*/
void log( Bundle bundle, int level, String message, Throwable exception )
{
log( bundle, null, level, message, exception );
}
private void log( Bundle bundle, ServiceReference sr, int level, String message, Throwable exception )
{
// failsafe in case bundle is null
if( null == bundle && null != sr )
{
bundle = sr.getBundle();
}
String category = "[undefined]";
if( bundle != null )
{
category = bundle.getSymbolicName();
if( null == category )
{
category = "[bundle@" + bundle.getBundleId() + ']';
}
}
PaxLogger logger = getLogger( bundle, category, "" );
if( level < LOG_ERROR )
{
logger.fatal( message, exception );
}
else
{
switch (level)
{
case LOG_ERROR:
logger.error( message, exception );
break;
case LOG_WARNING:
logger.warn( message, exception );
break;
case LOG_INFO:
logger.inform( message, exception );
break;
case LOG_DEBUG:
logger.debug( message, exception );
break;
default:
logger.trace( message, exception );
}
}
}
void handleEvents( Bundle bundle, ServiceReference sr, int level, String message, Throwable exception )
{
LogEntry entry = new LogEntryImpl( bundle, sr, level, message, exception );
m_logReader.fireEvent( entry );
// This should only be null for TestCases.
if( m_eventAdmin != null )
{
m_eventAdmin.postEvent( bundle, level, entry, message, exception, sr, getPaxContext().getContext() );
}
}
public void updated( Dictionary configuration )
throws ConfigurationException
{
if( configuration == null )
{
configureDefaults();
return;
}
Properties extracted = extractKeys( configuration );
getConfigLock().writeLock().lock();
ClassLoader loader = null;
List proxies;
try {
loader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
LogManager.resetConfiguration();
// If the updated() method is called without any log4j properties,
// then keep the default/previous configuration.
if( extracted.size() == 0 )
{
configureDefaults();
return;
}
PaxLoggingConfigurator configurator = new PaxLoggingConfigurator( m_bundleContext );
configurator.doConfigure( extracted, LogManager.getLoggerRepository() );
proxies = configurator.getProxies();
} finally {
getConfigLock().writeLock().unlock();
Thread.currentThread().setContextClassLoader(loader);
}
// Avoid holding the configuration lock when starting proxies
// It could cause deadlock if opening the service trackers block on the log because
// the service itself wants to log anything
for (Iterator iterator = proxies.iterator(); iterator.hasNext(); ) {
PaxAppenderProxy proxy = (PaxAppenderProxy) iterator.next();
proxy.open();
}
LinkedList loggers = setLevelToJavaLogging( configuration );
m_julLoggers.clear();
m_julLoggers.addAll( loggers );
}
private Properties extractKeys( Dictionary configuration )
{
Properties extracted = new Properties();
Enumeration list = configuration.keys();
while( list.hasMoreElements() )
{
Object obj = list.nextElement();
if( obj instanceof String )
{
extractKey( extracted, configuration, obj );
}
}
return extracted;
}
private void extractKey( Properties extracted, Dictionary configuration, Object obj )
{
String key = (String) obj;
Object value = configuration.get( obj );
if( key.startsWith( "log4j" ) )
{
extracted.put( key, value );
}
else if( key.startsWith( "pax." ) )
{
if( "pax.logging.entries.size".equals( key ) )
{
try
{
m_logReader.setMaxEntries( Integer.parseInt( (String) value ) );
}
catch( Exception e )
{
e.printStackTrace();
}
}
}
}
private void configureDefaults()
{
String levelName;
if( m_bundleContext == null )
{
levelName = System.getProperty( DEFAULT_SERVICE_LOG_LEVEL, "DEBUG" ).trim();
}
else
{
levelName = m_bundleContext.getProperty( DEFAULT_SERVICE_LOG_LEVEL );
if( levelName == null )
{
levelName = "DEBUG";
}
else
{
levelName = levelName.trim();
}
}
m_logLevel = convertLevel( levelName );
PaxLoggingConfigurator configurator = new PaxLoggingConfigurator( m_bundleContext );
Properties defaultProperties = new Properties();
defaultProperties.put( "log4j.rootLogger", convertLevel( m_logLevel ) + ", A1" );
defaultProperties.put( "log4j.appender.A1", "org.apache.log4j.ConsoleAppender" );
defaultProperties.put( "log4j.appender.A1.layout", "org.apache.log4j.TTCCLayout" );
// Extract System Properties prefixed with "pax.log4j", and drop the "pax." and include these
extractSystemProperties( defaultProperties );
configurator.doConfigure( defaultProperties, LogManager.getLoggerRepository() );
final java.util.logging.Logger rootLogger = java.util.logging.Logger.getLogger( "" );
rootLogger.setLevel( Level.FINE );
}
private void extractSystemProperties( Properties output )
{
Iterator list = System.getProperties().entrySet().iterator();
while( list.hasNext() )
{
Map.Entry entry = (Map.Entry) list.next();
String key = (String) entry.getKey();
if( key.startsWith( "pax.log4j" ) )
{
String value = entry.getValue().toString();
key = key.substring( 4 );
output.put( key, value );
}
}
}
/*
* use local class to delegate calls to underlying instance while keeping bundle reference
*/
public Object getService( final Bundle bundle, ServiceRegistration registration )
{
class ManagedPaxLoggingService
implements PaxLoggingService, LogService, ManagedService
{
public void log( int level, String message )
{
PaxLoggingServiceImpl.this.log( bundle, null, level, message, null );
}
public void log( int level, String message, Throwable exception )
{
PaxLoggingServiceImpl.this.log( bundle, null, level, message, exception );
}
public void log( ServiceReference sr, int level, String message )
{
PaxLoggingServiceImpl.this.log( bundle, sr, level, message, null );
}
public void log( ServiceReference sr, int level, String message, Throwable exception )
{
PaxLoggingServiceImpl.this.log( bundle, sr, level, message, exception );
}
public int getLogLevel()
{
return PaxLoggingServiceImpl.this.getLogLevel();
}
public PaxLogger getLogger( Bundle myBundle, String category, String fqcn )
{
return PaxLoggingServiceImpl.this.getLogger( myBundle, category, fqcn );
}
public void updated( Dictionary configuration )
throws ConfigurationException
{
PaxLoggingServiceImpl.this.updated( configuration );
}
public PaxContext getPaxContext()
{
return PaxLoggingServiceImpl.this.getPaxContext();
}
}
return new ManagedPaxLoggingService();
}
public void ungetService( Bundle bundle, ServiceRegistration registration, Object service )
{
// nothing to do...
}
public PaxContext getPaxContext()
{
return m_context;
}
private static int convertLevel( String levelName )
{
if( "DEBUG".equals( levelName ) )
{
return LOG_DEBUG;
}
else if( "INFO".equals( levelName ) )
{
return LOG_INFO;
}
else if( "ERROR".equals( levelName ) )
{
return LOG_ERROR;
}
else if( "WARN".equals( levelName ) )
{
return LOG_WARNING;
}
else if ( "OFF".equals( levelName ) || "NONE".equals( levelName ) )
{
return 0;
}
else
{
return LOG_DEBUG;
}
}
private static String convertLevel( int level )
{
switch( level )
{
case LOG_DEBUG:
return "DEBUG";
case LOG_INFO:
return "INFO";
case LOG_WARNING:
return "WARN";
case LOG_ERROR:
return "ERROR";
case 0:
return "OFF";
default:
return "DEBUG";
}
}
/**
* Configure Java Util Logging according to the provided configuration.
* Convert the log4j configuration to JUL config.
*
* It's necessary to do that, because with pax logging, JUL loggers are not replaced.
* So we need to configure JUL loggers in order that log messages goes correctly to log Handlers.
*
* @param configuration Properties coming from the configuration.
*/
private static LinkedList setLevelToJavaLogging( final Dictionary configuration )
{
for( Enumeration enum_ = java.util.logging.LogManager.getLogManager().getLoggerNames(); enum_.hasMoreElements();) {
String name = (String) enum_.nextElement();
java.util.logging.Logger.getLogger(name).setLevel( null );
}
LinkedList loggers = new LinkedList();
for( Enumeration keys = configuration.keys(); keys.hasMoreElements(); )
{
String name = (String) keys.nextElement();
String value = (String) configuration.get( name );
if (name.equals( "log4j.rootLogger" ))
{
setJULLevel( java.util.logging.Logger.getLogger(""), value );
// "global" comes from java.util.logging.Logger.GLOBAL_LOGGER_NAME, but that constant wasn't added until Java 1.6
setJULLevel( java.util.logging.Logger.getLogger("global"), value );
}
if (name.startsWith("log4j.logger."))
{
String packageName = name.substring( "log4j.logger.".length() );
java.util.logging.Logger logger = java.util.logging.Logger.getLogger(packageName);
setJULLevel( logger, value );
loggers.add( logger );
}
}
return loggers;
}
/**
* Set the log level to the specified JUL logger.
*
* @param logger The logger to configure
* @param log4jLevelConfig The value contained in the property file. (For example: "ERROR, file")
*/
private static void setJULLevel( java.util.logging.Logger logger, String log4jLevelConfig )
{
String crumb[] = log4jLevelConfig.split( "," );
if (crumb.length > 0)
{
Level level = log4jLevelToJULLevel( crumb[0].trim() );
logger.setLevel( level );
}
}
private static Level log4jLevelToJULLevel( final String levelProperty )
{
if( levelProperty.indexOf( "OFF" ) != -1 )
{
return Level.OFF;
}
else if( levelProperty.indexOf( "FATAL" ) != -1 )
{
return Level.SEVERE;
}
else if( levelProperty.indexOf( "ERROR" ) != -1 )
{
return Level.SEVERE;
}
else if( levelProperty.indexOf( "WARN" ) != -1 )
{
return Level.WARNING;
}
else if( levelProperty.indexOf( "INFO" ) != -1 )
{
return Level.INFO;
}
else if( levelProperty.indexOf( "DEBUG" ) != -1 )
{
return Level.FINE;
}
else if( levelProperty.indexOf( "TRACE" ) != -1 )
{
return Level.FINEST;
}
return Level.INFO;
}
}