/*
* Copyright 2008-2011 the original author or authors.
*
* 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.
*
* see https://github.com/lucidworks/solr-log4j2
*/
package com.nominanuda.solr;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.filter.ThresholdFilter;
import org.apache.logging.log4j.message.Message;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.logging.CircularList;
import org.apache.solr.logging.ListenerConfig;
import org.apache.solr.logging.LogWatcher;
import org.apache.solr.logging.LoggerInfo;
import com.google.common.base.Throwables;
public class Log4j2Watcher extends LogWatcher<LogEvent> {
protected class Log4j2Appender extends AbstractAppender {
private Log4j2Watcher watcher;
private ThresholdFilter filter;
private Level threshold;
Log4j2Appender(Log4j2Watcher watcher, ThresholdFilter filter, Level threshold) {
super("Log4j2WatcherAppender", filter, null);
this.watcher = watcher;
this.filter = filter;
this.threshold = threshold;
}
public void append(LogEvent logEvent) {
watcher.add(logEvent, logEvent.getTimeMillis());
}
public Level getThreshold() {
return threshold;
}
public void setThreshold(Level threshold) {
this.threshold = threshold;
removeFilter(filter);
filter = ThresholdFilter.createFilter(threshold, Filter.Result.ACCEPT, Filter.Result.DENY);
addFilter(filter);
}
}
protected class Log4j2Info extends LoggerInfo {
final Logger logger;
Log4j2Info(String name, Logger logger) {
super(name);
this.logger = logger;
}
@Override
public String getLevel() {
Object level = (logger != null) ? logger.getLevel() : null;
return (level != null) ? level.toString() : null;
}
@Override
public String getName() {
return name;
}
@Override
public boolean isSet() {
return (logger != null && logger.getLevel() != null);
}
}
public static final Logger watcherLog = LogManager.getLogger(Log4j2Watcher.class);
protected Log4j2Appender appender = null;
@Override
public String getName() {
return "Log4j2";
}
@Override
public List<String> getAllLevels() {
return Arrays.asList(Level.ALL.toString(), Level.TRACE.toString(), Level.DEBUG.toString(),
Level.INFO.toString(), Level.WARN.toString(), Level.ERROR.toString(), Level.FATAL.toString(),
Level.OFF.toString());
}
@Override
public void setLogLevel(String category, String level) {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
LoggerConfig loggerConfig = getLoggerConfig(ctx, category);
if (loggerConfig != null) {
boolean madeChanges = false;
if (level == null || "unset".equals(level) || "null".equals(level)) {
level = Level.OFF.toString();
loggerConfig.setLevel(Level.OFF);
madeChanges = true;
} else {
try {
loggerConfig.setLevel(Level.valueOf(level));
madeChanges = true;
} catch (IllegalArgumentException iae) {
watcherLog.error(level + " is not a valid log level! Valid values are: " + getAllLevels());
}
}
if (madeChanges) {
ctx.updateLoggers();
watcherLog.info("Set log level to '" + level + "' for category: " + category);
}
} else {
watcherLog
.warn("Cannot set level to '" + level + "' for category: " + category + "; no LoggerConfig found!");
}
}
protected boolean isRootLogger(String category) {
return LoggerInfo.ROOT_NAME.equals(category);
}
protected LoggerConfig getLoggerConfig(LoggerContext ctx, String category) {
Configuration config = ctx.getConfiguration();
return isRootLogger(category) ? config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME)
: config.getLoggerConfig(category);
}
@Override
public Collection<LoggerInfo> getAllLoggers() {
Logger root = LogManager.getRootLogger();
Map<String, LoggerInfo> map = new HashMap<String, LoggerInfo>();
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
for (org.apache.logging.log4j.core.Logger logger : ctx.getLoggers()) {
String name = logger.getName();
if (logger == root || root.equals(logger) || isRootLogger(name))
continue;
map.put(name, new Log4j2Info(name, logger));
while (true) {
int dot = name.lastIndexOf(".");
if (dot < 0)
break;
name = name.substring(0, dot);
if (!map.containsKey(name))
map.put(name, new Log4j2Info(name, null));
}
}
map.put(LoggerInfo.ROOT_NAME, new Log4j2Info(LoggerInfo.ROOT_NAME, root));
return map.values();
}
@Override
public void setThreshold(String level) {
Log4j2Appender app = getAppender();
Level current = app.getThreshold();
app.setThreshold(Level.toLevel(level));
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
LoggerConfig config = getLoggerConfig(ctx, LoggerInfo.ROOT_NAME);
config.removeAppender(app.getName());
config.addAppender(app, app.getThreshold(), app.getFilter());
((LoggerContext) LogManager.getContext(false)).updateLoggers();
watcherLog.info("Updated watcher threshold from " + current + " to " + level);
}
@Override
public String getThreshold() {
return String.valueOf(getAppender().getThreshold());
}
protected Log4j2Appender getAppender() {
if (appender == null)
throw new IllegalStateException(
"No appenders configured! Must call registerListener(ListenerConfig) first.");
return appender;
}
@Override
public void registerListener(ListenerConfig cfg) {
if (history != null)
throw new IllegalStateException("History already registered");
history = new CircularList<LogEvent>(cfg.size);
Level threshold = (cfg.threshold != null) ? Level.toLevel(cfg.threshold) : Level.WARN;
ThresholdFilter filter = ThresholdFilter.createFilter(threshold, Filter.Result.ACCEPT, Filter.Result.DENY);
appender = new Log4j2Appender(this, filter, threshold);
if (!appender.isStarted())
appender.start();
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
LoggerConfig config = getLoggerConfig(ctx, LoggerInfo.ROOT_NAME);
config.addAppender(appender, threshold, filter);
ctx.updateLoggers();
}
@Override
public long getTimestamp(LogEvent event) {
return event.getTimeMillis();
}
@Override
public SolrDocument toSolrDocument(LogEvent event) {
SolrDocument doc = new SolrDocument();
doc.setField("time", new Date(event.getTimeMillis()));
doc.setField("level", event.getLevel().toString());
doc.setField("logger", event.getLoggerName());
Message message = event.getMessage();
doc.setField("message", message.getFormattedMessage());
Throwable t = message.getThrowable();
if (t != null)
doc.setField("trace", Throwables.getStackTraceAsString(t));
Map<String, String> contextMap = event.getContextMap();
if (contextMap != null) {
for (String key : contextMap.keySet())
doc.setField(key, contextMap.get(key));
}
if (!doc.containsKey("core"))
doc.setField("core", ""); // avoids an ugly "undefined" column in
// the UI
return doc;
}
}