You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Log4JPropertiesBuilder.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.process.logging;
  21. import ch.qos.logback.classic.Level;
  22. import java.io.File;
  23. import java.util.List;
  24. import java.util.Objects;
  25. import java.util.Properties;
  26. import java.util.stream.Collectors;
  27. import java.util.stream.Stream;
  28. import org.apache.commons.lang.StringUtils;
  29. import org.sonar.process.MessageException;
  30. import org.sonar.process.Props;
  31. import static java.lang.String.format;
  32. import static java.lang.String.valueOf;
  33. public class Log4JPropertiesBuilder extends AbstractLogHelper {
  34. private static final String ROOT_LOGGER_NAME = "rootLogger";
  35. private static final int UNLIMITED_MAX_FILES = 100_000;
  36. private final Properties log4j2Properties = new Properties();
  37. private final Props props;
  38. public Log4JPropertiesBuilder(Props props) {
  39. super("%logger{1.}");
  40. this.props = Objects.requireNonNull(props, "Props can't be null");
  41. internalLogLevel(Level.ERROR);
  42. }
  43. @Override
  44. public String getRootLoggerName() {
  45. return ROOT_LOGGER_NAME;
  46. }
  47. public Properties get() {
  48. Properties res = new Properties();
  49. res.putAll(log4j2Properties);
  50. return res;
  51. }
  52. public void internalLogLevel(Level level) {
  53. putProperty("status", level.toString());
  54. }
  55. private void putProperty(String key, String value) {
  56. log4j2Properties.put(key, value);
  57. }
  58. /**
  59. * Make log4j2 configuration for a process to push all its logs to a log file.
  60. * <p>
  61. * <ul>
  62. * <li>the file's name will use the prefix defined in {@link RootLoggerConfig#getProcessId()#getLogFilenamePrefix()}.</li>
  63. * <li>the file will follow the rotation policy defined in property {@link #ROLLING_POLICY_PROPERTY} and
  64. * the max number of files defined in property {@link #MAX_FILES_PROPERTY}</li>
  65. * <li>the logs will follow the specified log pattern</li>
  66. * </ul>
  67. * </p>
  68. *
  69. * @see #buildLogPattern(RootLoggerConfig)
  70. */
  71. public void configureGlobalFileLog(RootLoggerConfig config, File logDir, String logPattern) {
  72. String appenderRef = writeFileAppender(config, logDir, logPattern);
  73. putProperty(ROOT_LOGGER_NAME + ".appenderRef." + appenderRef + ".ref", appenderRef);
  74. }
  75. private String writeFileAppender(RootLoggerConfig config, File logDir, String logPattern) {
  76. String appenderName = "file_" + config.getProcessId().getLogFilenamePrefix();
  77. RollingPolicy rollingPolicy = createRollingPolicy(logDir, config.getProcessId().getLogFilenamePrefix());
  78. FileAppender appender = new FileAppender(appenderName, rollingPolicy, logPattern);
  79. appender.writeAppenderProperties();
  80. return appender.getAppenderRef();
  81. }
  82. public void configureGlobalStdoutLog(String logPattern) {
  83. String appenderRef = writeStdoutAppender(logPattern);
  84. putProperty(ROOT_LOGGER_NAME + ".appenderRef." + appenderRef + ".ref", appenderRef);
  85. }
  86. private String writeStdoutAppender(String logPattern) {
  87. String appenderName = "stdout";
  88. ConsoleAppender appender = new ConsoleAppender(appenderName, logPattern);
  89. appender.writeAppenderProperties();
  90. return appender.getAppenderRef();
  91. }
  92. private RollingPolicy createRollingPolicy(File logDir, String filenamePrefix) {
  93. String rollingPolicy = props.value(ROLLING_POLICY_PROPERTY, "time:yyyy-MM-dd");
  94. int maxFiles = props.valueAsInt(MAX_FILES_PROPERTY, 7);
  95. if (maxFiles <= 0) {
  96. maxFiles = UNLIMITED_MAX_FILES;
  97. }
  98. if (rollingPolicy.startsWith("time:")) {
  99. return new TimeRollingPolicy(filenamePrefix, logDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "time:"));
  100. } else if (rollingPolicy.startsWith("size:")) {
  101. return new SizeRollingPolicy(filenamePrefix, logDir, maxFiles, StringUtils.substringAfter(rollingPolicy, "size:"));
  102. } else if ("none".equals(rollingPolicy)) {
  103. return new NoRollingPolicy(filenamePrefix, logDir);
  104. } else {
  105. throw new MessageException(format("Unsupported value for property %s: %s", ROLLING_POLICY_PROPERTY, rollingPolicy));
  106. }
  107. }
  108. public void apply(LogLevelConfig logLevelConfig) {
  109. if (!ROOT_LOGGER_NAME.equals(logLevelConfig.getRootLoggerName())) {
  110. throw new IllegalArgumentException("Value of LogLevelConfig#rootLoggerName must be \"" + ROOT_LOGGER_NAME + "\"");
  111. }
  112. Level propertyValueAsLevel = getPropertyValueAsLevel(props, SONAR_LOG_LEVEL_PROPERTY);
  113. boolean traceGloballyEnabled = propertyValueAsLevel == Level.TRACE;
  114. List<String> loggerNames = Stream.of(
  115. logLevelConfig.getConfiguredByProperties().keySet().stream(),
  116. logLevelConfig.getConfiguredByHardcodedLevel().keySet().stream(),
  117. logLevelConfig.getOffUnlessTrace().stream().filter(k -> !traceGloballyEnabled))
  118. .flatMap(s -> s)
  119. .filter(loggerName -> !ROOT_LOGGER_NAME.equals(loggerName))
  120. .distinct()
  121. .sorted()
  122. .collect(Collectors.toList());
  123. if (!loggerNames.isEmpty()) {
  124. putProperty("loggers", loggerNames.stream().collect(Collectors.joining(",")));
  125. }
  126. logLevelConfig.getConfiguredByProperties().forEach((loggerName, value) -> applyLevelByProperty(props, loggerName, value));
  127. logLevelConfig.getConfiguredByHardcodedLevel().forEach(this::applyHardcodedLevel);
  128. logLevelConfig.getOffUnlessTrace().stream().filter(k -> !traceGloballyEnabled).forEach(logger -> applyHardcodedLevel(logger, Level.OFF));
  129. }
  130. private void applyLevelByProperty(Props props, String loggerKey, List<String> properties) {
  131. putLevel(loggerKey, resolveLevel(props, properties.toArray(new String[0])));
  132. }
  133. private void applyHardcodedLevel(String loggerName, Level newLevel) {
  134. putLevel(loggerName, newLevel);
  135. }
  136. private void putLevel(String loggerName, Level level) {
  137. if (loggerName.equals(ROOT_LOGGER_NAME)) {
  138. putProperty(loggerName + ".level", level.toString());
  139. } else {
  140. putProperty("logger." + loggerName + ".name", loggerName);
  141. putProperty("logger." + loggerName + ".level", level.toString());
  142. }
  143. }
  144. private class FileAppender {
  145. private final String prefix;
  146. private final String appenderName;
  147. private final RollingPolicy rollingPolicy;
  148. private final String logPattern;
  149. private FileAppender(String appenderName, RollingPolicy rollingPolicy, String logPattern) {
  150. this.prefix = "appender." + appenderName + ".";
  151. this.appenderName = appenderName;
  152. this.rollingPolicy = rollingPolicy;
  153. this.logPattern = logPattern;
  154. }
  155. void writeAppenderProperties() {
  156. put("name", appenderName);
  157. put("layout.type", "PatternLayout");
  158. put("layout.pattern", logPattern);
  159. rollingPolicy.writePolicy(this.prefix);
  160. }
  161. void put(String key, String value) {
  162. Log4JPropertiesBuilder.this.putProperty(this.prefix + key, value);
  163. }
  164. String getAppenderRef() {
  165. return appenderName;
  166. }
  167. }
  168. private class ConsoleAppender {
  169. private final String prefix;
  170. private final String appenderName;
  171. private final String logPattern;
  172. private ConsoleAppender(String appenderName, String logPattern) {
  173. this.prefix = "appender." + appenderName + ".";
  174. this.appenderName = appenderName;
  175. this.logPattern = logPattern;
  176. }
  177. void writeAppenderProperties() {
  178. put("type", "Console");
  179. put("name", appenderName);
  180. put("layout.type", "PatternLayout");
  181. put("layout.pattern", logPattern);
  182. }
  183. void put(String key, String value) {
  184. Log4JPropertiesBuilder.this.putProperty(this.prefix + key, value);
  185. }
  186. String getAppenderRef() {
  187. return appenderName;
  188. }
  189. }
  190. private abstract class RollingPolicy {
  191. final String filenamePrefix;
  192. final File logsDir;
  193. RollingPolicy(String filenamePrefix, File logsDir) {
  194. this.filenamePrefix = filenamePrefix;
  195. this.logsDir = logsDir;
  196. }
  197. abstract void writePolicy(String propertyPrefix);
  198. void writeTypeProperty(String propertyPrefix, String type) {
  199. putProperty(propertyPrefix + "type", type);
  200. }
  201. void writeFileNameProperty(String propertyPrefix) {
  202. putProperty(propertyPrefix + "fileName", new File(logsDir, filenamePrefix + ".log").getAbsolutePath());
  203. }
  204. void writeFilePatternProperty(String propertyPrefix, String pattern) {
  205. putProperty(propertyPrefix + "filePattern", new File(logsDir, filenamePrefix + "." + pattern + ".log").getAbsolutePath());
  206. }
  207. }
  208. /**
  209. * Log files are not rotated, for example when unix command logrotate is in place.
  210. */
  211. private class NoRollingPolicy extends RollingPolicy {
  212. private NoRollingPolicy(String filenamePrefix, File logsDir) {
  213. super(filenamePrefix, logsDir);
  214. }
  215. @Override
  216. public void writePolicy(String propertyPrefix) {
  217. writeTypeProperty(propertyPrefix, "File");
  218. writeFileNameProperty(propertyPrefix);
  219. }
  220. }
  221. private class TimeRollingPolicy extends RollingPolicy {
  222. private final String datePattern;
  223. private final int maxFiles;
  224. private TimeRollingPolicy(String filenamePrefix, File logsDir, int maxFiles, String datePattern) {
  225. super(filenamePrefix, logsDir);
  226. this.datePattern = datePattern;
  227. this.maxFiles = maxFiles;
  228. }
  229. @Override
  230. public void writePolicy(String propertyPrefix) {
  231. writeTypeProperty(propertyPrefix, "RollingFile");
  232. writeFileNameProperty(propertyPrefix);
  233. writeFilePatternProperty(propertyPrefix, "%d{" + datePattern + "}");
  234. putProperty(propertyPrefix + "policies.type", "Policies");
  235. putProperty(propertyPrefix + "policies.time.type", "TimeBasedTriggeringPolicy");
  236. putProperty(propertyPrefix + "policies.time.interval", "1");
  237. putProperty(propertyPrefix + "policies.time.modulate", "true");
  238. putProperty(propertyPrefix + "strategy.type", "DefaultRolloverStrategy");
  239. putProperty(propertyPrefix + "strategy.fileIndex", "nomax");
  240. putProperty(propertyPrefix + "strategy.action.type", "Delete");
  241. putProperty(propertyPrefix + "strategy.action.basepath", logsDir.getAbsolutePath());
  242. putProperty(propertyPrefix + "strategy.action.maxDepth", valueOf(1));
  243. putProperty(propertyPrefix + "strategy.action.condition.type", "IfFileName");
  244. putProperty(propertyPrefix + "strategy.action.condition.glob", filenamePrefix + "*");
  245. putProperty(propertyPrefix + "strategy.action.condition.nested_condition.type", "IfAccumulatedFileCount");
  246. putProperty(propertyPrefix + "strategy.action.condition.nested_condition.exceeds", valueOf(maxFiles));
  247. }
  248. }
  249. private class SizeRollingPolicy extends RollingPolicy {
  250. private final String size;
  251. private final int maxFiles;
  252. private SizeRollingPolicy(String filenamePrefix, File logsDir, int maxFiles, String size) {
  253. super(filenamePrefix, logsDir);
  254. this.size = size;
  255. this.maxFiles = maxFiles;
  256. }
  257. @Override
  258. public void writePolicy(String propertyPrefix) {
  259. writeTypeProperty(propertyPrefix, "RollingFile");
  260. writeFileNameProperty(propertyPrefix);
  261. writeFilePatternProperty(propertyPrefix, "%i");
  262. putProperty(propertyPrefix + "policies.type", "Policies");
  263. putProperty(propertyPrefix + "policies.size.type", "SizeBasedTriggeringPolicy");
  264. putProperty(propertyPrefix + "policies.size.size", size);
  265. putProperty(propertyPrefix + "strategy.type", "DefaultRolloverStrategy");
  266. putProperty(propertyPrefix + "strategy.max", valueOf(maxFiles));
  267. }
  268. }
  269. }