/* * Copyright 2010 Proofpoint, Inc. * * 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 io.airlift.log; import com.google.common.collect.ImmutableSortedMap; import javax.annotation.concurrent.GuardedBy; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.Reader; import java.util.Collections; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Handler; import java.util.logging.LogManager; /** * Initializes the logging subsystem. * <p> * java.util.Logging, System.out and System.err are tunneled through the logging system. * <p> * System.out and System.err are assigned to loggers named "stdout" and "stderr", respectively. */ public class Logging { private static final Logger log = Logger.get(Logging.class); private static final String ROOT_LOGGER_NAME = ""; private static final java.util.logging.Logger ROOT = java.util.logging.Logger.getLogger(""); private static Logging instance; // hard reference to loggers for which we set the level private final Map<String, java.util.logging.Logger> loggers = new ConcurrentHashMap<>(); @GuardedBy("this") private OutputStreamHandler consoleHandler; /** * Sets up default logging: * <p> * - INFO level * - Log entries are written to stderr * * @return the logging system singleton */ public static synchronized Logging initialize() { if (instance == null) { instance = new Logging(); } return instance; } private Logging() { ROOT.setLevel(Level.INFO.toJulLevel()); for (Handler handler : ROOT.getHandlers()) { ROOT.removeHandler(handler); } rewireStdStreams(); } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") private void rewireStdStreams() { logConsole(new NonCloseableOutputStream(System.err)); log.info("Logging to stderr"); redirectStdStreams(); } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") private static void redirectStdStreams() { System.setOut(new PrintStream(new LoggingOutputStream(Logger.get("stdout")), true)); System.setErr(new PrintStream(new LoggingOutputStream(Logger.get("stderr")), true)); } private synchronized void logConsole(OutputStream stream) { consoleHandler = new OutputStreamHandler(stream); ROOT.addHandler(consoleHandler); } public synchronized void disableConsole() { log.info("Disabling stderr output"); ROOT.removeHandler(consoleHandler); consoleHandler = null; } @SuppressWarnings("MethodMayBeStatic") public void logToFile(String logPath, int maxHistory, long maxSizeInBytes) { log.info("Logging to %s", logPath); RollingFileHandler rollingFileHandler = new RollingFileHandler(logPath, maxHistory, maxSizeInBytes); ROOT.addHandler(rollingFileHandler); } public Level getRootLevel() { return getLevel(ROOT_LOGGER_NAME); } public void setRootLevel(Level newLevel) { setLevel(ROOT_LOGGER_NAME, newLevel); } public void setLevels(File file) throws IOException { Properties properties = new Properties(); try (Reader reader = new FileReader(file)) { properties.load(reader); } processLevels(properties); } @SuppressWarnings("MethodMayBeStatic") public Level getLevel(String loggerName) { return getEffectiveLevel(java.util.logging.Logger.getLogger(loggerName)); } private static Level getEffectiveLevel(java.util.logging.Logger logger) { java.util.logging.Level level = logger.getLevel(); if (level == null) { java.util.logging.Logger parent = logger.getParent(); if (parent != null) { return getEffectiveLevel(parent); } } if (level == null) { return Level.OFF; } return Level.fromJulLevel(level); } @SuppressWarnings("MethodMayBeStatic") public void setLevel(String loggerName, Level level) { loggers.computeIfAbsent(loggerName, java.util.logging.Logger::getLogger) .setLevel(level.toJulLevel()); } @SuppressWarnings("MethodMayBeStatic") public Map<String, Level> getAllLevels() { ImmutableSortedMap.Builder<String, Level> levels = ImmutableSortedMap.naturalOrder(); for (String loggerName : Collections.list(LogManager.getLogManager().getLoggerNames())) { java.util.logging.Level level = java.util.logging.Logger.getLogger(loggerName).getLevel(); if (level != null) { levels.put(loggerName, Level.fromJulLevel(level)); } } return levels.build(); } private void processLevels(Properties properties) { for (Map.Entry<Object, Object> entry : properties.entrySet()) { String loggerName = entry.getKey().toString(); Level level = Level.valueOf(entry.getValue().toString().toUpperCase(Locale.US)); setLevel(loggerName, level); } } public void configure(LoggingConfiguration config) throws IOException { if (config.getLogPath() != null) { logToFile(config.getLogPath(), config.getMaxHistory(), config.getMaxSize().toBytes()); } if (!config.isConsoleEnabled()) { disableConsole(); } if (config.getLevelsFile() != null) { setLevels(new File(config.getLevelsFile())); } } }