diff --git a/.gitignore b/.gitignore index d67ee403..48467368 100644 --- a/.gitignore +++ b/.gitignore @@ -13,14 +13,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -target/ +# IntelliJ IDEA .idea *.iml *.iws -/out -.flattened-pom.xml -/.mvn/wrapper/maven-wrapper.jar # Eclipse .project .classpath .settings/ +# Generated by Maven +.flattened-pom.xml +dependency-reduced-pom.xml +/.mvn/wrapper/maven-wrapper.jar +target/ diff --git a/log4j-config-converter/pom.xml b/log4j-config-converter/pom.xml new file mode 100644 index 00000000..aebeb99a --- /dev/null +++ b/log4j-config-converter/pom.xml @@ -0,0 +1,146 @@ + + + + 4.0.0 + + org.apache.logging.log4j + log4j-transform-parent + ${revision} + ../log4j-transform-parent + + + log4j-config-converter + Apache Log4j Configuration converter + Converts between multiple Log4j configuration formats. + + + + false + + org.apache.logging.log4j.config.converter.Log4j1ConfigurationConverter + + + 3.4.1 + + + 3.0.0-SNAPSHOT + 4.7.5 + + + + + + + info.picocli + picocli + ${picocli.version} + + + + + + + + + org.apache.logging.log4j + log4j-api + + + + org.apache.logging.log4j + log4j-1.2-api + + + + org.apache.logging.log4j + log4j-core + + + + info.picocli + picocli + + + + org.apache.logging.log4j + log4j-api-test + test + + + + org.junit.jupiter + junit-jupiter-api + test + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + info.picocli + picocli-codegen + ${picocli.version} + + + + + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin.version} + + + org.apache.logging.log4j + log4j-transform-maven-shade-plugin-extensions + ${project.version} + + + + + shade-jar-with-dependencies + + shade + + + true + + + + + + true + + + + + + + + + + + + diff --git a/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/InputStreamWrapper.java b/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/InputStreamWrapper.java new file mode 100644 index 00000000..5a771d9c --- /dev/null +++ b/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/InputStreamWrapper.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.config.converter; + +import java.io.IOException; +import java.io.InputStream; + +class InputStreamWrapper extends InputStream { + + private final String description; + private final InputStream input; + + public InputStreamWrapper(final InputStream input, final String description) { + this.input = input; + this.description = description; + } + + @Override + public int available() throws IOException { + return input.available(); + } + + @Override + public void close() throws IOException { + input.close(); + } + + @Override + public boolean equals(final Object obj) { + return input.equals(obj); + } + + @Override + public int hashCode() { + return input.hashCode(); + } + + @Override + public synchronized void mark(final int readlimit) { + input.mark(readlimit); + } + + @Override + public boolean markSupported() { + return input.markSupported(); + } + + @Override + public int read() throws IOException { + return input.read(); + } + + @Override + public int read(final byte[] b) throws IOException { + return input.read(b); + } + + @Override + public int read(final byte[] b, final int off, final int len) throws IOException { + return input.read(b, off, len); + } + + @Override + public synchronized void reset() throws IOException { + input.reset(); + } + + @Override + public long skip(final long n) throws IOException { + return input.skip(n); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [description=" + description + ", input=" + input + "]"; + } +} diff --git a/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/Log4j1ConfigurationConverter.java b/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/Log4j1ConfigurationConverter.java new file mode 100644 index 00000000..16414c46 --- /dev/null +++ b/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/Log4j1ConfigurationConverter.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.config.converter; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.concurrent.atomic.AtomicInteger; +import javax.xml.transform.TransformerException; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import org.apache.logging.log4j.core.config.ConfigurationException; +import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.core.config.builder.impl.DefaultConfigurationBuilder; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +/** + * Tool for converting a Log4j 1.x properties configuration file to Log4j 2.x XML configuration file. + * + *

+ * Run with "--help" on the command line. + *

+ * + *

+ * Example: + *

