All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.logging.log4j2.Log4j2Watcher Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * 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.solr.logging.log4j2;

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 com.google.common.base.Throwables;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
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.filter.ThresholdFilter;
import org.apache.logging.log4j.message.Message;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.util.SuppressForbidden;
import org.apache.solr.logging.CircularList;
import org.apache.solr.logging.ListenerConfig;
import org.apache.solr.logging.LogWatcher;
import org.apache.solr.logging.LoggerInfo;

@SuppressForbidden(reason = "class is specific to log4j2")
public class Log4j2Watcher extends LogWatcher {

  private final static String LOG4J2_WATCHER_APPENDER = "Log4j2WatcherAppender";

  @SuppressForbidden(reason = "class is specific to log4j2")
  protected class Log4j2Appender extends AbstractAppender {

    private Log4j2Watcher watcher;
    private ThresholdFilter filter;
    private Level threshold;

    Log4j2Appender(Log4j2Watcher watcher, ThresholdFilter filter, Level threshold) {
      super(LOG4J2_WATCHER_APPENDER, 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);
    }
  }

  @SuppressForbidden(reason = "class is specific to log4j2")
  protected class Log4j2Info extends LoggerInfo {
    final Level level;

    Log4j2Info(String name, Level level) {
      super(name);
      this.level = level;
    }

    @Override
    public String getLevel() {
      return (level != null) ? level.toString() : null;
    }

    @Override
    public String getName() {
      return name;
    }

    @Override
    public boolean isSet() {
      return (level != null) ? true : false;
    }
  }

  public static final Logger log = LogManager.getLogger(Log4j2Watcher.class);

  protected Log4j2Appender appender = null;

  @Override
  public String getName() {
    return "Log4j2";
  }

  @Override
  public List 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 loggerName, String level) {
    assert loggerName != null;
    assert level != null;
    LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
    Configuration config = ctx.getConfiguration();
    LoggerConfig loggerConfig = getLoggerConfig(ctx, loggerName);
    assert loggerConfig != null;
    boolean madeChanges = false;
    if (loggerName.equals(loggerConfig.getName()) || isRootLogger(loggerName)) {
      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) {
          log.error("{} is not a valid log level! Valid values are: {}", level, getAllLevels());
        }
      }
    } else {
      //It doesn't have its own logger yet so let's create one
      LoggerConfig explicitConfig = new LoggerConfig(loggerName, Level.valueOf(level), true);
      explicitConfig.setParent(loggerConfig);
      config.addLogger(loggerName, explicitConfig);
      madeChanges = true;
    }

    if (madeChanges) {
      ctx.updateLoggers();
      if (log.isInfoEnabled()) {
        log.info("Setting log level to '{}' for logger: {}", level, loggerName);
      }
    }

  }

  protected boolean isRootLogger(String loggerName) {
    return LoggerInfo.ROOT_NAME.equals(loggerName);
  }

  protected LoggerConfig getLoggerConfig(LoggerContext ctx, String loggerName) {
    Configuration config = ctx.getConfiguration();
    return isRootLogger(loggerName) ? config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME)
                                  : config.getLoggerConfig(loggerName);
  }

  @Override
  public Collection getAllLoggers() {
    Logger root = LogManager.getRootLogger();
    LoggerContext ctx = (LoggerContext)LogManager.getContext(false);
    Map map = new HashMap<>(ctx.getLoggers().size());

    //First let's get the explicitly configured loggers
    Map loggers = ctx.getConfiguration().getLoggers();
    for(Map.Entry logger : loggers.entrySet()) {
      String name = logger.getKey();

      if (logger == root || root.equals(logger) || isRootLogger(name) || "".equals(name)) {
        continue;
      }
      map.put(name, new Log4j2Info(name, logger.getValue().getLevel()));
    }

    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.getLevel()));
      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.getLevel()));
    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();
    if (log.isInfoEnabled()) {
      log.info("Updated watcher threshold from {} to {} ", current, 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(cfg.size);

    Level threshold = (cfg.threshold != null) ? Level.toLevel(cfg.threshold) : Level.WARN;
    ThresholdFilter filter = ThresholdFilter.createFilter(threshold, Filter.Result.ACCEPT, Filter.Result.DENY);

    // If there's already an appender like this, remove it
    LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
    LoggerConfig config = getLoggerConfig(ctx, LoggerInfo.ROOT_NAME);

    appender = new Log4j2Appender(this, filter, threshold); // "Log4j2WatcherAppender"

    config.removeAppender(appender.getName());

    if (!appender.isStarted())
      appender.start();

    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 contextMap = event.getContextMap();
    if (contextMap != null) {
      for (Map.Entry entry : contextMap.entrySet())
        doc.setField(entry.getKey(), entry.getValue());
    }

    if (!doc.containsKey("core"))
      doc.setField("core", ""); // avoids an ugly "undefined" column in the UI

    return doc;
  }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy