001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017package org.apache.log4j.config; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.nio.file.FileVisitResult; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.nio.file.SimpleFileVisitor; 026import java.nio.file.attribute.BasicFileAttributes; 027import java.util.concurrent.atomic.AtomicInteger; 028 029import org.apache.logging.log4j.core.config.ConfigurationException; 030import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder; 031import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration; 032import org.apache.logging.log4j.core.tools.BasicCommandLineArguments; 033import org.apache.logging.log4j.core.tools.picocli.CommandLine; 034import org.apache.logging.log4j.core.tools.picocli.CommandLine.Command; 035import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option; 036 037/** 038 * Tool for converting a Log4j 1.x properties configuration file to Log4j 2.x XML configuration file. 039 * 040 * <p> 041 * Run with "--help" on the command line. 042 * </p> 043 * 044 * <p> 045 * Example: 046 * </p> 047 * 048 * <pre> 049 * java org.apache.log4j.config.Log4j1ConfigurationConverter --recurse 050 * E:\vcs\git\apache\logging\logging-log4j2\log4j-1.2-api\src\test\resources\config-1.2\hadoop --in log4j.properties --verbose 051 * </pre> 052 */ 053public final class Log4j1ConfigurationConverter { 054 055 @Command(name = "Log4j1ConfigurationConverter") 056 public static class CommandLineArguments extends BasicCommandLineArguments implements Runnable { 057 058 @Option(names = { "--failfast", "-f" }, description = "Fails on the first failure in recurse mode.") 059 private boolean failFast; 060 061 @Option(names = { "--in", "-i" }, description = "Specifies the input file.") 062 private Path pathIn; 063 064 @Option(names = { "--out", "-o" }, description = "Specifies the output file.") 065 private Path pathOut; 066 067 @Option(names = { "--recurse", "-r" }, description = "Recurses into this folder looking for the input file") 068 private Path recurseIntoPath; 069 070 @Option(names = { "--verbose", "-v" }, description = "Be verbose.") 071 private boolean verbose; 072 073 public Path getPathIn() { 074 return pathIn; 075 } 076 077 public Path getPathOut() { 078 return pathOut; 079 } 080 081 public Path getRecurseIntoPath() { 082 return recurseIntoPath; 083 } 084 085 public boolean isFailFast() { 086 return failFast; 087 } 088 089 public boolean isVerbose() { 090 return verbose; 091 } 092 093 public void setFailFast(final boolean failFast) { 094 this.failFast = failFast; 095 } 096 097 public void setPathIn(final Path pathIn) { 098 this.pathIn = pathIn; 099 } 100 101 public void setPathOut(final Path pathOut) { 102 this.pathOut = pathOut; 103 } 104 105 public void setRecurseIntoPath(final Path recurseIntoPath) { 106 this.recurseIntoPath = recurseIntoPath; 107 } 108 109 public void setVerbose(final boolean verbose) { 110 this.verbose = verbose; 111 } 112 113 @Override 114 public void run() { 115 if (isHelp()) { 116 CommandLine.usage(this, System.err); 117 return; 118 } 119 new Log4j1ConfigurationConverter(this).run(); 120 } 121 122 @Override 123 public String toString() { 124 return "CommandLineArguments [recurseIntoPath=" + recurseIntoPath + ", verbose=" + verbose + ", pathIn=" 125 + pathIn + ", pathOut=" + pathOut + "]"; 126 } 127 } 128 129 private static final String FILE_EXT_XML = ".xml"; 130 131 public static void main(final String[] args) { 132 CommandLine.run(new CommandLineArguments(), System.err, args); 133 } 134 135 public static Log4j1ConfigurationConverter run(final CommandLineArguments cla) { 136 final Log4j1ConfigurationConverter log4j1ConfigurationConverter = new Log4j1ConfigurationConverter(cla); 137 log4j1ConfigurationConverter.run(); 138 return log4j1ConfigurationConverter; 139 } 140 141 private final CommandLineArguments cla; 142 143 private Log4j1ConfigurationConverter(final CommandLineArguments cla) { 144 this.cla = cla; 145 } 146 147 protected void convert(final InputStream input, final OutputStream output) throws IOException { 148 final ConfigurationBuilder<BuiltConfiguration> builder = new Log4j1ConfigurationParser() 149 .buildConfigurationBuilder(input); 150 builder.writeXmlConfiguration(output); 151 } 152 153 InputStream getInputStream() throws IOException { 154 final Path pathIn = cla.getPathIn(); 155 return pathIn == null ? System.in : new InputStreamWrapper(Files.newInputStream(pathIn), pathIn.toString()); 156 } 157 158 OutputStream getOutputStream() throws IOException { 159 final Path pathOut = cla.getPathOut(); 160 return pathOut == null ? System.out : Files.newOutputStream(pathOut); 161 } 162 163 private void run() { 164 if (cla.getRecurseIntoPath() != null) { 165 final AtomicInteger countOKs = new AtomicInteger(); 166 final AtomicInteger countFails = new AtomicInteger(); 167 try { 168 Files.walkFileTree(cla.getRecurseIntoPath(), new SimpleFileVisitor<Path>() { 169 @Override 170 public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) 171 throws IOException { 172 if (cla.getPathIn() == null || file.getFileName().equals(cla.getPathIn())) { 173 verbose("Reading %s", file); 174 String newFile = file.getFileName().toString(); 175 final int lastIndex = newFile.lastIndexOf("."); 176 newFile = lastIndex < 0 ? newFile + FILE_EXT_XML 177 : newFile.substring(0, lastIndex) + FILE_EXT_XML; 178 final Path resolved = file.resolveSibling(newFile); 179 try (final InputStream input = new InputStreamWrapper(Files.newInputStream(file), file.toString()); 180 final OutputStream output = Files.newOutputStream(resolved)) { 181 try { 182 convert(input, output); 183 countOKs.incrementAndGet(); 184 } catch (ConfigurationException | IOException e) { 185 countFails.incrementAndGet(); 186 if (cla.isFailFast()) { 187 throw e; 188 } 189 e.printStackTrace(); 190 } 191 verbose("Wrote %s", resolved); 192 } 193 } 194 return FileVisitResult.CONTINUE; 195 } 196 }); 197 } catch (final IOException e) { 198 throw new ConfigurationException(e); 199 } finally { 200 verbose("OK = %,d, Failures = %,d, Total = %,d", countOKs.get(), countFails.get(), 201 countOKs.get() + countFails.get()); 202 } 203 } else { 204 verbose("Reading %s", cla.getPathIn()); 205 try (final InputStream input = getInputStream(); final OutputStream output = getOutputStream()) { 206 convert(input, output); 207 } catch (final IOException e) { 208 throw new ConfigurationException(e); 209 } 210 verbose("Wrote %s", cla.getPathOut()); 211 } 212 } 213 214 private void verbose(final String template, final Object... args) { 215 if (cla.isVerbose()) { 216 System.err.println(String.format(template, args)); 217 } 218 } 219 220}