+ * + *
+ * java org.apache.logging.log4j.cli.converter.Log4j1ConfigurationConverter --recurse
+ * E:\vcs\git\apache\logging\logging-log4j2\log4j-1.2-api\src\test\resources\config-1.2\hadoop --in log4j.properties --verbose
+ * 
+ */ +@Command(name = "convert") +public final class Log4j1ConfigurationConverter implements Runnable { + + @Option( + names = {"--help", "-h"}, + usageHelp = true, + description = "Usage help.") + private boolean help; + + @Option( + names = {"--failfast", "-f"}, + description = "Fails on the first failure in recurse mode.") + private boolean failFast; + + @Option( + names = {"--in", "-i"}, + description = "Specifies the input file.") + private Path pathIn; + + @Option( + names = {"--out", "-o"}, + description = "Specifies the output file.") + private Path pathOut; + + @Option( + names = {"--recurse", "-r"}, + description = "Recurses into this folder looking for the input file") + private Path recurseIntoPath; + + @Option( + names = {"--verbose", "-v"}, + description = "Be verbose.") + private boolean verbose; + + private Log4j1ConfigurationConverter() {} + + public boolean isHelp() { + return help; + } + + public Path getPathIn() { + return pathIn; + } + + public Path getPathOut() { + return pathOut; + } + + public Path getRecurseIntoPath() { + return recurseIntoPath; + } + + public boolean isFailFast() { + return failFast; + } + + public boolean isVerbose() { + return verbose; + } + + public void setHelp(boolean help) { + this.help = help; + } + + public void setFailFast(final boolean failFast) { + this.failFast = failFast; + } + + public void setPathIn(final Path pathIn) { + this.pathIn = pathIn; + } + + public void setPathOut(final Path pathOut) { + this.pathOut = pathOut; + } + + public void setRecurseIntoPath(final Path recurseIntoPath) { + this.recurseIntoPath = recurseIntoPath; + } + + public void setVerbose(final boolean verbose) { + this.verbose = verbose; + } + + private static final String FILE_EXT_XML = ".xml"; + + public static void main(final String[] args) { + final Log4j1ConfigurationConverter command = new Log4j1ConfigurationConverter(); + new CommandLine(command).execute(args); + } + + private void convert(final InputStream input, final OutputStream output) throws IOException { + final ConfigurationBuilder builder = + new Log4j1ConfigurationParser().buildConfigurationBuilder(input); + builder.writeXmlConfiguration(output); + } + + private InputStream getInputStream() throws IOException { + final Path pathIn = getPathIn(); + return pathIn == null ? System.in : new InputStreamWrapper(Files.newInputStream(pathIn), pathIn.toString()); + } + + private OutputStream getOutputStream() throws IOException { + final Path pathOut = getPathOut(); + return pathOut == null ? System.out : Files.newOutputStream(pathOut); + } + + @Override + public void run() { + final Path recurseIntoPath = getRecurseIntoPath(); + final Path pathIn = getPathIn(); + if (recurseIntoPath != null) { + final AtomicInteger countOKs = new AtomicInteger(); + final AtomicInteger countFails = new AtomicInteger(); + try { + Files.walkFileTree(recurseIntoPath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) + throws IOException { + if (pathIn == null || pathIn.equals(file.getFileName())) { + verbose("Reading %s", file); + String newFile = String.valueOf(file.getFileName()); + final int lastIndex = newFile.lastIndexOf("."); + newFile = lastIndex < 0 + ? newFile + FILE_EXT_XML + : newFile.substring(0, lastIndex) + FILE_EXT_XML; + final Path resolvedPath = file.resolveSibling(newFile); + try (final InputStream input = + new InputStreamWrapper(Files.newInputStream(file), file.toString()); + final OutputStream output = Files.newOutputStream(resolvedPath)) { + try { + final ByteArrayOutputStream tmpOutput = new ByteArrayOutputStream(); + convert(input, tmpOutput); + tmpOutput.close(); + DefaultConfigurationBuilder.formatXml( + new StreamSource(new ByteArrayInputStream(tmpOutput.toByteArray())), + new StreamResult(output)); + countOKs.incrementAndGet(); + } catch (ConfigurationException | IOException e) { + countFails.incrementAndGet(); + if (isFailFast()) { + throw e; + } + e.printStackTrace(); + } catch (TransformerException e) { + countFails.incrementAndGet(); + if (isFailFast()) { + throw new IOException(e); + } + e.printStackTrace(); + } + verbose("Wrote %s", resolvedPath); + } + } + return FileVisitResult.CONTINUE; + } + }); + } catch (final IOException e) { + throw new ConfigurationException(e); + } finally { + verbose( + "OK = %,d, Failures = %,d, Total = %,d", + countOKs.get(), countFails.get(), countOKs.get() + countFails.get()); + } + } else { + verbose("Reading %s", pathIn); + try (final InputStream input = getInputStream(); + final OutputStream output = getOutputStream()) { + convert(input, output); + } catch (final IOException e) { + throw new ConfigurationException(e); + } + verbose("Wrote %s", getPathOut()); + } + } + + private void verbose(final String template, final Object... args) { + if (isVerbose()) { + System.err.println(String.format(template, args)); + } + } +} diff --git a/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/Log4j1ConfigurationParser.java b/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/Log4j1ConfigurationParser.java new file mode 100644 index 00000000..e7ce75a2 --- /dev/null +++ b/log4j-config-converter/src/main/java/org/apache/logging/log4j/config/converter/Log4j1ConfigurationParser.java @@ -0,0 +1,443 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.logging.log4j.config.converter; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; +import org.apache.log4j.config.PropertiesConfiguration; +import org.apache.log4j.helpers.OptionConverter; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.core.Filter.Result; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.FileAppender; +import org.apache.logging.log4j.core.appender.NullAppender; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.ConfigurationException; +import org.apache.logging.log4j.core.config.builder.api.*; +import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; +import org.apache.logging.log4j.status.StatusLogger; +import org.apache.logging.log4j.util.Strings; + +/** + * Experimental parser for Log4j 1.2 properties configuration files. + * + * This class is not thread-safe. + * + *

+ * From the Log4j 1.2 Javadocs: + *

+ *

+ * All option values admit variable substitution. The syntax of variable substitution is similar to that of Unix shells. The string between + * an opening "${" and closing "}" is interpreted as a key. The value of the substituted variable can be defined as a system property or in + * the configuration file itself. The value of the key is first searched in the system properties, and if not found there, it is then + * searched in the configuration file being parsed. The corresponding value replaces the ${variableName} sequence. For example, if java.home + * system property is set to /home/xyz, then every occurrence of the sequence ${java.home} will be interpreted as /home/xyz. + *

+ */ +public class Log4j1ConfigurationParser { + + private static final String COMMA_DELIMITED_RE = "\\s*,\\s*"; + private static final String ROOTLOGGER = "rootLogger"; + private static final String ROOTCATEGORY = "rootCategory"; + private static final String TRUE = "true"; + private static final String FALSE = "false"; + private static final String RELATIVE = "RELATIVE"; + private static final String NULL = "NULL"; + + private final Properties properties = new Properties(); + + private final ConfigurationBuilder builder = + ConfigurationBuilderFactory.newConfigurationBuilder(); + + /** + * Parses a Log4j 1.2 properties configuration file in ISO 8859-1 encoding into a ConfigurationBuilder. + * + * @param input + * InputStream to read from is assumed to be ISO 8859-1, and will not be closed. + * @return the populated ConfigurationBuilder, never {@code null} + * @throws IOException + * if unable to read the input + * @throws ConfigurationException + * if the input does not contain a valid configuration + */ + ConfigurationBuilder buildConfigurationBuilder(final InputStream input) throws IOException { + try { + properties.load(input); + final String rootCategoryValue = getLog4jValue(ROOTCATEGORY); + final String rootLoggerValue = getLog4jValue(ROOTLOGGER); + if (rootCategoryValue == null && rootLoggerValue == null) { + // This is not a Log4j 1 properties configuration file. + warn("Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input); + // throw new ConfigurationException( + // "Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input); + } + builder.setConfigurationName("Log4j1"); + // DEBUG + final String debugValue = getLog4jValue("debug"); + if (Boolean.parseBoolean(debugValue)) { + builder.setStatusLevel(Level.DEBUG); + } + // global threshold + final String threshold = OptionConverter.findAndSubst(PropertiesConfiguration.THRESHOLD_KEY, properties); + if (threshold != null) { + final Level level = OptionConverter.convertLevel(threshold.trim(), Level.ALL); + builder.add(builder.newFilter("ThresholdFilter", Result.NEUTRAL, Result.DENY) + .addAttribute("level", level)); + } + // Root + buildRootLogger(getLog4jValue(ROOTCATEGORY)); + buildRootLogger(getLog4jValue(ROOTLOGGER)); + // Appenders + final Map appenderNameToClassName = buildClassToPropertyPrefixMap(); + for (final Map.Entry entry : appenderNameToClassName.entrySet()) { + final String appenderName = entry.getKey(); + final String appenderClass = entry.getValue(); + buildAppender(appenderName, appenderClass); + } + // Loggers + buildLoggers("log4j.category."); + buildLoggers("log4j.logger."); + buildProperties(); + return builder; + } catch (final IllegalArgumentException e) { + throw new ConfigurationException(e); + } + } + + private void buildProperties() { + for (final Map.Entry entry : new TreeMap<>(properties).entrySet()) { + final String key = entry.getKey().toString(); + if (!key.startsWith("log4j.") && !key.equals(ROOTCATEGORY) && !key.equals(ROOTLOGGER)) { + builder.addProperty(key, Objects.toString(entry.getValue(), Strings.EMPTY)); + } + } + } + + private void warn(final String string) { + System.err.println(string); + } + + private Map buildClassToPropertyPrefixMap() { + final String prefix = "log4j.appender."; + final int preLength = prefix.length(); + final Map map = new HashMap<>(); + for (final Map.Entry entry : properties.entrySet()) { + final Object keyObj = entry.getKey(); + if (keyObj != null) { + final String key = keyObj.toString().trim(); + if (key.startsWith(prefix)) { + if (key.indexOf('.', preLength) < 0) { + final String name = key.substring(preLength); + final Object value = entry.getValue(); + if (value != null) { + map.put(name, value.toString().trim()); + } + } + } + } + } + return map; + } + + private void buildAppender(final String appenderName, final String appenderClass) { + switch (appenderClass) { + case "org.apache.log4j.ConsoleAppender": + buildConsoleAppender(appenderName); + break; + case "org.apache.log4j.FileAppender": + buildFileAppender(appenderName); + break; + case "org.apache.log4j.DailyRollingFileAppender": + buildDailyRollingFileAppender(appenderName); + break; + case "org.apache.log4j.RollingFileAppender": + buildRollingFileAppender(appenderName); + break; + case "org.apache.log4j.varia.NullAppender": + buildNullAppender(appenderName); + break; + default: + reportWarning("Unknown appender class: " + appenderClass + "; ignoring appender: " + appenderName); + } + } + + private void buildConsoleAppender(final String appenderName) { + final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, ConsoleAppender.PLUGIN_NAME); + final String targetValue = getLog4jAppenderValue(appenderName, "Target", "System.out"); + if (targetValue != null) { + final ConsoleAppender.Target target; + switch (targetValue) { + case "System.out": + target = ConsoleAppender.Target.SYSTEM_OUT; + break; + case "System.err": + target = ConsoleAppender.Target.SYSTEM_ERR; + break; + default: + reportWarning("Unknown value for console Target: " + targetValue); + target = null; + } + if (target != null) { + appenderBuilder.addAttribute("target", target); + } + } + buildAttribute(appenderName, appenderBuilder, "Follow", "follow"); + if (FALSE.equalsIgnoreCase(getLog4jAppenderValue(appenderName, "ImmediateFlush"))) { + reportWarning("ImmediateFlush=false is not supported on Console appender"); + } + buildAppenderLayout(appenderName, appenderBuilder); + builder.add(appenderBuilder); + } + + private void buildFileAppender(final String appenderName) { + final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, FileAppender.PLUGIN_NAME); + buildFileAppender(appenderName, appenderBuilder); + builder.add(appenderBuilder); + } + + private void buildFileAppender(final String appenderName, final AppenderComponentBuilder appenderBuilder) { + buildMandatoryAttribute(appenderName, appenderBuilder, "File", "fileName"); + buildAttribute(appenderName, appenderBuilder, "Append", "append"); + buildAttribute(appenderName, appenderBuilder, "BufferedIO", "bufferedIo"); + buildAttribute(appenderName, appenderBuilder, "BufferSize", "bufferSize"); + buildAttribute(appenderName, appenderBuilder, "ImmediateFlush", "immediateFlush"); + buildAppenderLayout(appenderName, appenderBuilder); + } + + private void buildDailyRollingFileAppender(final String appenderName) { + final AppenderComponentBuilder appenderBuilder = + builder.newAppender(appenderName, RollingFileAppender.PLUGIN_NAME); + buildFileAppender(appenderName, appenderBuilder); + final String fileName = getLog4jAppenderValue(appenderName, "File"); + final String datePattern = getLog4jAppenderValue(appenderName, "DatePattern", ".yyyy-MM-dd"); + appenderBuilder.addAttribute("filePattern", fileName + "%d{" + datePattern + "}"); + final ComponentBuilder triggeringPolicy = builder.newComponent("Policies") + .addComponent(builder.newComponent("TimeBasedTriggeringPolicy").addAttribute("modulate", true)); + appenderBuilder.addComponent(triggeringPolicy); + appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy") + .addAttribute("max", Integer.MAX_VALUE) + .addAttribute("fileIndex", "min")); + builder.add(appenderBuilder); + } + + private void buildRollingFileAppender(final String appenderName) { + final AppenderComponentBuilder appenderBuilder = + builder.newAppender(appenderName, RollingFileAppender.PLUGIN_NAME); + buildFileAppender(appenderName, appenderBuilder); + final String fileName = getLog4jAppenderValue(appenderName, "File"); + appenderBuilder.addAttribute("filePattern", fileName + ".%i"); + final String maxFileSizeString = getLog4jAppenderValue(appenderName, "MaxFileSize", "10485760"); + final String maxBackupIndexString = getLog4jAppenderValue(appenderName, "MaxBackupIndex", "1"); + final ComponentBuilder triggeringPolicy = builder.newComponent("Policies") + .addComponent( + builder.newComponent("SizeBasedTriggeringPolicy").addAttribute("size", maxFileSizeString)); + appenderBuilder.addComponent(triggeringPolicy); + appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy") + .addAttribute("max", maxBackupIndexString) + .addAttribute("fileIndex", "min")); + builder.add(appenderBuilder); + } + + private void buildAttribute( + final String componentName, + final ComponentBuilder componentBuilder, + final String sourceAttributeName, + final String targetAttributeName) { + final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName); + if (attributeValue != null) { + componentBuilder.addAttribute(targetAttributeName, attributeValue); + } + } + + private void buildMandatoryAttribute( + final String componentName, + final ComponentBuilder componentBuilder, + final String sourceAttributeName, + final String targetAttributeName) { + final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName); + if (attributeValue != null) { + componentBuilder.addAttribute(targetAttributeName, attributeValue); + } else { + reportWarning("Missing " + sourceAttributeName + " for " + componentName); + } + } + + private void buildNullAppender(final String appenderName) { + final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, NullAppender.PLUGIN_NAME); + builder.add(appenderBuilder); + } + + private void buildAppenderLayout(final String name, final AppenderComponentBuilder appenderBuilder) { + final String layoutClass = getLog4jAppenderValue(name, "layout", null); + if (layoutClass != null) { + switch (layoutClass) { + case "org.apache.log4j.PatternLayout": + case "org.apache.log4j.EnhancedPatternLayout": { + final String pattern = getLog4jAppenderValue(name, "layout.ConversionPattern", null); + appenderBuilder.add(newPatternLayout(pattern != null ? pattern : "%m%n")); + break; + } + case "org.apache.log4j.SimpleLayout": { + appenderBuilder.add(newPatternLayout("%p - %m%n")); + break; + } + case "org.apache.log4j.TTCCLayout": { + String pattern = ""; + final String dateFormat = getLog4jAppenderValue(name, "layout.DateFormat", RELATIVE); + final String timezone = getLog4jAppenderValue(name, "layout.TimeZone", null); + if (dateFormat != null) { + if (RELATIVE.equalsIgnoreCase(dateFormat)) { + pattern += "%r "; + } else if (!NULL.equalsIgnoreCase(dateFormat)) { + pattern += "%d{" + dateFormat + "}"; + if (timezone != null) { + pattern += "{" + timezone + "}"; + } + pattern += " "; + } + } + if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ThreadPrinting", TRUE))) { + pattern += "[%t] "; + } + pattern += "%p "; + if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.CategoryPrefixing", TRUE))) { + pattern += "%c "; + } + if (Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.ContextPrinting", TRUE))) { + pattern += "%notEmpty{%ndc }"; + } + pattern += "- %m%n"; + appenderBuilder.add(newPatternLayout(pattern)); + break; + } + case "org.apache.log4j.HTMLLayout": { + final LayoutComponentBuilder htmlLayout = builder.newLayout("HtmlLayout"); + htmlLayout.addAttribute("title", getLog4jAppenderValue(name, "layout.Title", "Log4J Log Messages")); + htmlLayout.addAttribute( + "locationInfo", + Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE))); + appenderBuilder.add(htmlLayout); + break; + } + case "org.apache.log4j.xml.XMLLayout": { + final LayoutComponentBuilder xmlLayout = builder.newLayout("Log4j1XmlLayout"); + xmlLayout.addAttribute( + "locationInfo", + Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.LocationInfo", FALSE))); + xmlLayout.addAttribute( + "properties", + Boolean.parseBoolean(getLog4jAppenderValue(name, "layout.Properties", FALSE))); + appenderBuilder.add(xmlLayout); + break; + } + default: + reportWarning("Unknown layout class: " + layoutClass); + } + } + } + + private LayoutComponentBuilder newPatternLayout(final String pattern) { + final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout"); + if (pattern != null) { + layoutBuilder.addAttribute("pattern", pattern); + } + return layoutBuilder; + } + + private void buildRootLogger(final String rootLoggerValue) { + if (rootLoggerValue == null) { + return; + } + final String[] rootLoggerParts = rootLoggerValue.split(COMMA_DELIMITED_RE); + final String rootLoggerLevel = getLevelString(rootLoggerParts, Level.ERROR.name()); + final RootLoggerComponentBuilder loggerBuilder = builder.newRootLogger(rootLoggerLevel); + // + final String[] sortedAppenderNames = Arrays.copyOfRange(rootLoggerParts, 1, rootLoggerParts.length); + Arrays.sort(sortedAppenderNames); + for (final String appender : sortedAppenderNames) { + loggerBuilder.add(builder.newAppenderRef(appender)); + } + builder.add(loggerBuilder); + } + + private String getLevelString(final String[] loggerParts, final String defaultLevel) { + return loggerParts.length > 0 ? loggerParts[0] : defaultLevel; + } + + private void buildLoggers(final String prefix) { + final int preLength = prefix.length(); + for (final Map.Entry entry : properties.entrySet()) { + final Object keyObj = entry.getKey(); + if (keyObj != null) { + final String key = keyObj.toString().trim(); + if (key.startsWith(prefix)) { + final String name = key.substring(preLength); + final Object value = entry.getValue(); + if (value != null) { + // a Level may be followed by a list of Appender refs. + final String valueStr = value.toString().trim(); + final String[] split = valueStr.split(COMMA_DELIMITED_RE); + final String level = getLevelString(split, null); + if (level == null) { + warn("Level is missing for entry " + entry); + } else { + final LoggerComponentBuilder newLogger = builder.newLogger(name, level); + if (split.length > 1) { + // Add Appenders to this logger + final String[] sortedAppenderNames = Arrays.copyOfRange(split, 1, split.length); + Arrays.sort(sortedAppenderNames); + for (final String appenderName : sortedAppenderNames) { + newLogger.add(builder.newAppenderRef(appenderName)); + } + } + builder.add(newLogger); + } + } + } + } + } + } + + private String getLog4jAppenderValue(final String appenderName, final String attributeName) { + return getProperty("log4j.appender." + appenderName + "." + attributeName); + } + + private String getProperty(final String key) { + final String value = properties.getProperty(key); + final String substVars = OptionConverter.substVars(value, properties); + return substVars == null ? null : substVars.trim(); + } + + private String getProperty(final String key, final String defaultValue) { + final String value = getProperty(key); + return value == null ? defaultValue : value; + } + + private String getLog4jAppenderValue( + final String appenderName, final String attributeName, final String defaultValue) { + return getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue); + } + + private String getLog4jValue(final String key) { + return getProperty("log4j." + key); + } + + private void reportWarning(final String msg) { + StatusLogger.getLogger().warn("Log4j 1 configuration parser: " + msg); + } +} diff --git a/pom.xml b/pom.xml index e28d5d99..930ac6f0 100644 --- a/pom.xml +++ b/pom.xml @@ -73,6 +73,7 @@ log4j-transform-parent + log4j-config-converter log4j-transform-maven-plugin log4j-transform-maven-shade-plugin-extensions log4j-weaver @@ -116,6 +117,12 @@ + + org.apache.logging.log4j + log4j-config-converter + ${project.version} + + org.apache.logging.log4j log4j-transform-maven-plugin