| /* |
| * 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.drill.test; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.slf4j.LoggerFactory; |
| |
| import ch.qos.logback.classic.Level; |
| import ch.qos.logback.classic.Logger; |
| import ch.qos.logback.classic.LoggerContext; |
| import ch.qos.logback.classic.encoder.PatternLayoutEncoder; |
| import ch.qos.logback.classic.spi.ILoggingEvent; |
| import ch.qos.logback.core.ConsoleAppender; |
| |
| /** |
| * Establishes test-specific logging without having to alter the global |
| * <tt>logback-test.xml</tt> file. Allows directing output to the console |
| * (if not already configured) and setting the log level on specific loggers |
| * of interest in the test. The fixture automatically restores the original |
| * log configuration on exit. |
| * <p> |
| * Typical usage: <pre><code> |
| * {@literal @}Test |
| * public void myTest() { |
| * LogFixtureBuilder logBuilder = LogFixture.builder() |
| * .toConsole() |
| * .disable() // Silence all other loggers |
| * .logger(ExternalSortBatch.class, Level.DEBUG); |
| * try (LogFixture logs = logBuilder.build()) { |
| * // Test code here |
| * } |
| * }</code></pre> |
| * <p> |
| * You can – and should – combine the log fixture with the |
| * cluster and client fixtures to have complete control over your test-time |
| * Drill environment. |
| */ |
| |
| public class LogFixture implements AutoCloseable { |
| |
| // Elapsed time in ms, log level, thread, logger, message. |
| |
| public static final String DEFAULT_CONSOLE_FORMAT = "%r %level [%thread] [%logger] - %msg%n"; |
| private static final String DRILL_PACKAGE_NAME = "org.apache.drill"; |
| |
| /** |
| * Memento for a logger name and level. |
| */ |
| public static class LogSpec { |
| String loggerName; |
| Level logLevel; |
| |
| public LogSpec(String loggerName, Level level) { |
| this.loggerName = loggerName; |
| this.logLevel = level; |
| } |
| } |
| |
| /** |
| * Builds the log settings to be used for a test. The log settings here |
| * add to those specified in a <tt>logback.xml</tt> or |
| * <tt>logback-test.xml</tt> file on your class path. In particular, if |
| * the logging configuration already redirects the Drill logger to the |
| * console, setting console logging here does nothing. |
| */ |
| |
| public static class LogFixtureBuilder { |
| |
| private String consoleFormat = DEFAULT_CONSOLE_FORMAT; |
| private boolean logToConsole; |
| private List<LogSpec> loggers = new ArrayList<>(); |
| private ConsoleAppender<ILoggingEvent> appender; |
| |
| /** |
| * Send all enabled logging to the console (if not already configured.) Some |
| * Drill log configuration files send the root to the console (or file), but |
| * the Drill loggers to Lilith. In that case, Lilith "hides" the console |
| * logger. Using this call adds a console logger to the Drill logger so that |
| * output does, in fact, go to the console regardless of the configuration |
| * in the Logback configuration file. |
| * |
| * @return this builder |
| */ |
| public LogFixtureBuilder toConsole() { |
| logToConsole = true; |
| return this; |
| } |
| |
| public LogFixtureBuilder toConsole(ConsoleAppender<ILoggingEvent> appender, String format) { |
| this.appender = appender; |
| return toConsole(format); |
| } |
| |
| /** |
| * Send logging to the console using the defined format. |
| * |
| * @param format valid Logback log format |
| * @return this builder |
| */ |
| |
| public LogFixtureBuilder toConsole(String format) { |
| consoleFormat = format; |
| return toConsole(); |
| } |
| |
| /** |
| * Set a specific logger to the given level. |
| * |
| * @param loggerName name of the logger (typically used for package-level |
| * loggers) |
| * @param level the desired Logback-defined level |
| * @return this builder |
| */ |
| public LogFixtureBuilder logger(String loggerName, Level level) { |
| loggers.add(new LogSpec(loggerName, level)); |
| return this; |
| } |
| |
| /** |
| * Set a specific logger to the given level. |
| * |
| * @param loggerClass class that defines the logger (typically used for |
| * class-specific loggers) |
| * @param level the desired Logback-defined level |
| * @return this builder |
| */ |
| public LogFixtureBuilder logger(Class<?> loggerClass, Level level) { |
| loggers.add(new LogSpec(loggerClass.getName(), level)); |
| return this; |
| } |
| |
| /** |
| * Turns off all logging. If called first, you can set disable as your |
| * general policy, then turn back on loggers selectively for those |
| * of interest. |
| * @return this builder |
| */ |
| public LogFixtureBuilder disable() { |
| return rootLogger(Level.OFF); |
| } |
| |
| /** |
| * Set the desired log level on the root logger. |
| * @param level the desired Logback log level |
| * @return this builder |
| */ |
| |
| public LogFixtureBuilder rootLogger(Level level) { |
| loggers.add(new LogSpec(Logger.ROOT_LOGGER_NAME, level)); |
| return this; |
| } |
| |
| /** |
| * Apply the log levels and output, then return a fixture to be used |
| * in a try-with-resources block. The fixture automatically restores |
| * the original configuration on completion of the try block. |
| * @return the log fixture |
| */ |
| public LogFixture build() { |
| return new LogFixture(this); |
| } |
| } |
| |
| private PatternLayoutEncoder ple; |
| private ConsoleAppender<ILoggingEvent> appender; |
| private List<LogSpec> loggers = new ArrayList<>(); |
| private Logger drillLogger; |
| |
| public LogFixture(LogFixtureBuilder builder) { |
| if (builder.logToConsole) { |
| setupConsole(builder); |
| } |
| setupLoggers(builder); |
| } |
| |
| /** |
| * Creates a new log fixture builder. |
| * @return the log fixture builder |
| */ |
| |
| public static LogFixtureBuilder builder() { |
| return new LogFixtureBuilder(); |
| } |
| |
| private void setupConsole(LogFixtureBuilder builder) { |
| drillLogger = (Logger)LoggerFactory.getLogger(DRILL_PACKAGE_NAME); |
| if (builder.appender == null && drillLogger.getAppender("STDOUT") != null) { |
| return; |
| } |
| LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory(); |
| ple = new PatternLayoutEncoder(); |
| ple.setPattern(builder.consoleFormat); |
| ple.setContext(lc); |
| ple.start(); |
| |
| appender = builder.appender == null ? new ConsoleAppender<>() : builder.appender; |
| appender.setContext(lc); |
| appender.setName("Console"); |
| appender.setEncoder(ple); |
| appender.start(); |
| |
| Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); |
| root.addAppender(appender); |
| drillLogger.addAppender(appender); |
| } |
| |
| private void setupLoggers(LogFixtureBuilder builder) { |
| for (LogSpec spec : builder.loggers) { |
| setupLogger(spec); |
| } |
| } |
| |
| private void setupLogger(LogSpec spec) { |
| Logger logger = (Logger)LoggerFactory.getLogger(spec.loggerName); |
| Level oldLevel = logger.getLevel(); |
| logger.setLevel(spec.logLevel); |
| loggers.add(new LogSpec(spec.loggerName, oldLevel)); |
| } |
| |
| @Override |
| public void close() { |
| restoreLoggers(); |
| restoreConsole(); |
| } |
| |
| private void restoreLoggers() { |
| for (LogSpec spec : loggers) { |
| Logger logger = (Logger)LoggerFactory.getLogger(spec.loggerName); |
| logger.setLevel(spec.logLevel); |
| } |
| } |
| |
| private void restoreConsole() { |
| if (appender == null) { |
| return; |
| } |
| Logger root = (Logger)LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); |
| root.detachAppender(appender); |
| drillLogger.detachAppender(appender); |
| appender.stop(); |
| ple.stop(); |
| } |
| |
| } |