blob: 4848346a4e52ee1c33804ac40e3b2c8db4d31482 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.qpid.server.store.berkeleydb.logging;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.sleepycat.je.cleaner.Cleaner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Slf4jLoggingHandler extends Handler
{
private static Pattern NOT_DELETED_DUE_TO_PROTECTION = Pattern.compile("Cleaner has ([0-9]+) files not deleted because they are protected.*");
private final ConcurrentMap<String,Logger> _loggers = new ConcurrentHashMap<>();
private final int _logHandlerCleanerProtectedFilesLimit;
public Slf4jLoggingHandler(final String prefix, final int logHandlerCleanerProtectedFilesLimit)
{
setFormatter(new Formatter()
{
@Override
public String format(final LogRecord record)
{
return prefix + " " + formatMessage(record);
}
});
_logHandlerCleanerProtectedFilesLimit = logHandlerCleanerProtectedFilesLimit;
}
private interface MappedLevel
{
boolean isEnabled(final Logger logger);
void log(Logger logger, String message);
}
private static final MappedLevel ERROR = new MappedLevel()
{
@Override
public boolean isEnabled(final Logger logger)
{
return logger.isErrorEnabled();
}
@Override
public void log(final Logger logger, final String message)
{
logger.error(message);
}
};
private static final MappedLevel WARN = new MappedLevel()
{
@Override
public boolean isEnabled(final Logger logger)
{
return logger.isWarnEnabled();
}
@Override
public void log(final Logger logger, final String message)
{
logger.warn(message);
}
};
private static final MappedLevel INFO = new MappedLevel()
{
@Override
public boolean isEnabled(final Logger logger)
{
return logger.isInfoEnabled();
}
@Override
public void log(final Logger logger, final String message)
{
logger.info(message);
}
};
private static final MappedLevel DEBUG = new MappedLevel()
{
@Override
public boolean isEnabled(final Logger logger)
{
return logger.isDebugEnabled();
}
@Override
public void log(final Logger logger, final String message)
{
logger.debug(message);
}
};
private static final MappedLevel TRACE = new MappedLevel()
{
@Override
public boolean isEnabled(final Logger logger)
{
return logger.isTraceEnabled();
}
@Override
public void log(final Logger logger, final String message)
{
logger.trace(message);
}
};
private static final Map<Level, MappedLevel> LEVEL_MAP;
static
{
Map<Level, MappedLevel> map = new HashMap<>();
map.put(Level.SEVERE, ERROR);
map.put(Level.WARNING, WARN);
//Note that INFO comes out at DEBUG level as the BDB logging at INFO seems to be more of a DEBUG nature
map.put(Level.INFO, DEBUG);
map.put(Level.CONFIG, DEBUG);
map.put(Level.FINE, TRACE);
map.put(Level.FINER, TRACE);
map.put(Level.FINEST, TRACE);
map.put(Level.ALL, TRACE);
LEVEL_MAP = Collections.unmodifiableMap(map);
}
@Override
public void publish(final LogRecord record)
{
MappedLevel level = convertLevel(record);
final Logger logger = getLogger(record.getLoggerName());
if (level.isEnabled(logger))
{
Formatter formatter = getFormatter();
try
{
String message = formatter.format(record);
try
{
level.log(logger, message);
}
catch (RuntimeException e)
{
reportError(null, e, ErrorManager.WRITE_FAILURE);
}
}
catch (RuntimeException e)
{
reportError(null, e, ErrorManager.FORMAT_FAILURE);
}
}
}
private Logger getLogger(String loggerName)
{
Logger logger = _loggers.get(loggerName);
if(logger == null)
{
logger = LoggerFactory.getLogger(loggerName);
_loggers.putIfAbsent(loggerName, logger);
}
return logger;
}
private MappedLevel convertLevel(LogRecord record)
{
if (record.getLevel() == Level.SEVERE && isJECleaner(record)
&& record.getMessage().startsWith("Average cleaner backlog has grown from"))
{
// this is not a real ERROR condition; reducing level to INFO
return INFO;
}
if (record.getLevel() == Level.WARNING && isJECleaner(record))
{
Matcher matcher = NOT_DELETED_DUE_TO_PROTECTION.matcher(record.getMessage());
if (matcher.matches() && matcher.groupCount() > 0 && Integer.parseInt(matcher.group(1)) < _logHandlerCleanerProtectedFilesLimit)
{
return DEBUG;
}
}
return convertLevel(record.getLevel());
}
private boolean isJECleaner(final LogRecord record)
{
return Cleaner.class.getName().equals(record.getLoggerName());
}
@Override
public boolean isLoggable(final LogRecord record)
{
MappedLevel mappedLevel = convertLevel(record.getLevel());
return mappedLevel.isEnabled(getLogger(record.getLoggerName()));
}
private MappedLevel convertLevel(final Level level)
{
//Note that INFO comes out at DEBUG level as the BDB logging at INFO seems to be more of a DEBUG nature
MappedLevel result = LEVEL_MAP.get(level);
if(result == null)
{
if (level.intValue() >= Level.SEVERE.intValue())
{
result = ERROR;
}
else if (level.intValue() >= Level.WARNING.intValue())
{
result = WARN;
}
else if (level.intValue() >= Level.CONFIG.intValue())
{
result = DEBUG;
}
else
{
result = TRACE;
}
}
return result;
}
@Override
public void flush()
{
}
@Override
public void close() throws SecurityException
{
}
}