aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2015-02-13 14:56:28 +0100
committerSimon Brandhof <simon.brandhof@sonarsource.com>2015-02-19 12:01:20 +0100
commitc063afcb3a908e4205f4f3b605f5137489ae2eba (patch)
tree57192e99495589cb415e451c250124a4a77ce1e5
parent776b67bdfafe3602802e3c76f14b0a5fd3871d12 (diff)
downloadsonarqube-c063afcb3a908e4205f4f3b605f5137489ae2eba.tar.gz
sonarqube-c063afcb3a908e4205f4f3b605f5137489ae2eba.zip
SONAR-6057 Make log rotation more configurable for production
-rw-r--r--pom.xml4
-rw-r--r--server/sonar-process-monitor/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml1
-rw-r--r--server/sonar-process/pom.xml7
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java215
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java61
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java172
-rw-r--r--server/sonar-process/src/test/resources/logback-test.xml13
-rw-r--r--server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml1
-rw-r--r--server/sonar-search/pom.xml7
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java43
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/SearchServer.java3
-rw-r--r--server/sonar-search/src/main/resources/org/sonar/search/logback.xml25
-rw-r--r--server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java49
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/Logging.java109
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/ProgrammaticLogbackValve.java46
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java78
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/TomcatConnectors.java (renamed from server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java)4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/WebLogging.java76
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java2
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml96
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java1
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java44
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java2
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/TomcatAccessLogTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/app/LoggingTest.java)28
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/TomcatConnectorsTest.java (renamed from server/sonar-server/src/test/java/org/sonar/server/app/ConnectorsTest.java)33
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/app/WebLoggingTest.java53
-rw-r--r--server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml32
-rw-r--r--sonar-application/src/main/assembly/conf/sonar.properties26
-rw-r--r--sonar-application/src/main/java/org/sonar/application/App.java8
-rw-r--r--sonar-application/src/main/java/org/sonar/application/AppLogging.java102
-rw-r--r--sonar-application/src/main/resources/org/sonar/application/logback.xml48
-rw-r--r--sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java94
-rw-r--r--sonar-application/src/test/resources/logback-test.xml20
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Logger.java16
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Loggers.java6
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/InMemoryLogger.java88
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/Slf4jLoggers.java54
39 files changed, 1214 insertions, 459 deletions
diff --git a/pom.xml b/pom.xml
index cfd7c40779c..b4b19ca6356 100644
--- a/pom.xml
+++ b/pom.xml
@@ -71,8 +71,8 @@
<sonarSvn.version>1.0-RC1</sonarSvn.version>
<h2.version>1.3.176</h2.version>
<jetty.version>8.1.12.v20130726</jetty.version>
- <logback.version>1.0.13</logback.version>
- <slf4j.version>1.7.5</slf4j.version>
+ <logback.version>1.1.2</logback.version>
+ <slf4j.version>1.7.10</slf4j.version>
<tomcat.version>8.0.18</tomcat.version>
<elasticsearch.version>1.4.2</elasticsearch.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/server/sonar-process-monitor/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml b/server/sonar-process-monitor/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml
deleted file mode 100644
index 298193e01fa..00000000000
--- a/server/sonar-process-monitor/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml
+++ /dev/null
@@ -1 +0,0 @@
-<configuration/>
diff --git a/server/sonar-process/pom.xml b/server/sonar-process/pom.xml
index f4da18c6757..03bbf3d7c81 100644
--- a/server/sonar-process/pom.xml
+++ b/server/sonar-process/pom.xml
@@ -12,16 +12,10 @@
<name>SonarQube :: Process</name>
<dependencies>
-
- <dependency>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
-
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
@@ -34,6 +28,7 @@
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
+
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
diff --git a/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java b/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java
new file mode 100644
index 00000000000..862fc22c7a4
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/LogbackHelper.java
@@ -0,0 +1,215 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.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 org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+/**
+ * Helps to configure Logback in a programmatic way, without using XML.
+ */
+public class LogbackHelper {
+
+ public static final String ROLLING_POLICY_PROPERTY = "sonar.log.rollingPolicy";
+ public static final String MAX_FILES_PROPERTY = "sonar.log.maxFiles";
+
+ public LoggerContext getRootContext() {
+ Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
+ return rootLogger.getLoggerContext();
+ }
+
+ public LoggerContextListener enableJulChangePropagation(LoggerContext loggerContext) {
+ LevelChangePropagator propagator = new LevelChangePropagator();
+ propagator.setContext(loggerContext);
+ propagator.start();
+ loggerContext.addListener(propagator);
+ return propagator;
+ }
+
+ public ConsoleAppender newConsoleAppender(Context loggerContext, String name, String pattern) {
+ PatternLayoutEncoder consoleEncoder = new PatternLayoutEncoder();
+ consoleEncoder.setContext(loggerContext);
+ consoleEncoder.setPattern(pattern);
+ consoleEncoder.start();
+ ConsoleAppender consoleAppender = new ConsoleAppender();
+ consoleAppender.setContext(loggerContext);
+ consoleAppender.setEncoder(consoleEncoder);
+ consoleAppender.setName(name);
+ consoleAppender.setTarget("System.out");
+ consoleAppender.start();
+ return consoleAppender;
+ }
+
+ public Logger configureLogger(LoggerContext loggerContext, String loggerName, Level level) {
+ Logger logger = loggerContext.getLogger(loggerName);
+ logger.setLevel(level);
+ return logger;
+ }
+
+ /**
+ * 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(ProcessConstants.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 (rollingPolicy.equals("none")) {
+ return new NoRollingPolicy(ctx, filenamePrefix, logsDir, maxFiles);
+
+ } else {
+ throw new MessageException(String.format("Unsupported value for property %s: %s", ROLLING_POLICY_PROPERTY, rollingPolicy));
+ }
+ }
+
+ public static abstract class RollingPolicy {
+ protected final Context context;
+ protected final String filenamePrefix;
+ protected final File logsDir;
+ protected final int maxFiles;
+
+ public RollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles) {
+ this.context = context;
+ this.filenamePrefix = filenamePrefix;
+ this.logsDir = logsDir;
+ this.maxFiles = maxFiles;
+ }
+
+ public abstract FileAppender createAppender(String appenderName);
+ }
+
+ /**
+ * Log files are not rotated, for example for unix command logrotate is in place.
+ */
+ static class NoRollingPolicy extends RollingPolicy {
+ NoRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles) {
+ super(context, filenamePrefix, logsDir, maxFiles);
+ }
+
+ @Override
+ public FileAppender createAppender(String appenderName) {
+ FileAppender 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
+ */
+ static class TimeRollingPolicy extends RollingPolicy {
+ private final String datePattern;
+
+ public TimeRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles, String datePattern) {
+ super(context, filenamePrefix, logsDir, maxFiles);
+ this.datePattern = datePattern;
+ }
+
+ @Override
+ public FileAppender createAppender(String appenderName) {
+ RollingFileAppender appender = new RollingFileAppender();
+ appender.setContext(context);
+ appender.setName(appenderName);
+ appender.setFile(new File(logsDir, filenamePrefix + ".log").getAbsolutePath());
+
+ TimeBasedRollingPolicy rollingPolicy = new TimeBasedRollingPolicy();
+ rollingPolicy.setContext(context);
+ rollingPolicy.setFileNamePattern(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
+ */
+ static class SizeRollingPolicy extends RollingPolicy {
+ private final String size;
+
+ SizeRollingPolicy(Context context, String filenamePrefix, File logsDir, int maxFiles, String parameter) {
+ super(context, filenamePrefix, logsDir, maxFiles);
+ this.size = parameter;
+ }
+
+ @Override
+ public FileAppender createAppender(String appenderName) {
+ RollingFileAppender appender = new RollingFileAppender();
+ appender.setContext(context);
+ appender.setName(appenderName);
+ appender.setFile(new File(logsDir, filenamePrefix + ".log").getAbsolutePath());
+
+ SizeBasedTriggeringPolicy trigger = new SizeBasedTriggeringPolicy(size);
+ trigger.setContext(context);
+ trigger.start();
+ appender.setTriggeringPolicy(trigger);
+
+ FixedWindowRollingPolicy rollingPolicy = new FixedWindowRollingPolicy();
+ rollingPolicy.setContext(context);
+ rollingPolicy.setFileNamePattern(filenamePrefix + ".%i.log");
+ rollingPolicy.setMaxIndex(1);
+ rollingPolicy.setMaxIndex(maxFiles);
+ rollingPolicy.setMaxIndex(maxFiles);
+ rollingPolicy.setParent(appender);
+ rollingPolicy.start();
+ appender.setRollingPolicy(rollingPolicy);
+
+ return appender;
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java
deleted file mode 100644
index fe7f647d2e9..00000000000
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.Logger;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.joran.JoranConfigurator;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.core.Appender;
-import ch.qos.logback.core.joran.spi.JoranException;
-import ch.qos.logback.core.util.StatusPrinter;
-import org.slf4j.LoggerFactory;
-
-public class ProcessLogging {
-
- public void configure(Props props, String logbackXmlResource) {
- LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
- try {
- JoranConfigurator configurator = new JoranConfigurator();
- configurator.setContext(context);
- context.reset();
- context.putProperty(ProcessConstants.PATH_LOGS, props.nonNullValue(ProcessConstants.PATH_LOGS));
- doConfigure(configurator, logbackXmlResource);
- } catch (JoranException ignored) {
- // StatusPrinter will handle this
- }
- StatusPrinter.printInCaseOfErrorsOrWarnings(context);
- }
-
- public void addConsoleAppender() {
- Logger consoleLogger = (Logger) LoggerFactory.getLogger("console");
- Appender<ILoggingEvent> consoleAppender = consoleLogger.getAppender("CONSOLE");
-
- Logger gobblerLogger = (Logger) LoggerFactory.getLogger("gobbler");
- gobblerLogger.addAppender(consoleAppender);
- }
-
- /**
- * Extracted only for unit testing
- */
- void doConfigure(JoranConfigurator configurator, String logbackXmlResource) throws JoranException {
- configurator.doConfigure(getClass().getResource(logbackXmlResource));
- }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java b/server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java
new file mode 100644
index 00000000000..d761a2dca76
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/LogbackHelperTest.java
@@ -0,0 +1,172 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.util.Properties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+
+public class LogbackHelperTest {
+
+ Props props = new Props(new Properties());
+ LogbackHelper sut = new LogbackHelper();
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Before
+ public void setUp() throws Exception {
+ File dir = temp.newFolder();
+ props.set(ProcessConstants.PATH_LOGS, dir.getAbsolutePath());
+ }
+
+ @AfterClass
+ public static void resetLogback() throws Exception {
+ new LogbackHelper().resetFromXml("/logback-test.xml");
+ }
+
+ @Test
+ public void getRootContext() {
+ assertThat(sut.getRootContext()).isNotNull();
+ }
+
+ @Test
+ public void enableJulChangePropagation() {
+ LoggerContext ctx = sut.getRootContext();
+ int countListeners = ctx.getCopyOfListenerList().size();
+
+ LoggerContextListener propagator = sut.enableJulChangePropagation(ctx);
+ assertThat(ctx.getCopyOfListenerList().size()).isEqualTo(countListeners + 1);
+
+ ctx.removeListener(propagator);
+ }
+
+ @Test
+ public void newConsoleAppender() throws Exception {
+ LoggerContext ctx = sut.getRootContext();
+ ConsoleAppender appender = sut.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");
+ }
+
+ @Test
+ public void configureLogger() throws Exception {
+ LoggerContext ctx = sut.getRootContext();
+
+ Logger logger = sut.configureLogger(ctx, "my_logger", Level.WARN);
+
+ assertThat(logger.getLevel()).isEqualTo(Level.WARN);
+ assertThat(logger.getName()).isEqualTo("my_logger");
+ }
+
+ @Test
+ public void createRollingPolicy_defaults() throws Exception {
+ LoggerContext ctx = sut.getRootContext();
+ LogbackHelper.RollingPolicy policy = sut.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()).isEqualTo("sonar.%d{yyyy-MM-dd}.log");
+ }
+
+ @Test
+ public void createRollingPolicy_none() throws Exception {
+ props.set("sonar.log.rollingPolicy", "none");
+ LoggerContext ctx = sut.getRootContext();
+ LogbackHelper.RollingPolicy policy = sut.createRollingPolicy(ctx, props, "sonar");
+
+ Appender appender = policy.createAppender("SONAR_FILE");
+ assertThat(appender).isNotInstanceOf(RollingFileAppender.class).isInstanceOf(FileAppender.class);
+ }
+
+ @Test
+ public void createRollingPolicy_size() throws Exception {
+ props.set("sonar.log.rollingPolicy", "size:1MB");
+ props.set("sonar.log.maxFiles", "20");
+ LoggerContext ctx = sut.getRootContext();
+ LogbackHelper.RollingPolicy policy = sut.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()).isEqualTo("sonar.%i.log");
+ SizeBasedTriggeringPolicy triggeringPolicy = (SizeBasedTriggeringPolicy) fileAppender.getTriggeringPolicy();
+ assertThat(triggeringPolicy.getMaxFileSize()).isEqualTo("1MB");
+ }
+
+ @Test
+ public void createRollingPolicy_time() throws Exception {
+ props.set("sonar.log.rollingPolicy", "time:yyyy-MM");
+ props.set("sonar.log.maxFiles", "20");
+
+ LoggerContext ctx = sut.getRootContext();
+ LogbackHelper.RollingPolicy policy = sut.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()).isEqualTo("sonar.%d{yyyy-MM}.log");
+ }
+
+ @Test
+ public void createRollingPolicy_fail_if_unknown_policy() throws Exception {
+ props.set("sonar.log.rollingPolicy", "unknown:foo");
+ try {
+ LoggerContext ctx = sut.getRootContext();
+ sut.createRollingPolicy(ctx, props, "sonar");
+ fail();
+ } catch (MessageException e) {
+ assertThat(e).hasMessage("Unsupported value for property sonar.log.rollingPolicy: unknown:foo");
+ }
+ }
+}
diff --git a/server/sonar-process/src/test/resources/logback-test.xml b/server/sonar-process/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..f2d5a6a466f
--- /dev/null
+++ b/server/sonar-process/src/test/resources/logback-test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<configuration debug="false">
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <root level="debug">
+ <appender-ref ref="STDOUT"/>
+ </root>
+</configuration>
diff --git a/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml b/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml
deleted file mode 100644
index 298193e01fa..00000000000
--- a/server/sonar-process/src/test/resources/org/sonar/process/LoggingTest/logback-access.xml
+++ /dev/null
@@ -1 +0,0 @@
-<configuration/>
diff --git a/server/sonar-search/pom.xml b/server/sonar-search/pom.xml
index 26d3b355560..9f836ef23ba 100644
--- a/server/sonar-search/pom.xml
+++ b/server/sonar-search/pom.xml
@@ -37,7 +37,12 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
- <scope>runtime</scope>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>ch.qos.logback</groupId>
+ <artifactId>logback-core</artifactId>
+ <scope>provided</scope>
</dependency>
<!-- testing -->
diff --git a/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java b/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java
new file mode 100644
index 00000000000..2c2d45ad39b
--- /dev/null
+++ b/server/sonar-search/src/main/java/org/sonar/search/SearchLogging.java
@@ -0,0 +1,43 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.search;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.ConsoleAppender;
+import org.sonar.process.LogbackHelper;
+
+public class SearchLogging {
+
+ private static final String LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level es[%logger{20}] %X %msg%n";
+
+ private LogbackHelper helper = new LogbackHelper();
+
+ public LoggerContext configure() {
+ LoggerContext ctx = helper.getRootContext();
+ ctx.reset();
+
+ ConsoleAppender consoleAppender = helper.newConsoleAppender(ctx, "CONSOLE", LOG_FORMAT);
+ Logger rootLogger = helper.configureLogger(ctx, Logger.ROOT_LOGGER_NAME, Level.INFO);
+ rootLogger.addAppender(consoleAppender);
+ return ctx;
+ }
+}
diff --git a/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java b/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java
index 7689b91f2ef..f5b601ce21c 100644
--- a/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java
+++ b/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java
@@ -26,7 +26,6 @@ import org.slf4j.LoggerFactory;
import org.sonar.process.MinimumViableSystem;
import org.sonar.process.Monitored;
import org.sonar.process.ProcessEntryPoint;
-import org.sonar.process.ProcessLogging;
import org.sonar.process.Props;
public class SearchServer implements Monitored {
@@ -76,7 +75,7 @@ public class SearchServer implements Monitored {
public static void main(String... args) {
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args);
- new ProcessLogging().configure(entryPoint.getProps(), "/org/sonar/search/logback.xml");
+ new SearchLogging().configure();
SearchServer searchServer = new SearchServer(entryPoint.getProps());
entryPoint.launch(searchServer);
}
diff --git a/server/sonar-search/src/main/resources/org/sonar/search/logback.xml b/server/sonar-search/src/main/resources/org/sonar/search/logback.xml
deleted file mode 100644
index 3c24d95cdc0..00000000000
--- a/server/sonar-search/src/main/resources/org/sonar/search/logback.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-
-<!--
- Configuration for default logger. Only used while embedded server is starting,
- before proper logging configuration is loaded.
-
- See http://logback.qos.ch/manual/configuration.html
--->
-<configuration debug="false">
- <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
-
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>
- %d{yyyy.MM.dd HH:mm:ss} %-5level sea[%logger{20}] %X %msg%n
- </pattern>
- </encoder>
- </appender>
-
- <root>
- <level value="INFO"/>
- <appender-ref ref="CONSOLE"/>
- </root>
-
-</configuration>
diff --git a/server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java b/server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java
new file mode 100644
index 00000000000..f85772f7340
--- /dev/null
+++ b/server/sonar-search/src/test/java/org/sonar/search/SearchLoggingTest.java
@@ -0,0 +1,49 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.search;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.ConsoleAppender;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.sonar.process.LogbackHelper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SearchLoggingTest {
+
+ SearchLogging sut = new SearchLogging();
+
+ @AfterClass
+ public static void resetLogback() throws Exception {
+ new LogbackHelper().resetFromXml("/logback-test.xml");
+ }
+
+ @Test
+ public void log_to_console() throws Exception {
+ LoggerContext ctx = sut.configure();
+
+ Logger root = ctx.getLogger(Logger.ROOT_LOGGER_NAME);
+ Appender appender = root.getAppender("CONSOLE");
+ assertThat(appender).isInstanceOf(ConsoleAppender.class);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
index ea651f54e01..89b3a09377a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java
@@ -55,8 +55,8 @@ class EmbeddedTomcat {
tomcat.getHost().setAutoDeploy(false);
tomcat.getHost().setCreateDirs(false);
tomcat.getHost().setDeployOnStartup(true);
- Logging.configure(tomcat, props);
- Connectors.configure(tomcat, props);
+ new TomcatAccessLog().configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
webappContext = Webapp.configure(tomcat, props);
try {
tomcat.start();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Logging.java b/server/sonar-server/src/main/java/org/sonar/server/app/Logging.java
deleted file mode 100644
index d83e49501bb..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/app/Logging.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.server.app;
-
-import ch.qos.logback.access.tomcat.LogbackValve;
-import com.google.common.collect.ImmutableMap;
-import org.apache.catalina.LifecycleEvent;
-import org.apache.catalina.LifecycleListener;
-import org.apache.catalina.startup.Tomcat;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.slf4j.bridge.SLF4JBridgeHandler;
-import org.sonar.core.config.Logback;
-import org.sonar.core.profiling.Profiling;
-import org.sonar.process.ProcessConstants;
-import org.sonar.process.Props;
-
-import java.io.File;
-import java.util.Map;
-import java.util.logging.LogManager;
-
-class Logging {
-
- private static final String LOG_COMMON_PREFIX = "%d{yyyy.MM.dd HH:mm:ss} %-5level ";
- private static final String LOG_COMMON_SUFFIX = "%msg%n";
-
- private static final String LOG_LOGFILE_SPECIFIC_PART = "web[%logger{20}] %X ";
- private static final String LOG_FULL_SPECIFIC_PART = "%thread ";
-
- private static final String LOGFILE_STANDARD_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_LOGFILE_SPECIFIC_PART + LOG_COMMON_SUFFIX;
- private static final String LOGFILE_FULL_LOGGING_FORMAT = LOG_COMMON_PREFIX + LOG_FULL_SPECIFIC_PART + LOG_LOGFILE_SPECIFIC_PART + LOG_COMMON_SUFFIX;
-
- static final String ACCESS_RELATIVE_PATH = "WEB-INF/config/logback-access.xml";
- static final String PROPERTY_ENABLE_ACCESS_LOGS = "sonar.web.accessLogs.enable";
-
- private Logging() {
- }
-
- static void init(Props props) {
- configureLogback(props);
-
- // Configure java.util.logging, used by Tomcat, in order to forward to slf4j
- LogManager.getLogManager().reset();
- SLF4JBridgeHandler.install();
- }
-
- /**
- * Configure Logback from classpath, with configuration from sonar.properties
- */
- private static void configureLogback(Props props) {
- String configProfilingLevel = props.value(Profiling.CONFIG_PROFILING_LEVEL, "NONE");
- Profiling.Level profilingLevel = Profiling.Level.fromConfigString(configProfilingLevel);
- Map<String, String> variables = ImmutableMap.of(
- ProcessConstants.PATH_LOGS, props.nonNullValue(ProcessConstants.PATH_LOGS),
- "LOGGING_FORMAT", profilingLevel == Profiling.Level.FULL ? LOGFILE_FULL_LOGGING_FORMAT : LOGFILE_STANDARD_LOGGING_FORMAT);
- Logback.configure("/org/sonar/server/platform/logback.xml", variables);
- }
-
- static void configure(Tomcat tomcat, Props props) {
- tomcat.setSilent(true);
- tomcat.getService().addLifecycleListener(new LifecycleLogger(LoggerFactory.getLogger(Logging.class)));
- configureLogbackAccess(tomcat, props);
- }
-
- private static void configureLogbackAccess(Tomcat tomcat, Props props) {
- if (props.valueAsBoolean(PROPERTY_ENABLE_ACCESS_LOGS, true)) {
- LogbackValve valve = new LogbackValve();
- valve.setQuiet(true);
- valve.setFilename(new File(props.nonNullValue(ProcessConstants.PATH_WEB), ACCESS_RELATIVE_PATH).getAbsolutePath());
- tomcat.getHost().getPipeline().addValve(valve);
- }
- }
-
- static class LifecycleLogger implements LifecycleListener {
- private Logger logger;
-
- LifecycleLogger(Logger logger) {
- this.logger = logger;
- }
-
- @Override
- public void lifecycleEvent(LifecycleEvent event) {
- if ("after_start".equals(event.getType())) {
- logger.info("Web server is started");
-
- } else if ("after_destroy".equals(event.getType())) {
- logger.info("Web server is stopped");
- }
- }
- }
-
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/ProgrammaticLogbackValve.java b/server/sonar-server/src/main/java/org/sonar/server/app/ProgrammaticLogbackValve.java
new file mode 100644
index 00000000000..63d18c937ab
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/ProgrammaticLogbackValve.java
@@ -0,0 +1,46 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.app;
+
+import ch.qos.logback.access.tomcat.LogbackValve;
+import ch.qos.logback.core.util.ExecutorServiceUtil;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleState;
+import org.apache.commons.lang.reflect.FieldUtils;
+
+/**
+ * Implementation of {@link ch.qos.logback.access.tomcat.LogbackValve} that does not
+ * rely on the required file logback-access.xml. It allows to be configured
+ * programmatically.
+ */
+public class ProgrammaticLogbackValve extends LogbackValve {
+
+ @Override
+ public void startInternal() throws LifecycleException {
+ try {
+ // direct coupling with LogbackValve implementation
+ FieldUtils.writeField(this, "executorService", ExecutorServiceUtil.newExecutorService(), true);
+ FieldUtils.writeField(this, "started", true, true);
+ setState(LifecycleState.STARTING);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java b/server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java
index d2e7f4e736a..7fcf0295552 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/StartupLogs.java
@@ -65,7 +65,7 @@ class StartupLogs {
AbstractHttp11JsseProtocol protocol = (AbstractHttp11JsseProtocol) connector.getProtocolHandler();
sb.append(" | ciphers=");
- if (props.contains(Connectors.PROP_HTTPS_CIPHERS)) {
+ if (props.contains(TomcatConnectors.PROP_HTTPS_CIPHERS)) {
sb.append(StringUtils.join(protocol.getCiphersUsed(), ","));
} else {
sb.append("JVM defaults");
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java
new file mode 100644
index 00000000000..69b80a51d69
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatAccessLog.java
@@ -0,0 +1,78 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.app;
+
+import ch.qos.logback.access.PatternLayoutEncoder;
+import ch.qos.logback.core.FileAppender;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.startup.Tomcat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.LogbackHelper;
+import org.sonar.process.Props;
+
+class TomcatAccessLog {
+
+ public static final String PROPERTY_ENABLE = "sonar.web.accessLogs.enable";
+ public static final String PROPERTY_PATTERN = "sonar.web.accessLogs.pattern";
+
+ void configure(Tomcat tomcat, Props props) {
+ tomcat.setSilent(true);
+ tomcat.getService().addLifecycleListener(new LifecycleLogger(LoggerFactory.getLogger(TomcatAccessLog.class)));
+ configureLogbackAccess(tomcat, props);
+ }
+
+ private void configureLogbackAccess(Tomcat tomcat, Props props) {
+ if (props.valueAsBoolean(PROPERTY_ENABLE, true)) {
+ ProgrammaticLogbackValve valve = new ProgrammaticLogbackValve();
+ LogbackHelper helper = new LogbackHelper();
+ LogbackHelper.RollingPolicy policy = helper.createRollingPolicy(valve, props, "access");
+ FileAppender appender = policy.createAppender("ACCESS_LOG");
+ PatternLayoutEncoder fileEncoder = new PatternLayoutEncoder();
+ fileEncoder.setContext(valve);
+ fileEncoder.setPattern(props.value(PROPERTY_PATTERN, "combined"));
+ fileEncoder.start();
+ appender.setEncoder(fileEncoder);
+ appender.start();
+ valve.addAppender(appender);
+ tomcat.getHost().getPipeline().addValve(valve);
+ }
+ }
+
+ static class LifecycleLogger implements LifecycleListener {
+ private Logger logger;
+
+ LifecycleLogger(Logger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public void lifecycleEvent(LifecycleEvent event) {
+ if ("after_start".equals(event.getType())) {
+ logger.info("Web server is started");
+
+ } else if ("after_destroy".equals(event.getType())) {
+ logger.info("Web server is stopped");
+ }
+ }
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatConnectors.java
index 1518117eafe..61f4718bfdc 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/Connectors.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatConnectors.java
@@ -35,7 +35,7 @@ import java.util.Set;
/**
* Configuration of Tomcat connectors
*/
-class Connectors {
+class TomcatConnectors {
public static final String PROP_HTTPS_CIPHERS = "sonar.web.https.ciphers";
@@ -43,7 +43,7 @@ class Connectors {
public static final String HTTP_PROTOCOL = "HTTP/1.1";
public static final String AJP_PROTOCOL = "AJP/1.3";
- private Connectors() {
+ private TomcatConnectors() {
// only static stuff
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebLogging.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebLogging.java
new file mode 100644
index 00000000000..fa1d251f5c9
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebLogging.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.app;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.ConsoleAppender;
+import org.slf4j.bridge.SLF4JBridgeHandler;
+import org.sonar.process.LogbackHelper;
+
+import java.util.logging.LogManager;
+
+/**
+ * Configure logback for web server process. Logs must be written to console, which is
+ * forwarded to file logs/sonar.log by the app master process.
+ */
+class WebLogging {
+
+ private static final String LOG_FORMAT = "%d{yyyy.MM.dd HH:mm:ss} %-5level web[%logger{20}] %X %msg%n";
+
+ private final LogbackHelper helper = new LogbackHelper();
+
+ LoggerContext configure() {
+ LoggerContext ctx = helper.getRootContext();
+ ctx.reset();
+
+ helper.enableJulChangePropagation(ctx);
+ configureAppender(ctx);
+ configureLevels(ctx);
+
+ // Configure java.util.logging, used by Tomcat, in order to forward to slf4j
+ LogManager.getLogManager().reset();
+ SLF4JBridgeHandler.install();
+ return ctx;
+ }
+
+ private void configureAppender(LoggerContext ctx) {
+ ConsoleAppender consoleAppender = helper.newConsoleAppender(ctx, "CONSOLE", LOG_FORMAT);
+ Logger rootLogger = helper.configureLogger(ctx, Logger.ROOT_LOGGER_NAME, Level.INFO);
+ rootLogger.addAppender(consoleAppender);
+ }
+
+ private void configureLevels(LoggerContext ctx) {
+ // override level of some loggers
+ helper.configureLogger(ctx, "rails", Level.WARN);
+ helper.configureLogger(ctx, "org.hibernate.cache.ReadWriteCache", Level.ERROR);
+ helper.configureLogger(ctx, "org.hibernate", Level.WARN);
+ helper.configureLogger(ctx, "org.hibernate.SQL", Level.WARN);
+ helper.configureLogger(ctx, "org.apache.ibatis", Level.WARN);
+ helper.configureLogger(ctx, "java.sql", Level.WARN);
+ helper.configureLogger(ctx, "java.sql.ResultSet", Level.WARN);
+ helper.configureLogger(ctx, "org.sonar.MEASURE_FILTER", Level.WARN);
+ helper.configureLogger(ctx, "org.elasticsearch", Level.INFO);
+ helper.configureLogger(ctx, "org.elasticsearch.node", Level.INFO);
+ helper.configureLogger(ctx, "org.elasticsearch.http", Level.INFO);
+ helper.configureLogger(ctx, "ch.qos.logback", Level.WARN);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
index 24919d9d6e2..724a5cdc5ea 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
@@ -60,7 +60,7 @@ public class WebServer implements Monitored {
*/
public static void main(String[] args) throws Exception {
ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args);
- Logging.init(entryPoint.getProps());
+ new WebLogging().configure();
WebServer server = new WebServer(entryPoint.getProps());
entryPoint.launch(server);
}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml b/server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml
deleted file mode 100644
index 57b87e84938..00000000000
--- a/server/sonar-server/src/main/resources/org/sonar/server/platform/logback.xml
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-
-<!--
- Logging configuration.
- By default logs are generated in logs/sonar.log.
-
- See http://logback.qos.ch/manual/configuration.html
--->
-<configuration debug="false">
- <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
-
- <!-- do not change appender name. See org.sonar.server.platform.SwitchLogbackAppender -->
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>
- ${LOGGING_FORMAT}
- </pattern>
- </encoder>
- </appender>
-
- <!-- do not change appender name. See org.sonar.server.platform.SwitchLogbackAppender -->
- <appender name="ANALYSIS_REPORTS" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <File>${sonar.path.logs}/analysis_reports.log</File>
- <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
- <param name="FileNamePattern" value="${sonar.path.logs}/analysis_reports.%i.log"/>
- <param name="MinIndex" value="1"/>
- <param name="MaxIndex" value="5"/>
- </rollingPolicy>
- <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
- <param name="MaxFileSize" value="10MB"/>
- </triggeringPolicy>
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>%d{yyyy.MM.dd HH:mm:ss} %-5level [%logger{20}] %msg%n</pattern>
- </encoder>
- </appender>
-
- <appender name="SWITCH" class="org.sonar.server.platform.SwitchLogbackAppender">
- <!-- do not change appender names. See org.sonar.server.platform.SwitchLogbackAppender -->
- <appender-ref ref="CONSOLE" />
- <appender-ref ref="ANALYSIS_REPORTS" />
- </appender>
-
- <!-- Display Rails warnings and errors -->
- <logger name="rails">
- <level value="WARN"/>
- </logger>
-
- <logger name="org.hibernate.cache.ReadWriteCache">
- <!-- removing "An item was expired by the cache while it was locked (increase your cache timeout)" msg -->
- <level value="ERROR"/>
- </logger>
- <logger name="org.hibernate">
- <level value="WARN"/>
- </logger>
-
- <!-- Display SQL requests and results by setting the following loggers to level DEBUG -->
- <logger name="org.hibernate.SQL">
- <level value="WARN"/>
- </logger>
- <logger name="org.apache.ibatis">
- <level value="WARN"/>
- </logger>
- <logger name="java.sql">
- <level value="WARN"/>
- </logger>
- <logger name="java.sql.ResultSet">
- <level value="WARN"/>
- </logger>
-
- <!-- Execution of measure filters -->
- <logger name="org.sonar.MEASURE_FILTER">
- <level value="WARN"/>
- </logger>
-
- <!-- Turn off ElasticSearch logging -->
- <logger name="org.elasticsearch">
- <level value="INFO"/>
- </logger>
- <logger name="org.elasticsearch.node">
- <level value="INFO"/>
- </logger>
- <logger name="org.elasticsearch.http">
- <level value="INFO"/>
- </logger>
-
- <!-- LOG level for logback itself -->
- <logger name="ch.qos.logback">
- <level value="WARN"/>
- </logger>
-
- <root>
- <level value="INFO"/>
- <appender-ref ref="SWITCH"/>
- </root>
-
-</configuration>
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
index 3dd916816e6..71c2fcc6f74 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/EmbeddedTomcatTest.java
@@ -50,6 +50,7 @@ public class EmbeddedTomcatTest {
FileUtils.write(new File(home, "web/WEB-INF/web.xml"), "<web-app/>");
props.set("sonar.path.home", home.getAbsolutePath());
props.set("sonar.path.web", webDir.getAbsolutePath());
+ props.set("sonar.path.logs", temp.newFolder().getAbsolutePath());
// start server on a random port
int httpPort = NetworkUtils.freePort();
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java
new file mode 100644
index 00000000000..ee436f7f9f3
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/ProgrammaticLogbackValveTest.java
@@ -0,0 +1,44 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.app;
+
+import ch.qos.logback.core.ConsoleAppender;
+import org.apache.catalina.Container;
+import org.junit.Test;
+import org.sonar.process.LogbackHelper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+public class ProgrammaticLogbackValveTest {
+
+ @Test
+ public void startInternal() throws Exception {
+ ProgrammaticLogbackValve valve = new ProgrammaticLogbackValve();
+ valve.setContainer(mock(Container.class));
+ LogbackHelper helper = new LogbackHelper();
+ ConsoleAppender appender = helper.newConsoleAppender(valve, "CONSOLE", "combined");
+ valve.addAppender(appender);
+ valve.start();
+ assertThat(valve.isStarted()).isTrue();
+ valve.stop();
+ assertThat(valve.isStarted()).isFalse();
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java
index ea80502c785..b4906a5ca14 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/StartupLogsTest.java
@@ -81,7 +81,7 @@ public class StartupLogsTest {
when(protocol.getCiphersUsed()).thenReturn(new String[] {"SSL_RSA", "TLS_RSA_WITH_RC4"});
when(connector.getProtocolHandler()).thenReturn(protocol);
when(tomcat.getService().findConnectors()).thenReturn(new Connector[] {connector});
- props.set(Connectors.PROP_HTTPS_CIPHERS, "SSL_RSA,TLS_RSA_WITH_RC4");
+ props.set(TomcatConnectors.PROP_HTTPS_CIPHERS, "SSL_RSA,TLS_RSA_WITH_RC4");
sut.log(tomcat);
verify(logger).info("HTTPS connector enabled on port 1234 | ciphers=SSL_RSA,TLS_RSA_WITH_RC4");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/LoggingTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/TomcatAccessLogTest.java
index da9ee50d4af..7e081214eb3 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/app/LoggingTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/TomcatAccessLogTest.java
@@ -19,16 +19,13 @@
*/
package org.sonar.server.app;
-import ch.qos.logback.access.tomcat.LogbackValve;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
-import org.apache.catalina.Valve;
import org.apache.catalina.startup.Tomcat;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.sonar.process.ProcessConstants;
@@ -38,12 +35,12 @@ import java.io.File;
import java.io.IOException;
import java.util.Properties;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
-public class LoggingTest {
+public class TomcatAccessLogTest {
+
+ TomcatAccessLog sut = new TomcatAccessLog();
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -58,23 +55,16 @@ public class LoggingTest {
public void enable_access_logs_by_Default() throws Exception {
Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
Props props = new Props(new Properties());
- props.set(ProcessConstants.PATH_WEB, temp.newFolder().getAbsolutePath());
- Logging.configure(tomcat, props);
+ props.set(ProcessConstants.PATH_LOGS, temp.newFolder().getAbsolutePath());
+ sut.configure(tomcat, props);
- verify(tomcat.getHost().getPipeline()).addValve(argThat(new ArgumentMatcher<Valve>() {
- @Override
- public boolean matches(Object o) {
- LogbackValve v = (LogbackValve) o;
- String confFile = v.getFilename();
- return confFile.endsWith("logback-access.xml");
- }
- }));
+ verify(tomcat.getHost().getPipeline()).addValve(any(ProgrammaticLogbackValve.class));
}
@Test
public void log_when_started_and_stopped() {
Logger logger = mock(Logger.class);
- Logging.LifecycleLogger listener = new Logging.LifecycleLogger(logger);
+ TomcatAccessLog.LifecycleLogger listener = new TomcatAccessLog.LifecycleLogger(logger);
LifecycleEvent event = new LifecycleEvent(mock(Lifecycle.class), "before_init", null);
listener.lifecycleEvent(event);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/ConnectorsTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/TomcatConnectorsTest.java
index 70ba503943b..6c4c7d49c80 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/app/ConnectorsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/TomcatConnectorsTest.java
@@ -34,14 +34,11 @@ import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
-public class ConnectorsTest {
+public class TomcatConnectorsTest {
Tomcat tomcat = mock(Tomcat.class, Mockito.RETURNS_DEEP_STUBS);
@@ -55,7 +52,7 @@ public class ConnectorsTest {
p.setProperty("sonar.web.http.acceptCount", "20");
Props props = new Props(p);
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
verify(tomcat).setConnector(argThat(new PropertiesMatcher(
ImmutableMap.<String, Object>of("minSpareThreads", 2, "maxThreads", 30, "acceptCount", 20)
@@ -66,7 +63,7 @@ public class ConnectorsTest {
public void configure_default_thread_pool() throws Exception {
Props props = new Props(new Properties());
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
verify(tomcat).setConnector(argThat(new PropertiesMatcher(
ImmutableMap.<String, Object>of("minSpareThreads", 5, "maxThreads", 50, "acceptCount", 25)
@@ -82,7 +79,7 @@ public class ConnectorsTest {
p.setProperty("sonar.web.https.minThreads", "5");
Props props = new Props(p);
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
@@ -108,7 +105,7 @@ public class ConnectorsTest {
Props props = new Props(p);
try {
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage()).isEqualTo("HTTP connectors are disabled");
@@ -122,7 +119,7 @@ public class ConnectorsTest {
p.setProperty("sonar.web.https.port", "9443");
Props props = new Props(p);
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
verify(tomcat).setConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
@@ -142,27 +139,27 @@ public class ConnectorsTest {
p.setProperty("sonar.web.https.port", "9443");
Props props = new Props(p);
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
public boolean matches(Object o) {
Connector c = (Connector) o;
- return c.getScheme().equals("http") && c.getPort() == 9000 && c.getProtocol().equals(Connectors.HTTP_PROTOCOL);
+ return c.getScheme().equals("http") && c.getPort() == 9000 && c.getProtocol().equals(TomcatConnectors.HTTP_PROTOCOL);
}
}));
verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
public boolean matches(Object o) {
Connector c = (Connector) o;
- return c.getScheme().equals("http") && c.getPort() == 9009 && c.getProtocol().equals(Connectors.AJP_PROTOCOL);
+ return c.getScheme().equals("http") && c.getPort() == 9009 && c.getProtocol().equals(TomcatConnectors.AJP_PROTOCOL);
}
}));
verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
public boolean matches(Object o) {
Connector c = (Connector) o;
- return c.getScheme().equals("https") && c.getPort() == 9443 && c.getProtocol().equals(Connectors.HTTP_PROTOCOL);
+ return c.getScheme().equals("https") && c.getPort() == 9443 && c.getProtocol().equals(TomcatConnectors.HTTP_PROTOCOL);
}
}));
}
@@ -175,7 +172,7 @@ public class ConnectorsTest {
p.setProperty("sonar.web.https.port", "9000");
try {
- Connectors.configure(tomcat, new Props(p));
+ TomcatConnectors.configure(tomcat, new Props(p));
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("HTTP, AJP and HTTPS must not use the same port 9000");
@@ -189,7 +186,7 @@ public class ConnectorsTest {
p.setProperty("sonar.ajp.port", "9009");
p.setProperty("sonar.web.https.port", "9443");
- Connectors.configure(tomcat, new Props(p));
+ TomcatConnectors.configure(tomcat, new Props(p));
verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
@@ -221,7 +218,7 @@ public class ConnectorsTest {
p.setProperty("sonar.web.https.port", "9443");
p.setProperty("sonar.web.host", "1.2.3.4");
- Connectors.configure(tomcat, new Props(p));
+ TomcatConnectors.configure(tomcat, new Props(p));
verify(tomcat.getService()).addConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
@@ -250,7 +247,7 @@ public class ConnectorsTest {
Props props = new Props(p);
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
verify(tomcat).setConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
@@ -272,7 +269,7 @@ public class ConnectorsTest {
Props props = new Props(p);
- Connectors.configure(tomcat, props);
+ TomcatConnectors.configure(tomcat, props);
verify(tomcat).setConnector(argThat(new ArgumentMatcher<Connector>() {
@Override
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/WebLoggingTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/WebLoggingTest.java
new file mode 100644
index 00000000000..467b58be54d
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/WebLoggingTest.java
@@ -0,0 +1,53 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.server.app;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.ConsoleAppender;
+import org.junit.AfterClass;
+import org.junit.Test;
+import org.sonar.process.LogbackHelper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WebLoggingTest {
+
+ WebLogging sut = new WebLogging();
+
+ @AfterClass
+ public static void resetLogback() throws Exception {
+ new LogbackHelper().resetFromXml("/logback-test.xml");
+ }
+
+ @Test
+ public void log_to_console() throws Exception {
+ LoggerContext ctx = sut.configure();
+
+ Logger root = ctx.getLogger(Logger.ROOT_LOGGER_NAME);
+ Appender appender = root.getAppender("CONSOLE");
+ assertThat(appender).isInstanceOf(ConsoleAppender.class);
+
+ // change level of some loggers
+ assertThat(ctx.getLogger("java.sql").getLevel()).isEqualTo(Level.WARN);
+ }
+}
diff --git a/server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml b/server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml
deleted file mode 100644
index 504f5fc6797..00000000000
--- a/server/sonar-web/src/main/webapp/WEB-INF/config/logback-access.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-
-<!--
-
- Log HTTP requests in logs/access.log. For internal use only.
-
- See http://logback.qos.ch/access.html#configuration and Tomcat configuration in sonar-application.
-
--->
-
-<configuration debug="false">
-
- <!-- Required for performance reason. See http://www.slf4j.org/legacy.html#jul-to-slf4j -->
- <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
-
- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <file>${sonar.path.logs}/access.log</file>
- <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
- <param name="FileNamePattern" value="${sonar.path.logs}/access.%i.log"/>
- <param name="MinIndex" value="1"/>
- <param name="MaxIndex" value="3"/>
- </rollingPolicy>
- <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
- <param name="MaxFileSize" value="5MB"/>
- </triggeringPolicy>
- <encoder>
- <pattern>combined</pattern>
- </encoder>
- </appender>
-
- <appender-ref ref="FILE" />
-</configuration>
diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties
index 53fb2cfb207..c2a1b29cf57 100644
--- a/sonar-application/src/main/assembly/conf/sonar.properties
+++ b/sonar-application/src/main/assembly/conf/sonar.properties
@@ -189,11 +189,6 @@
#sonar.web.http.acceptCount=25
#sonar.web.https.acceptCount=25
-# Access logs are generated in the file logs/access.log. This file is rolled over when it's 5Mb.
-# An archive of 3 files is kept in the same directory.
-# Access logs are enabled by default.
-#sonar.web.accessLogs.enable=true
-
# TCP port for incoming AJP connections. Disabled if value is -1. Disabled by default.
#sonar.ajp.port=-1
@@ -258,6 +253,27 @@
# Default is <installation home>/logs
#sonar.path.logs=logs
+# Rolling policy of log files
+# - based on time if value starts with "time:", for example by day ("time:yyyy-MM-dd")
+# or by month ("time:yyyy-MM")
+# - based on size if value starts with "size:", for example "size:10MB"
+# - disabled if value is "none". That needs logs to be managed by an external system like logrotate.
+#sonar.log.rollingPolicy=time:yyyy-MM-dd
+
+# Maximum number of files to keep if a rolling policy is enabled
+#sonar.log.maxFiles=7
+
+# Access log is the list of all the HTTP requests received by server. If enabled, it is stored
+# in the file {sonar.path.logs}/access.log. This file follows the same rolling policy as for
+# sonar.log (see sonar.log.rollingPolicy and sonar.log.maxFiles).
+#sonar.web.accessLogs.enable=true
+
+# Format of access log. It is ignored if sonar.web.accessLogs.enable=false. Value is:
+# - "common" is the Common Log Format (shortcut for: %h %l %u %user %date "%r" %s %b)
+# - "combined" is another format widely recognized (shortcut for: %h %l %u [%t] "%r" %s %b "%i{Referer}" "%i{User-Agent}")
+# - else a custom pattern. See http://logback.qos.ch/manual/layouts.html#AccessPatternLayout
+#sonar.web.accessLogs.pattern=combined
+
#--------------------------------------------------------------------------------------------------
# OTHERS
diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java
index af325f1d370..e70c34df4fe 100644
--- a/sonar-application/src/main/java/org/sonar/application/App.java
+++ b/sonar-application/src/main/java/org/sonar/application/App.java
@@ -24,7 +24,6 @@ import org.apache.commons.lang.StringUtils;
import org.sonar.process.MinimumViableSystem;
import org.sonar.process.ProcessCommands;
import org.sonar.process.ProcessConstants;
-import org.sonar.process.ProcessLogging;
import org.sonar.process.Props;
import org.sonar.process.StopWatcher;
import org.sonar.process.Stoppable;
@@ -114,11 +113,8 @@ public class App implements Stoppable {
CommandLineParser cli = new CommandLineParser();
Properties rawProperties = cli.parseArguments(args);
Props props = new PropsBuilder(rawProperties, new JdbcSettings()).build();
- ProcessLogging logging = new ProcessLogging();
- logging.configure(props, "/org/sonar/application/logback.xml");
- if (props.valueAsBoolean("sonar.log.console", false)) {
- logging.addConsoleAppender();
- }
+ AppLogging logging = new AppLogging();
+ logging.configure(props);
App app = new App();
app.start(props);
diff --git a/sonar-application/src/main/java/org/sonar/application/AppLogging.java b/sonar-application/src/main/java/org/sonar/application/AppLogging.java
new file mode 100644
index 00000000000..6c8e56553da
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/AppLogging.java
@@ -0,0 +1,102 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.application;
+
+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.core.Appender;
+import ch.qos.logback.core.ConsoleAppender;
+import ch.qos.logback.core.FileAppender;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.LogbackHelper;
+import org.sonar.process.Props;
+
+/**
+ * Configure logback for the master process
+ */
+class AppLogging {
+
+ static final String CONSOLE_LOGGER = "console";
+ static final String CONSOLE_APPENDER = "CONSOLE";
+ static final String GOBBLER_LOGGER = "gobbler";
+ static final String GOBBLER_APPENDER = "GOBBLER";
+ static final String APP_PATTERN = "%d{yyyy.MM.dd HH:mm:ss} %-5level app[%logger{20}] %msg%n";
+
+ private final LogbackHelper helper = new LogbackHelper();
+
+ LoggerContext configure(Props props) {
+ LoggerContext ctx = helper.getRootContext();
+ ctx.reset();
+ helper.enableJulChangePropagation(ctx);
+ configureConsole(ctx);
+ configureGobbler(props, ctx);
+ configureRoot(ctx);
+ if (props.valueAsBoolean("sonar.log.console", false)) {
+ // used by SonarSource testing environment
+ copyGobblerToConsole();
+ }
+ return ctx;
+ }
+
+ /**
+ * Enable the copy in console of the logs written in logs/sonar.log
+ */
+ private void copyGobblerToConsole() {
+ Logger consoleLogger = (Logger) LoggerFactory.getLogger(CONSOLE_LOGGER);
+ Appender consoleAppender = consoleLogger.getAppender(CONSOLE_APPENDER);
+
+ Logger gobblerLogger = (Logger) LoggerFactory.getLogger(GOBBLER_LOGGER);
+ gobblerLogger.addAppender(consoleAppender);
+ }
+
+ private void configureConsole(LoggerContext loggerContext) {
+ ConsoleAppender consoleAppender = helper.newConsoleAppender(loggerContext, CONSOLE_APPENDER, "%msg%n");
+ Logger consoleLogger = loggerContext.getLogger(CONSOLE_LOGGER);
+ consoleLogger.setAdditive(false);
+ consoleLogger.addAppender(consoleAppender);
+ }
+
+ private void configureGobbler(Props props, LoggerContext ctx) {
+ // configure appender
+ LogbackHelper.RollingPolicy rollingPolicy = helper.createRollingPolicy(ctx, props, "sonar");
+ FileAppender fileAppender = rollingPolicy.createAppender(GOBBLER_APPENDER);
+ fileAppender.setContext(ctx);
+ PatternLayoutEncoder fileEncoder = new PatternLayoutEncoder();
+ fileEncoder.setContext(ctx);
+ fileEncoder.setPattern("%msg%n");
+ fileEncoder.start();
+ fileAppender.setEncoder(fileEncoder);
+ fileAppender.start();
+
+ // configure logger
+ Logger gobblerLogger = ctx.getLogger(GOBBLER_LOGGER);
+ gobblerLogger.setAdditive(false);
+ gobblerLogger.addAppender(fileAppender);
+ }
+
+ private void configureRoot(LoggerContext loggerContext) {
+ ConsoleAppender consoleAppender = helper.newConsoleAppender(loggerContext, "ROOT_CONSOLE", APP_PATTERN);
+ Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
+ rootLogger.setLevel(Level.INFO);
+ rootLogger.addAppender(consoleAppender);
+ }
+}
diff --git a/sonar-application/src/main/resources/org/sonar/application/logback.xml b/sonar-application/src/main/resources/org/sonar/application/logback.xml
deleted file mode 100644
index cbcd991ded9..00000000000
--- a/sonar-application/src/main/resources/org/sonar/application/logback.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-
-<configuration debug="false">
- <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
-
- <appender name="LOGFILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <File>${sonar.path.logs}/sonar.log</File>
- <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy">
- <param name="FileNamePattern" value="${sonar.path.logs}/sonar.%i.log"/>
- <param name="MinIndex" value="1"/>
- <param name="MaxIndex" value="5"/>
- </rollingPolicy>
- <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
- <param name="MaxFileSize" value="10MB"/>
- </triggeringPolicy>
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>%msg%n</pattern>
- </encoder>
- </appender>
-
- <appender name="APP" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>%d{yyyy.MM.dd HH:mm:ss} %-5level app[%logger{20}] %msg%n</pattern>
- </encoder>
- </appender>
-
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>
- %msg%n
- </pattern>
- </encoder>
- </appender>
-
- <logger name="console" additivity="false">
- <appender-ref ref="CONSOLE"/>
- </logger>
-
- <logger name="gobbler" additivity="false">
- <appender-ref ref="LOGFILE"/>
- </logger>
-
- <root>
- <level value="INFO"/>
- <appender-ref ref="APP"/>
- </root>
-
-</configuration>
diff --git a/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java b/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java
new file mode 100644
index 00000000000..e2c902aaf39
--- /dev/null
+++ b/sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java
@@ -0,0 +1,94 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.application;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.FileAppender;
+import ch.qos.logback.core.rolling.RollingFileAppender;
+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.LogbackHelper;
+import org.sonar.process.ProcessConstants;
+import org.sonar.process.Props;
+
+import java.io.File;
+import java.util.Properties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AppLoggingTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ Props props = new Props(new Properties());
+ AppLogging sut = new AppLogging();
+
+ @Before
+ public void setUp() throws Exception {
+ File dir = temp.newFolder();
+ props.set(ProcessConstants.PATH_LOGS, dir.getAbsolutePath());
+ }
+
+ @AfterClass
+ public static void resetLogback() throws Exception {
+ new LogbackHelper().resetFromXml("/logback-test.xml");
+ }
+
+ @Test
+ public void configure_defaults() throws Exception {
+ LoggerContext ctx = sut.configure(props);
+
+ Logger gobbler = ctx.getLogger(AppLogging.GOBBLER_LOGGER);
+ Appender<ILoggingEvent> appender = gobbler.getAppender(AppLogging.GOBBLER_APPENDER);
+ assertThat(appender).isInstanceOf(RollingFileAppender.class);
+
+ // gobbler is not copied to console
+ assertThat(gobbler.getAppender(AppLogging.GOBBLER_APPENDER)).isNotNull();
+ assertThat(gobbler.getAppender(AppLogging.CONSOLE_APPENDER)).isNull();
+ }
+
+ @Test
+ public void configure_no_rotation() throws Exception {
+ props.set("sonar.log.rollingPolicy", "none");
+
+ LoggerContext ctx = sut.configure(props);
+
+ Logger gobbler = ctx.getLogger(AppLogging.GOBBLER_LOGGER);
+ Appender<ILoggingEvent> appender = gobbler.getAppender(AppLogging.GOBBLER_APPENDER);
+ assertThat(appender).isNotInstanceOf(RollingFileAppender.class).isInstanceOf(FileAppender.class);
+ }
+
+ @Test
+ public void copyGobblerToConsole() throws Exception {
+ props.set("sonar.log.console", "true");
+
+ LoggerContext ctx = sut.configure(props);
+ Logger gobbler = ctx.getLogger(AppLogging.GOBBLER_LOGGER);
+ assertThat(gobbler.getAppender(AppLogging.GOBBLER_APPENDER)).isNotNull();
+ assertThat(gobbler.getAppender(AppLogging.CONSOLE_APPENDER)).isNotNull();
+ }
+}
diff --git a/sonar-application/src/test/resources/logback-test.xml b/sonar-application/src/test/resources/logback-test.xml
index ff2270cc122..f62e5c2b41d 100644
--- a/sonar-application/src/test/resources/logback-test.xml
+++ b/sonar-application/src/test/resources/logback-test.xml
@@ -1,28 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
-
-<!--
- Configuration for default logger. Only used while embedded server is starting,
- before proper logging configuration is loaded.
-
- See http://logback.qos.ch/manual/configuration.html
--->
<configuration debug="false">
<contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <filter class="ch.qos.logback.classic.filter.LevelFilter">
- <level>INFO</level>
- <onMatch>ACCEPT</onMatch>
- <onMismatch>DENY</onMismatch>
- </filter>
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
- <pattern>
- %d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n
- </pattern>
- </encoder>
- </appender>
-
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
%d{yyyy.MM.dd HH:mm:ss} %-5level %msg%n
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Logger.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Logger.java
new file mode 100644
index 00000000000..dab1503bce8
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Logger.java
@@ -0,0 +1,16 @@
+package org.sonar.api.utils.log;
+
+public interface Logger {
+
+ boolean isDebugEnabled();
+
+ void debug(String message);
+
+ void info(String message);
+
+ void warn(String message);
+
+ void error(String message);
+
+ void error(String message, Throwable throwable);
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Loggers.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Loggers.java
new file mode 100644
index 00000000000..6a9f278e112
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/Loggers.java
@@ -0,0 +1,6 @@
+package org.sonar.api.utils.log;
+
+public class Loggers {
+
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/InMemoryLogger.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/InMemoryLogger.java
new file mode 100644
index 00000000000..b7f2fa061de
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/InMemoryLogger.java
@@ -0,0 +1,88 @@
+package org.sonar.api.utils.log.internal;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import org.sonar.api.utils.log.Logger;
+
+import java.util.List;
+
+/**
+ * Implementation of {@link org.sonar.api.utils.log.Logger} which keeps logs
+ * in memory, so that they can be loaded after writing. It is helpful
+ * for testing.
+ */
+public class InMemoryLogger implements Logger {
+
+ private static enum Level {
+ DEBUG, INFO, WARN, ERROR
+ }
+
+ private boolean debugEnabled = false;
+ private final ArrayListMultimap<Level, String> logs = ArrayListMultimap.create();
+
+ @Override
+ public boolean isDebugEnabled() {
+ return debugEnabled;
+ }
+
+ public InMemoryLogger setDebugEnabled(boolean b) {
+ this.debugEnabled = b;
+ return this;
+ }
+
+ @Override
+ public void debug(String message) {
+ if (isDebugEnabled()) {
+ log(Level.DEBUG, message);
+ }
+ }
+
+ @Override
+ public void info(String message) {
+ log(Level.INFO, message);
+ }
+
+ @Override
+ public void warn(String message) {
+ log(Level.WARN, message);
+ }
+
+ @Override
+ public void error(String message) {
+ log(Level.ERROR, message);
+ }
+
+ @Override
+ public void error(String message, Throwable throwable) {
+ log(Level.ERROR, String.format("%s | %s", message, throwable.getMessage()));
+ }
+
+ public List<String> logs() {
+ return Lists.newArrayList(logs.values());
+ }
+
+ public List<String> debugLogs() {
+ return logs.get(Level.DEBUG);
+ }
+
+ public List<String> infoLogs() {
+ return logs.get(Level.INFO);
+ }
+
+ public List<String> warnLogs() {
+ return logs.get(Level.WARN);
+ }
+
+ public List<String> errorLogs() {
+ return logs.get(Level.ERROR);
+ }
+
+ public InMemoryLogger clear() {
+ logs.clear();
+ return this;
+ }
+
+ private void log(Level level, String message) {
+ logs.put(level, message);
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/Slf4jLoggers.java b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/Slf4jLoggers.java
new file mode 100644
index 00000000000..d1cfadd2bf5
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/utils/log/internal/Slf4jLoggers.java
@@ -0,0 +1,54 @@
+package org.sonar.api.utils.log.internal;
+
+
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.log.Logger;
+
+public class Slf4jLoggers {
+
+ public static class Slf4jLogger implements Logger {
+ private final org.slf4j.Logger slf4j;
+
+ public Slf4jLogger(org.slf4j.Logger slf4j) {
+ this.slf4j = slf4j;
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return slf4j.isDebugEnabled();
+ }
+
+ @Override
+ public void debug(String message) {
+ slf4j.debug(message);
+ }
+
+ @Override
+ public void info(String message) {
+ slf4j.info(message);
+ }
+
+ @Override
+ public void warn(String message) {
+ slf4j.warn(message);
+ }
+
+ @Override
+ public void error(String message) {
+ slf4j.error(message);
+ }
+
+ @Override
+ public void error(String message, Throwable throwable) {
+ slf4j.error(message, throwable);
+ }
+ }
+
+ public Slf4jLogger getLogger(String name) {
+ return new Slf4jLogger(LoggerFactory.getLogger(name));
+ }
+
+ public Slf4jLogger getLogger(Class name) {
+ return new Slf4jLogger(LoggerFactory.getLogger(name));
+ }
+}