import org.sonar.db.DefaultDatabase;
import org.sonar.db.purge.PurgeProfiler;
import org.sonar.db.version.DatabaseVersion;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.Props;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.component.ComponentFinder;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.process;
-
-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.joran.JoranConfigurator;
-import ch.qos.logback.classic.jul.LevelChangePropagator;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.classic.spi.LoggerContextListener;
-import ch.qos.logback.core.ConsoleAppender;
-import ch.qos.logback.core.Context;
-import ch.qos.logback.core.FileAppender;
-import ch.qos.logback.core.joran.spi.JoranException;
-import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
-import ch.qos.logback.core.rolling.RollingFileAppender;
-import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
-import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
-import java.io.File;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Set;
-import javax.annotation.CheckForNull;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.LoggerFactory;
-
-import static java.lang.String.format;
-import static java.util.Objects.requireNonNull;
-import static org.slf4j.Logger.ROOT_LOGGER_NAME;
-
-/**
- * Helps to configure Logback in a programmatic way, without using XML.
- */
-public class LogbackHelper {
-
- private static final String ALL_LOGS_TO_CONSOLE_PROPERTY = "sonar.log.console";
- private static final String PROCESS_NAME_PLACEHOLDER = "XXXX";
- private static final String THREAD_ID_PLACEHOLDER = "ZZZZ";
- private static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level";
- private static final String SONAR_PROCESS_LOG_LEVEL_PROPERTY = "sonar.log.level." + PROCESS_NAME_PLACEHOLDER;
- private static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy";
- private static final String MAX_FILES_PROPERTY = "sonar.log.maxFiles";
- private static final String LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level " + PROCESS_NAME_PLACEHOLDER + "[" + THREAD_ID_PLACEHOLDER + "][%logger{20}] %msg%n";
- private static final Level[] ALLOWED_ROOT_LOG_LEVELS = new Level[] {Level.TRACE, Level.DEBUG, Level.INFO};
-
- public static Collection<Level> allowedLogLevels() {
- return Arrays.asList(ALLOWED_ROOT_LOG_LEVELS);
- }
-
- public LoggerContext getRootContext() {
- org.slf4j.Logger logger;
- while (!((logger = LoggerFactory.getLogger(ROOT_LOGGER_NAME)) instanceof Logger)) {
- // It occurs when the initialization step is still not finished because of a race condition
- // on ILoggerFactory.getILoggerFactory
- // http://jira.qos.ch/browse/SLF4J-167
- // Substitute loggers are used.
- // http://www.slf4j.org/codes.html#substituteLogger
- // Bug is not fixed in SLF4J 1.7.14.
- try {
- Thread.sleep(10);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- return ((Logger) logger).getLoggerContext();
- }
-
- public LoggerContextListener enableJulChangePropagation(LoggerContext loggerContext) {
- LevelChangePropagator propagator = new LevelChangePropagator();
- propagator.setContext(loggerContext);
- propagator.start();
- loggerContext.addListener(propagator);
- return propagator;
- }
-
- public static final class RootLoggerConfig {
- private final ProcessId processId;
- private final String threadIdFieldPattern;
-
- private RootLoggerConfig(Builder builder) {
- this.processId = requireNonNull(builder.processId);
- this.threadIdFieldPattern = builder.threadIdFieldPattern;
- }
-
- public static Builder newRootLoggerConfigBuilder() {
- return new Builder();
- }
-
- ProcessId getProcessId() {
- return processId;
- }
-
- String getThreadIdFieldPattern() {
- return threadIdFieldPattern;
- }
-
- public static final class Builder {
- @CheckForNull
- private ProcessId processId;
- private String threadIdFieldPattern = "";
-
- private Builder() {
- // prevents instantiation outside RootLoggerConfig, use static factory method
- }
-
- public Builder setProcessId(ProcessId processId) {
- this.processId = processId;
- return this;
- }
-
- public Builder setThreadIdFieldPattern(String threadIdFieldPattern) {
- this.threadIdFieldPattern = requireNonNull(threadIdFieldPattern, "threadIdFieldPattern can't be null");
- return this;
- }
-
- public RootLoggerConfig build() {
- return new RootLoggerConfig(this);
- }
- }
- }
-
- public String buildLogPattern(LogbackHelper.RootLoggerConfig config) {
- return LOG_FORMAT
- .replace(PROCESS_NAME_PLACEHOLDER, config.getProcessId().getKey())
- .replace(THREAD_ID_PLACEHOLDER, config.getThreadIdFieldPattern());
- }
-
- /**
- * Creates a new {@link ConsoleAppender} to {@code System.out} with the specified name and log pattern.
- *
- * @see #buildLogPattern(RootLoggerConfig)
- */
- public ConsoleAppender<ILoggingEvent> newConsoleAppender(Context loggerContext, String name, String logPattern) {
- PatternLayoutEncoder consoleEncoder = new PatternLayoutEncoder();
- consoleEncoder.setContext(loggerContext);
- consoleEncoder.setPattern(logPattern);
- consoleEncoder.start();
- ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
- consoleAppender.setContext(loggerContext);
- consoleAppender.setEncoder(consoleEncoder);
- consoleAppender.setName(name);
- consoleAppender.setTarget("System.out");
- consoleAppender.start();
- return consoleAppender;
- }
-
- /**
- * Make logback configuration for a process to push all its logs to a log file.
- * <p>
- * <ul>
- * <li>the file's name will use the prefix defined in {@link RootLoggerConfig#getProcessId()#getLogFilenamePrefix()}.</li>
- * <li>the file will follow the rotation policy defined in property {@link #ROLLING_POLICY_PROPERTY} and
- * the max number of files defined in property {@link #MAX_FILES_PROPERTY}</li>
- * <li>the logs will follow the specified log pattern</li>
- * </ul>
- * </p>
- *
- * @see #buildLogPattern(RootLoggerConfig)
- */
- public FileAppender<ILoggingEvent> configureGlobalFileLog(Props props, RootLoggerConfig config, String logPattern) {
- LoggerContext ctx = getRootContext();
- Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
- FileAppender<ILoggingEvent> fileAppender = newFileAppender(ctx, props, config, logPattern);
- rootLogger.addAppender(fileAppender);
- return fileAppender;
- }
-
- public FileAppender<ILoggingEvent> newFileAppender(LoggerContext ctx, Props props, LogbackHelper.RootLoggerConfig config, String logPattern) {
- RollingPolicy rollingPolicy = createRollingPolicy(ctx, props, config.getProcessId().getLogFilenamePrefix());
- FileAppender<ILoggingEvent> fileAppender = rollingPolicy.createAppender("file_" + config.getProcessId().getLogFilenamePrefix());
- fileAppender.setContext(ctx);
- PatternLayoutEncoder fileEncoder = new PatternLayoutEncoder();
- fileEncoder.setContext(ctx);
- fileEncoder.setPattern(logPattern);
- fileEncoder.start();
- fileAppender.setEncoder(fileEncoder);
- fileAppender.start();
- return fileAppender;
- }
-
- /**
- * Make the logback configuration for a sub process to correctly push all its logs to be read by a stream gobbler
- * on the sub process's System.out.
- *
- * @see #buildLogPattern(RootLoggerConfig)
- */
- public void configureForSubprocessGobbler(Props props, String logPattern) {
- if (isAllLogsToConsoleEnabled(props)) {
- LoggerContext ctx = getRootContext();
- ctx.getLogger(ROOT_LOGGER_NAME).addAppender(newConsoleAppender(ctx, "root_console", logPattern));
- }
- }
-
- /**
- * Finds out whether we are in testing environment (usually ITs) and logs of all processes must be forward to
- * App's System.out. This is specified by the value of property {@link #ALL_LOGS_TO_CONSOLE_PROPERTY}.
- */
- public boolean isAllLogsToConsoleEnabled(Props props) {
- return props.valueAsBoolean(ALL_LOGS_TO_CONSOLE_PROPERTY, false);
- }
-
- /**
- * Configure the log level of the root logger reading the value of property {@link #SONAR_LOG_LEVEL_PROPERTY}.
- *
- * @throws IllegalArgumentException if the value of {@link #SONAR_LOG_LEVEL_PROPERTY} is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
- */
- public Level configureRootLogLevel(Props props, ProcessId processId) {
- Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()));
- getRootContext().getLogger(ROOT_LOGGER_NAME).setLevel(newLevel);
- return newLevel;
- }
-
- /**
- * Resolve a log level reading the value of specified properties.
- * <p>
- * To compute the applied log level the following rules will be followed:
- * <ul>the last property with a defined and valid value in the order of the {@code propertyKeys} argument will be applied</ul>
- * <ul>if there is none, {@link Level#INFO INFO} will be returned</ul>
- * </p>
- *
- * @throws IllegalArgumentException if the value of the specified property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
- */
- private static Level resolveLevel(Props props, String... propertyKeys) {
- Level newLevel = Level.INFO;
- for (String propertyKey : propertyKeys) {
- Level level = getPropertyValueAsLevel(props, propertyKey);
- if (level != null) {
- newLevel = level;
- }
- }
- return newLevel;
- }
-
- @CheckForNull
- private static Level getPropertyValueAsLevel(Props props, String propertyKey) {
- String value = props.value(propertyKey);
- if (value == null) {
- return null;
- }
-
- Level level = Level.toLevel(value, Level.INFO);
- if (!isAllowed(level)) {
- throw new IllegalArgumentException(format("log level %s in property %s is not a supported value (allowed levels are %s)",
- level, propertyKey, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
- }
- return level;
- }
-
- private static boolean isAllowed(Level level) {
- for (Level allowedRootLogLevel : ALLOWED_ROOT_LOG_LEVELS) {
- if (level == allowedRootLogLevel) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Configure the log level of the root logger to the specified level.
- *
- * @throws IllegalArgumentException if the specified level is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
- */
- public Level configureRootLogLevel(Level newLevel) {
- Logger rootLogger = getRootContext().getLogger(ROOT_LOGGER_NAME);
- ensureSupportedLevel(newLevel);
- rootLogger.setLevel(newLevel);
- return newLevel;
- }
-
- private static void ensureSupportedLevel(Level newLevel) {
- if (!isAllowed(newLevel)) {
- throw new IllegalArgumentException(format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
- }
- }
-
- public Level configureLoggerLogLevelFromDomain(String loggerName, Props props, ProcessId processId, LogDomain domain) {
- return configureLoggersLogLevelFromDomain(Collections.singleton(loggerName), props, processId, domain);
- }
-
- public Level configureLoggersLogLevelFromDomain(Set<String> loggerNames, Props props, ProcessId processId, LogDomain domain) {
- String processProperty = SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey());
- Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, processProperty, processProperty + "." + domain.key);
- loggerNames.forEach(loggerName -> {
- Logger logger = getRootContext().getLogger(loggerName);
- logger.setLevel(newLevel);
- });
- return newLevel;
- }
-
- /**
- * Configure the log level of the specified logger to specified level.
- * <p>
- * Any level is allowed.
- * </p>
- */
- public Logger configureLogger(String loggerName, Level level) {
- Logger logger = getRootContext().getLogger(loggerName);
- logger.setLevel(level);
- return logger;
- }
-
- public Level getLoggerLevel(String loggerName) {
- return getRootContext().getLogger(loggerName).getLevel();
- }
-
- /**
- * Generally used to reset logback in logging tests
- */
- public void resetFromXml(String xmlResourcePath) throws JoranException {
- LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
- JoranConfigurator configurator = new JoranConfigurator();
- configurator.setContext(context);
- context.reset();
- configurator.doConfigure(LogbackHelper.class.getResource(xmlResourcePath));
- }
-
- public RollingPolicy createRollingPolicy(Context ctx, Props props, String filenamePrefix) {
- String rollingPolicy = props.value(ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd");
- int maxFiles = props.valueAsInt(MAX_FILES_PROPERTY, 7);
- File logsDir = props.nonNullValueAsFile(ProcessProperties.PATH_LOGS);
-
- if (rollingPolicy.startsWith("time:")) {
- return new TimeRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "time:"));
-
- } else if (rollingPolicy.startsWith("size:")) {
- return new SizeRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "size:"));
-
- } else if ("none".equals(rollingPolicy)) {
- return new NoRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles);
-
- } else {
- throw new MessageException(format("Unsupported value for property %s: %s", ROLLING_POLICY_PROPERTY, rollingPolicy));
- }
- }
-
- public abstract static class RollingPolicy {
- protected final Context context;
- final String filenamePrefix;
- final File logsDir;
- final int maxFiles;
-
- RollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles) {
- this.context = context;
- this.filenamePrefix = filenamePrefix;
- this.logsDir = logsDir;
- this.maxFiles = maxFiles;
- }
-
- public abstract FileAppender<ILoggingEvent> createAppender(String appenderName);
- }
-
- /**
- * Log files are not rotated, for example for unix command logrotate is in place.
- */
- private static class NoRollingPolicy extends RollingPolicy {
- private NoRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles) {
- super(context, filenamePrefix, logsDir, maxFiles);
- }
-
- @Override
- public FileAppender<ILoggingEvent> createAppender(String appenderName) {
- FileAppender<ILoggingEvent> appender = new FileAppender<>();
- appender.setContext(context);
- appender.setFile(new File(logsDir, filenamePrefix + ".log").getAbsolutePath());
- appender.setName(appenderName);
- return appender;
- }
- }
-
- /**
- * Log files are rotated according to time (one file per day, month or year).
- * See http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy
- */
- private static class TimeRollingPolicy extends RollingPolicy {
- private final String datePattern;
-
- private TimeRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles, String datePattern) {
- super(context, filenamePrefix, logsDir, maxFiles);
- this.datePattern = datePattern;
- }
-
- @Override
- public FileAppender<ILoggingEvent> createAppender(String appenderName) {
- RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
- appender.setContext(context);
- appender.setName(appenderName);
- String filePath = new File(logsDir, filenamePrefix + ".log").getAbsolutePath();
- appender.setFile(filePath);
-
- TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy();
- rollingPolicy.setContext(context);
- rollingPolicy.setFileNamePattern(StringUtils.replace(filePath, filenamePrefix + ".log", filenamePrefix + ".%d{" + datePattern + "}.log"));
- rollingPolicy.setMaxHistory(maxFiles);
- rollingPolicy.setParent(appender);
- rollingPolicy.start();
- appender.setRollingPolicy(rollingPolicy);
-
- return appender;
- }
- }
-
- /**
- * Log files are rotated according to their size.
- * See http://logback.qos.ch/manual/appenders.html#FixedWindowRollingPolicy
- */
- private static class SizeRollingPolicy extends RollingPolicy {
- private final String size;
-
- private SizeRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles, String parameter) {
- super(context, filenamePrefix, logsDir, maxFiles);
- this.size = parameter;
- }
-
- @Override
- public FileAppender<ILoggingEvent> createAppender(String appenderName) {
- RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
- appender.setContext(context);
- appender.setName(appenderName);
- String filePath = new File(logsDir, filenamePrefix + ".log").getAbsolutePath();
- appender.setFile(filePath);
-
- SizeBasedTriggeringPolicy<ILoggingEvent> trigger = new SizeBasedTriggeringPolicy<>(size);
- trigger.setContext(context);
- trigger.start();
- appender.setTriggeringPolicy(trigger);
-
- FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
- rollingPolicy.setContext(context);
- rollingPolicy.setFileNamePattern(StringUtils.replace(filePath, filenamePrefix + ".log", filenamePrefix + ".%i.log"));
- rollingPolicy.setMinIndex(1);
- rollingPolicy.setMaxIndex(maxFiles);
- rollingPolicy.setParent(appender);
- rollingPolicy.start();
- appender.setRollingPolicy(rollingPolicy);
-
- return appender;
- }
- }
-
- public enum LogDomain {
- SQL("sql"),
- ES_CLIENT("es"),
- JMX("jmx");
-
- private final String key;
-
- LogDomain(String key) {
- this.key = key;
- }
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.logging;
+
+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.joran.JoranConfigurator;
+import ch.qos.logback.classic.jul.LevelChangePropagator;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggerContextListener;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.Context;
+import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.joran.spi.JoranException;
+import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
+import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import javax.annotation.CheckForNull;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+import static org.slf4j.Logger.ROOT_LOGGER_NAME;
+
+/**
+ * Helps to configure Logback in a programmatic way, without using XML.
+ */
+public class LogbackHelper {
+
+ private static final String ALL_LOGS_TO_CONSOLE_PROPERTY = "sonar.log.console";
+ private static final String PROCESS_NAME_PLACEHOLDER = "XXXX";
+ private static final String THREAD_ID_PLACEHOLDER = "ZZZZ";
+ private static final String SONAR_LOG_LEVEL_PROPERTY = "sonar.log.level";
+ private static final String SONAR_PROCESS_LOG_LEVEL_PROPERTY = "sonar.log.level." + PROCESS_NAME_PLACEHOLDER;
+ private static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy";
+ private static final String MAX_FILES_PROPERTY = "sonar.log.maxFiles";
+ private static final String LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level " + PROCESS_NAME_PLACEHOLDER + "[" + THREAD_ID_PLACEHOLDER + "][%logger{20}] %msg%n";
+ private static final Level[] ALLOWED_ROOT_LOG_LEVELS = new Level[] {Level.TRACE, Level.DEBUG, Level.INFO};
+
+ public static Collection<Level> allowedLogLevels() {
+ return Arrays.asList(ALLOWED_ROOT_LOG_LEVELS);
+ }
+
+ public LoggerContext getRootContext() {
+ org.slf4j.Logger logger;
+ while (!((logger = LoggerFactory.getLogger(ROOT_LOGGER_NAME)) instanceof Logger)) {
+ // It occurs when the initialization step is still not finished because of a race condition
+ // on ILoggerFactory.getILoggerFactory
+ // http://jira.qos.ch/browse/SLF4J-167
+ // Substitute loggers are used.
+ // http://www.slf4j.org/codes.html#substituteLogger
+ // Bug is not fixed in SLF4J 1.7.14.
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ return ((Logger) logger).getLoggerContext();
+ }
+
+ public LoggerContextListener enableJulChangePropagation(LoggerContext loggerContext) {
+ LevelChangePropagator propagator = new LevelChangePropagator();
+ propagator.setContext(loggerContext);
+ propagator.start();
+ loggerContext.addListener(propagator);
+ return propagator;
+ }
+
+ public static final class RootLoggerConfig {
+ private final ProcessId processId;
+ private final String threadIdFieldPattern;
+
+ private RootLoggerConfig(Builder builder) {
+ this.processId = requireNonNull(builder.processId);
+ this.threadIdFieldPattern = builder.threadIdFieldPattern;
+ }
+
+ public static Builder newRootLoggerConfigBuilder() {
+ return new Builder();
+ }
+
+ ProcessId getProcessId() {
+ return processId;
+ }
+
+ String getThreadIdFieldPattern() {
+ return threadIdFieldPattern;
+ }
+
+ public static final class Builder {
+ @CheckForNull
+ private ProcessId processId;
+ private String threadIdFieldPattern = "";
+
+ private Builder() {
+ // prevents instantiation outside RootLoggerConfig, use static factory method
+ }
+
+ public Builder setProcessId(ProcessId processId) {
+ this.processId = processId;
+ return this;
+ }
+
+ public Builder setThreadIdFieldPattern(String threadIdFieldPattern) {
+ this.threadIdFieldPattern = requireNonNull(threadIdFieldPattern, "threadIdFieldPattern can't be null");
+ return this;
+ }
+
+ public RootLoggerConfig build() {
+ return new RootLoggerConfig(this);
+ }
+ }
+ }
+
+ public String buildLogPattern(LogbackHelper.RootLoggerConfig config) {
+ return LOG_FORMAT
+ .replace(PROCESS_NAME_PLACEHOLDER, config.getProcessId().getKey())
+ .replace(THREAD_ID_PLACEHOLDER, config.getThreadIdFieldPattern());
+ }
+
+ /**
+ * Creates a new {@link ConsoleAppender} to {@code System.out} with the specified name and log pattern.
+ *
+ * @see #buildLogPattern(RootLoggerConfig)
+ */
+ public ConsoleAppender<ILoggingEvent> newConsoleAppender(Context loggerContext, String name, String logPattern) {
+ PatternLayoutEncoder consoleEncoder = new PatternLayoutEncoder();
+ consoleEncoder.setContext(loggerContext);
+ consoleEncoder.setPattern(logPattern);
+ consoleEncoder.start();
+ ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
+ consoleAppender.setContext(loggerContext);
+ consoleAppender.setEncoder(consoleEncoder);
+ consoleAppender.setName(name);
+ consoleAppender.setTarget("System.out");
+ consoleAppender.start();
+ return consoleAppender;
+ }
+
+ /**
+ * Make logback configuration for a process to push all its logs to a log file.
+ * <p>
+ * <ul>
+ * <li>the file's name will use the prefix defined in {@link RootLoggerConfig#getProcessId()#getLogFilenamePrefix()}.</li>
+ * <li>the file will follow the rotation policy defined in property {@link #ROLLING_POLICY_PROPERTY} and
+ * the max number of files defined in property {@link #MAX_FILES_PROPERTY}</li>
+ * <li>the logs will follow the specified log pattern</li>
+ * </ul>
+ * </p>
+ *
+ * @see #buildLogPattern(RootLoggerConfig)
+ */
+ public FileAppender<ILoggingEvent> configureGlobalFileLog(Props props, RootLoggerConfig config, String logPattern) {
+ LoggerContext ctx = getRootContext();
+ Logger rootLogger = ctx.getLogger(ROOT_LOGGER_NAME);
+ FileAppender<ILoggingEvent> fileAppender = newFileAppender(ctx, props, config, logPattern);
+ rootLogger.addAppender(fileAppender);
+ return fileAppender;
+ }
+
+ public FileAppender<ILoggingEvent> newFileAppender(LoggerContext ctx, Props props, LogbackHelper.RootLoggerConfig config, String logPattern) {
+ RollingPolicy rollingPolicy = createRollingPolicy(ctx, props, config.getProcessId().getLogFilenamePrefix());
+ FileAppender<ILoggingEvent> fileAppender = rollingPolicy.createAppender("file_" + config.getProcessId().getLogFilenamePrefix());
+ fileAppender.setContext(ctx);
+ PatternLayoutEncoder fileEncoder = new PatternLayoutEncoder();
+ fileEncoder.setContext(ctx);
+ fileEncoder.setPattern(logPattern);
+ fileEncoder.start();
+ fileAppender.setEncoder(fileEncoder);
+ fileAppender.start();
+ return fileAppender;
+ }
+
+ /**
+ * Make the logback configuration for a sub process to correctly push all its logs to be read by a stream gobbler
+ * on the sub process's System.out.
+ *
+ * @see #buildLogPattern(RootLoggerConfig)
+ */
+ public void configureForSubprocessGobbler(Props props, String logPattern) {
+ if (isAllLogsToConsoleEnabled(props)) {
+ LoggerContext ctx = getRootContext();
+ ctx.getLogger(ROOT_LOGGER_NAME).addAppender(newConsoleAppender(ctx, "root_console", logPattern));
+ }
+ }
+
+ /**
+ * Finds out whether we are in testing environment (usually ITs) and logs of all processes must be forward to
+ * App's System.out. This is specified by the value of property {@link #ALL_LOGS_TO_CONSOLE_PROPERTY}.
+ */
+ public boolean isAllLogsToConsoleEnabled(Props props) {
+ return props.valueAsBoolean(ALL_LOGS_TO_CONSOLE_PROPERTY, false);
+ }
+
+ /**
+ * Configure the log level of the root logger reading the value of property {@link #SONAR_LOG_LEVEL_PROPERTY}.
+ *
+ * @throws IllegalArgumentException if the value of {@link #SONAR_LOG_LEVEL_PROPERTY} is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
+ */
+ public Level configureRootLogLevel(Props props, ProcessId processId) {
+ Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()));
+ getRootContext().getLogger(ROOT_LOGGER_NAME).setLevel(newLevel);
+ return newLevel;
+ }
+
+ /**
+ * Resolve a log level reading the value of specified properties.
+ * <p>
+ * To compute the applied log level the following rules will be followed:
+ * <ul>the last property with a defined and valid value in the order of the {@code propertyKeys} argument will be applied</ul>
+ * <ul>if there is none, {@link Level#INFO INFO} will be returned</ul>
+ * </p>
+ *
+ * @throws IllegalArgumentException if the value of the specified property is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
+ */
+ private static Level resolveLevel(Props props, String... propertyKeys) {
+ Level newLevel = Level.INFO;
+ for (String propertyKey : propertyKeys) {
+ Level level = getPropertyValueAsLevel(props, propertyKey);
+ if (level != null) {
+ newLevel = level;
+ }
+ }
+ return newLevel;
+ }
+
+ @CheckForNull
+ private static Level getPropertyValueAsLevel(Props props, String propertyKey) {
+ String value = props.value(propertyKey);
+ if (value == null) {
+ return null;
+ }
+
+ Level level = Level.toLevel(value, Level.INFO);
+ if (!isAllowed(level)) {
+ throw new IllegalArgumentException(format("log level %s in property %s is not a supported value (allowed levels are %s)",
+ level, propertyKey, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
+ }
+ return level;
+ }
+
+ private static boolean isAllowed(Level level) {
+ for (Level allowedRootLogLevel : ALLOWED_ROOT_LOG_LEVELS) {
+ if (level == allowedRootLogLevel) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Configure the log level of the root logger to the specified level.
+ *
+ * @throws IllegalArgumentException if the specified level is not one of {@link #ALLOWED_ROOT_LOG_LEVELS}
+ */
+ public Level configureRootLogLevel(Level newLevel) {
+ Logger rootLogger = getRootContext().getLogger(ROOT_LOGGER_NAME);
+ ensureSupportedLevel(newLevel);
+ rootLogger.setLevel(newLevel);
+ return newLevel;
+ }
+
+ private static void ensureSupportedLevel(Level newLevel) {
+ if (!isAllowed(newLevel)) {
+ throw new IllegalArgumentException(format("%s log level is not supported (allowed levels are %s)", newLevel, Arrays.toString(ALLOWED_ROOT_LOG_LEVELS)));
+ }
+ }
+
+ public Level configureLoggerLogLevelFromDomain(String loggerName, Props props, ProcessId processId, LogDomain domain) {
+ return configureLoggersLogLevelFromDomain(Collections.singleton(loggerName), props, processId, domain);
+ }
+
+ public Level configureLoggersLogLevelFromDomain(Set<String> loggerNames, Props props, ProcessId processId, LogDomain domain) {
+ String processProperty = SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey());
+ Level newLevel = resolveLevel(props, SONAR_LOG_LEVEL_PROPERTY, processProperty, processProperty + "." + domain.key);
+ loggerNames.forEach(loggerName -> {
+ Logger logger = getRootContext().getLogger(loggerName);
+ logger.setLevel(newLevel);
+ });
+ return newLevel;
+ }
+
+ /**
+ * Configure the log level of the specified logger to specified level.
+ * <p>
+ * Any level is allowed.
+ * </p>
+ */
+ public Logger configureLogger(String loggerName, Level level) {
+ Logger logger = getRootContext().getLogger(loggerName);
+ logger.setLevel(level);
+ return logger;
+ }
+
+ public Level getLoggerLevel(String loggerName) {
+ return getRootContext().getLogger(loggerName).getLevel();
+ }
+
+ /**
+ * Generally used to reset logback in logging tests
+ */
+ public void resetFromXml(String xmlResourcePath) throws JoranException {
+ LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
+ JoranConfigurator configurator = new JoranConfigurator();
+ configurator.setContext(context);
+ context.reset();
+ configurator.doConfigure(LogbackHelper.class.getResource(xmlResourcePath));
+ }
+
+ public RollingPolicy createRollingPolicy(Context ctx, Props props, String filenamePrefix) {
+ String rollingPolicy = props.value(ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd");
+ int maxFiles = props.valueAsInt(MAX_FILES_PROPERTY, 7);
+ File logsDir = props.nonNullValueAsFile(ProcessProperties.PATH_LOGS);
+
+ if (rollingPolicy.startsWith("time:")) {
+ return new TimeRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "time:"));
+
+ } else if (rollingPolicy.startsWith("size:")) {
+ return new SizeRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "size:"));
+
+ } else if ("none".equals(rollingPolicy)) {
+ return new NoRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles);
+
+ } else {
+ throw new MessageException(format("Unsupported value for property %s: %s", ROLLING_POLICY_PROPERTY, rollingPolicy));
+ }
+ }
+
+ public abstract static class RollingPolicy {
+ protected final Context context;
+ final String filenamePrefix;
+ final File logsDir;
+ final int maxFiles;
+
+ RollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles) {
+ this.context = context;
+ this.filenamePrefix = filenamePrefix;
+ this.logsDir = logsDir;
+ this.maxFiles = maxFiles;
+ }
+
+ public abstract FileAppender<ILoggingEvent> createAppender(String appenderName);
+ }
+
+ /**
+ * Log files are not rotated, for example for unix command logrotate is in place.
+ */
+ private static class NoRollingPolicy extends RollingPolicy {
+ private NoRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles) {
+ super(context, filenamePrefix, logsDir, maxFiles);
+ }
+
+ @Override
+ public FileAppender<ILoggingEvent> createAppender(String appenderName) {
+ FileAppender<ILoggingEvent> appender = new FileAppender<>();
+ appender.setContext(context);
+ appender.setFile(new File(logsDir, filenamePrefix + ".log").getAbsolutePath());
+ appender.setName(appenderName);
+ return appender;
+ }
+ }
+
+ /**
+ * Log files are rotated according to time (one file per day, month or year).
+ * See http://logback.qos.ch/manual/appenders.html#TimeBasedRollingPolicy
+ */
+ private static class TimeRollingPolicy extends RollingPolicy {
+ private final String datePattern;
+
+ private TimeRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles, String datePattern) {
+ super(context, filenamePrefix, logsDir, maxFiles);
+ this.datePattern = datePattern;
+ }
+
+ @Override
+ public FileAppender<ILoggingEvent> createAppender(String appenderName) {
+ RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
+ appender.setContext(context);
+ appender.setName(appenderName);
+ String filePath = new File(logsDir, filenamePrefix + ".log").getAbsolutePath();
+ appender.setFile(filePath);
+
+ TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy();
+ rollingPolicy.setContext(context);
+ rollingPolicy.setFileNamePattern(StringUtils.replace(filePath, filenamePrefix + ".log", filenamePrefix + ".%d{" + datePattern + "}.log"));
+ rollingPolicy.setMaxHistory(maxFiles);
+ rollingPolicy.setParent(appender);
+ rollingPolicy.start();
+ appender.setRollingPolicy(rollingPolicy);
+
+ return appender;
+ }
+ }
+
+ /**
+ * Log files are rotated according to their size.
+ * See http://logback.qos.ch/manual/appenders.html#FixedWindowRollingPolicy
+ */
+ private static class SizeRollingPolicy extends RollingPolicy {
+ private final String size;
+
+ private SizeRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles, String parameter) {
+ super(context, filenamePrefix, logsDir, maxFiles);
+ this.size = parameter;
+ }
+
+ @Override
+ public FileAppender<ILoggingEvent> createAppender(String appenderName) {
+ RollingFileAppender<ILoggingEvent> appender = new RollingFileAppender<>();
+ appender.setContext(context);
+ appender.setName(appenderName);
+ String filePath = new File(logsDir, filenamePrefix + ".log").getAbsolutePath();
+ appender.setFile(filePath);
+
+ SizeBasedTriggeringPolicy<ILoggingEvent> trigger = new SizeBasedTriggeringPolicy<>(size);
+ trigger.setContext(context);
+ trigger.start();
+ appender.setTriggeringPolicy(trigger);
+
+ FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
+ rollingPolicy.setContext(context);
+ rollingPolicy.setFileNamePattern(StringUtils.replace(filePath, filenamePrefix + ".log", filenamePrefix + ".%i.log"));
+ rollingPolicy.setMinIndex(1);
+ rollingPolicy.setMaxIndex(maxFiles);
+ rollingPolicy.setParent(appender);
+ rollingPolicy.start();
+ appender.setRollingPolicy(rollingPolicy);
+
+ return appender;
+ }
+ }
+
+ public enum LogDomain {
+ SQL("sql"),
+ ES_CLIENT("es"),
+ JMX("jmx");
+
+ private final String key;
+
+ LogDomain(String key) {
+ this.key = key;
+ }
+ }
+}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2016 SonarSource SA
- * mailto:contact AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.process;
-
-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.LoggerContextListener;
-import ch.qos.logback.core.Appender;
-import ch.qos.logback.core.ConsoleAppender;
-import ch.qos.logback.core.FileAppender;
-import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
-import ch.qos.logback.core.rolling.RollingFileAppender;
-import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
-import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
-import java.io.File;
-import java.util.Properties;
-import org.junit.AfterClass;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-
-public class LogbackHelperTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- private Props props = new Props(new Properties());
- private LogbackHelper underTest = new LogbackHelper();
-
- @Before
- public void setUp() throws Exception {
- File dir = temp.newFolder();
- props.set(ProcessProperties.PATH_LOGS, dir.getAbsolutePath());
- }
-
- @AfterClass
- public static void resetLogback() throws Exception {
- new LogbackHelper().resetFromXml("/logback-test.xml");
- }
-
- @Test
- public void getRootContext() {
- assertThat(underTest.getRootContext()).isNotNull();
- }
-
- @Test
- public void enableJulChangePropagation() {
- LoggerContext ctx = underTest.getRootContext();
- int countListeners = ctx.getCopyOfListenerList().size();
-
- LoggerContextListener propagator = underTest.enableJulChangePropagation(ctx);
- assertThat(ctx.getCopyOfListenerList().size()).isEqualTo(countListeners + 1);
-
- ctx.removeListener(propagator);
- }
-
- @Test
- public void newConsoleAppender() {
- LoggerContext ctx = underTest.getRootContext();
- ConsoleAppender<?> appender = underTest.newConsoleAppender(ctx, "MY_APPENDER", "%msg%n");
-
- assertThat(appender.getName()).isEqualTo("MY_APPENDER");
- assertThat(appender.getContext()).isSameAs(ctx);
- assertThat(appender.isStarted()).isTrue();
- assertThat(((PatternLayoutEncoder) appender.getEncoder()).getPattern()).isEqualTo("%msg%n");
- assertThat(appender.getCopyOfAttachedFiltersList()).isEmpty();
- }
-
- @Test
- public void configureLogger() {
- Logger logger = underTest.configureLogger("my_logger", Level.WARN);
-
- assertThat(logger.getLevel()).isEqualTo(Level.WARN);
- assertThat(logger.getName()).isEqualTo("my_logger");
- }
-
- @Test
- public void createRollingPolicy_defaults() {
- LoggerContext ctx = underTest.getRootContext();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
- FileAppender appender = policy.createAppender("SONAR_FILE");
- assertThat(appender).isInstanceOf(RollingFileAppender.class);
-
- // max 5 daily files
- RollingFileAppender fileAppender = (RollingFileAppender) appender;
- TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) fileAppender.getTriggeringPolicy();
- assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(7);
- assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM-dd}.log");
- }
-
- @Test
- public void createRollingPolicy_none() {
- props.set("sonar.log.rollingPolicy", "none");
- LoggerContext ctx = underTest.getRootContext();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
-
- Appender appender = policy.createAppender("SONAR_FILE");
- assertThat(appender).isNotInstanceOf(RollingFileAppender.class).isInstanceOf(FileAppender.class);
- }
-
- @Test
- public void createRollingPolicy_size() {
- props.set("sonar.log.rollingPolicy", "size:1MB");
- props.set("sonar.log.maxFiles", "20");
- LoggerContext ctx = underTest.getRootContext();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
-
- Appender appender = policy.createAppender("SONAR_FILE");
- assertThat(appender).isInstanceOf(RollingFileAppender.class);
-
- // max 20 files of 1Mb
- RollingFileAppender fileAppender = (RollingFileAppender) appender;
- FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) fileAppender.getRollingPolicy();
- assertThat(rollingPolicy.getMaxIndex()).isEqualTo(20);
- assertThat(rollingPolicy.getFileNamePattern()).endsWith("sonar.%i.log");
- SizeBasedTriggeringPolicy triggeringPolicy = (SizeBasedTriggeringPolicy) fileAppender.getTriggeringPolicy();
- assertThat(triggeringPolicy.getMaxFileSize()).isEqualTo("1MB");
- }
-
- @Test
- public void createRollingPolicy_time() {
- props.set("sonar.log.rollingPolicy", "time:yyyy-MM");
- props.set("sonar.log.maxFiles", "20");
-
- LoggerContext ctx = underTest.getRootContext();
- LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
-
- RollingFileAppender appender = (RollingFileAppender) policy.createAppender("SONAR_FILE");
-
- // max 5 monthly files
- TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) appender.getTriggeringPolicy();
- assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(20);
- assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM}.log");
- }
-
- @Test
- public void createRollingPolicy_fail_if_unknown_policy() {
- props.set("sonar.log.rollingPolicy", "unknown:foo");
- try {
- LoggerContext ctx = underTest.getRootContext();
- underTest.createRollingPolicy(ctx, props, "sonar");
- fail();
- } catch (MessageException e) {
- assertThat(e).hasMessage("Unsupported value for property sonar.log.rollingPolicy: unknown:foo");
- }
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process.logging;
+
+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.LoggerContextListener;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.rolling.FixedWindowRollingPolicy;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+import ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy;
+import ch.qos.logback.core.rolling.TimeBasedRollingPolicy;
+import java.io.File;
+import java.util.Properties;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.MessageException;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class LogbackHelperTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ private Props props = new Props(new Properties());
+ private LogbackHelper underTest = new LogbackHelper();
+
+ @Before
+ public void setUp() throws Exception {
+ File dir = temp.newFolder();
+ props.set(ProcessProperties.PATH_LOGS, dir.getAbsolutePath());
+ }
+
+ @AfterClass
+ public static void resetLogback() throws Exception {
+ new LogbackHelper().resetFromXml("/logback-test.xml");
+ }
+
+ @Test
+ public void getRootContext() {
+ assertThat(underTest.getRootContext()).isNotNull();
+ }
+
+ @Test
+ public void enableJulChangePropagation() {
+ LoggerContext ctx = underTest.getRootContext();
+ int countListeners = ctx.getCopyOfListenerList().size();
+
+ LoggerContextListener propagator = underTest.enableJulChangePropagation(ctx);
+ assertThat(ctx.getCopyOfListenerList().size()).isEqualTo(countListeners + 1);
+
+ ctx.removeListener(propagator);
+ }
+
+ @Test
+ public void newConsoleAppender() {
+ LoggerContext ctx = underTest.getRootContext();
+ ConsoleAppender<?> appender = underTest.newConsoleAppender(ctx, "MY_APPENDER", "%msg%n");
+
+ assertThat(appender.getName()).isEqualTo("MY_APPENDER");
+ assertThat(appender.getContext()).isSameAs(ctx);
+ assertThat(appender.isStarted()).isTrue();
+ assertThat(((PatternLayoutEncoder) appender.getEncoder()).getPattern()).isEqualTo("%msg%n");
+ assertThat(appender.getCopyOfAttachedFiltersList()).isEmpty();
+ }
+
+ @Test
+ public void configureLogger() {
+ Logger logger = underTest.configureLogger("my_logger", Level.WARN);
+
+ assertThat(logger.getLevel()).isEqualTo(Level.WARN);
+ assertThat(logger.getName()).isEqualTo("my_logger");
+ }
+
+ @Test
+ public void createRollingPolicy_defaults() {
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+ FileAppender appender = policy.createAppender("SONAR_FILE");
+ assertThat(appender).isInstanceOf(RollingFileAppender.class);
+
+ // max 5 daily files
+ RollingFileAppender fileAppender = (RollingFileAppender) appender;
+ TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) fileAppender.getTriggeringPolicy();
+ assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(7);
+ assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM-dd}.log");
+ }
+
+ @Test
+ public void createRollingPolicy_none() {
+ props.set("sonar.log.rollingPolicy", "none");
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+
+ Appender appender = policy.createAppender("SONAR_FILE");
+ assertThat(appender).isNotInstanceOf(RollingFileAppender.class).isInstanceOf(FileAppender.class);
+ }
+
+ @Test
+ public void createRollingPolicy_size() {
+ props.set("sonar.log.rollingPolicy", "size:1MB");
+ props.set("sonar.log.maxFiles", "20");
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+
+ Appender appender = policy.createAppender("SONAR_FILE");
+ assertThat(appender).isInstanceOf(RollingFileAppender.class);
+
+ // max 20 files of 1Mb
+ RollingFileAppender fileAppender = (RollingFileAppender) appender;
+ FixedWindowRollingPolicy rollingPolicy = (FixedWindowRollingPolicy) fileAppender.getRollingPolicy();
+ assertThat(rollingPolicy.getMaxIndex()).isEqualTo(20);
+ assertThat(rollingPolicy.getFileNamePattern()).endsWith("sonar.%i.log");
+ SizeBasedTriggeringPolicy triggeringPolicy = (SizeBasedTriggeringPolicy) fileAppender.getTriggeringPolicy();
+ assertThat(triggeringPolicy.getMaxFileSize()).isEqualTo("1MB");
+ }
+
+ @Test
+ public void createRollingPolicy_time() {
+ props.set("sonar.log.rollingPolicy", "time:yyyy-MM");
+ props.set("sonar.log.maxFiles", "20");
+
+ LoggerContext ctx = underTest.getRootContext();
+ LogbackHelper.RollingPolicy policy = underTest.createRollingPolicy(ctx, props, "sonar");
+
+ RollingFileAppender appender = (RollingFileAppender) policy.createAppender("SONAR_FILE");
+
+ // max 5 monthly files
+ TimeBasedRollingPolicy triggeringPolicy = (TimeBasedRollingPolicy) appender.getTriggeringPolicy();
+ assertThat(triggeringPolicy.getMaxHistory()).isEqualTo(20);
+ assertThat(triggeringPolicy.getFileNamePattern()).endsWith("sonar.%d{yyyy-MM}.log");
+ }
+
+ @Test
+ public void createRollingPolicy_fail_if_unknown_policy() {
+ props.set("sonar.log.rollingPolicy", "unknown:foo");
+ try {
+ LoggerContext ctx = underTest.getRootContext();
+ underTest.createRollingPolicy(ctx, props, "sonar");
+ fail();
+ } catch (MessageException e) {
+ assertThat(e).hasMessage("Unsupported value for property sonar.log.rollingPolicy: unknown:foo");
+ }
+ }
+}
package org.sonar.search;
import ch.qos.logback.classic.LoggerContext;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
-import static org.sonar.process.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
+import static org.sonar.process.logging.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
public class SearchLogging {
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
*/
package org.sonar.ce.log;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
import org.sonar.server.app.ServerProcessLogging;
import static org.sonar.ce.log.CeLogging.MDC_CE_TASK_UUID;
-import static org.sonar.process.LogbackHelper.LogDomain;
+import static org.sonar.process.logging.LogbackHelper.LogDomain;
/**
* Configure logback for the Compute Engine process. Logs are written to file "ce.log" in SQ's log directory.
import ch.qos.logback.classic.LoggerContext;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
import org.sonar.server.platform.ServerLogging;
-import static org.sonar.process.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
+import static org.sonar.process.logging.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
public abstract class ServerProcessLogging {
protected static final Set<String> JMX_RMI_LOGGER_NAMES = ImmutableSet.of(
import org.apache.catalina.startup.Tomcat;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.Props;
class TomcatAccessLog {
import java.util.logging.LogManager;
import org.slf4j.bridge.SLF4JBridgeHandler;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
-import static org.sonar.process.LogbackHelper.LogDomain;
+import static org.sonar.process.logging.LogbackHelper.LogDomain;
import static org.sonar.server.platform.web.requestid.RequestIdMDCStorage.HTTP_REQUEST_ID_MDC_KEY;
/**
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.api.utils.log.Loggers;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
@ServerSide
import org.sonar.db.purge.PurgeProfiler;
import org.sonar.db.semaphore.SemaphoresImpl;
import org.sonar.db.version.DatabaseVersion;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.server.app.ProcessCommandWrapperImpl;
import org.sonar.server.app.RestartFlagHolderImpl;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.platform.ServerLogging;
import org.sonar.server.user.UserSession;
-import static org.sonar.process.LogbackHelper.allowedLogLevels;
+import static org.sonar.process.logging.LogbackHelper.allowedLogLevels;
public class ChangeLogLevelAction implements SystemWsAction {
import org.mockito.Mockito;
import org.slf4j.MDC;
import org.sonar.ce.queue.CeTask;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.apache.catalina.Container;
import org.junit.AfterClass;
import org.junit.Test;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.sonar.api.config.Settings;
import org.sonar.api.utils.log.LogTester;
import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import static org.assertj.core.api.Assertions.assertThat;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
-import static org.sonar.process.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
+import static org.sonar.process.logging.LogbackHelper.RootLoggerConfig.newRootLoggerConfigBuilder;
import static org.sonar.process.monitor.StreamGobbler.LOGGER_GOBBLER;
/**
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.sonar.db.profiling.NullConnectionInterceptor;
import org.sonar.db.profiling.ProfiledConnectionInterceptor;
import org.sonar.db.profiling.ProfiledDataSource;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static java.lang.String.format;
import org.sonar.api.config.Settings;
import org.sonar.api.config.MapSettings;
import org.sonar.db.dialect.PostgreSql;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.db.dialect.H2;
-import org.sonar.process.LogbackHelper;
+import org.sonar.process.logging.LogbackHelper;
/**
* This class should be call using @ClassRule in order to create the schema once (ft @Rule is used