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 */
017
018 package org.apache.logging.log4j.core.tools;
019
020 import java.io.PrintStream;
021 import java.util.ArrayList;
022 import java.util.Arrays;
023 import java.util.List;
024
025 /**
026 * Generates source code for custom or extended logger wrappers.
027 * <p>
028 * Usage:
029 * <p>
030 * To generate source code for an extended logger that adds custom log levels to the existing ones: <br>
031 * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
032 * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
033 * <p>
034 * Example of creating an extended logger:<br>
035 * {@code java org.apache.logging.log4j.core.tools.Generate$ExtendedLogger com.mycomp.ExtLogger DIAG=350 NOTICE=450 VERBOSE=550}
036 * <p>
037 * To generate source code for a custom logger that replaces the existing log levels with custom ones: <br>
038 * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger <logger.class.name> <CUSTOMLEVEL>=<WEIGHT>
039 * [CUSTOMLEVEL2=WEIGHT2 [CUSTOMLEVEL3=WEIGHT3] ...]}
040 * <p>
041 * Example of creating a custom logger:<br>
042 * {@code java org.apache.logging.log4j.core.tools.Generate$CustomLogger com.mycomp.MyLogger DEFCON1=350 DEFCON2=450 DEFCON3=550}
043 */
044 public final class Generate {
045
046 static final String PACKAGE_DECLARATION = "package %s;%n%n";
047
048 static enum Type {
049 CUSTOM {
050 @Override
051 String imports() {
052 return "" //
053 + "import java.io.Serializable;%n" //
054 + "import org.apache.logging.log4j.Level;%n" //
055 + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n" //
056 + "import org.apache.logging.log4j.Marker;%n" //
057 + "import org.apache.logging.log4j.message.Message;%n" //
058 + "import org.apache.logging.log4j.message.MessageFactory;%n" //
059 + "import org.apache.logging.log4j.spi.AbstractLogger;%n" //
060 + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" //
061 + "%n";
062 }
063
064 @Override
065 String declaration() {
066 return "" //
067 + "/**%n" //
068 + " * Custom Logger interface with convenience methods for%n" //
069 + " * %s%n" //
070 + " */%n" //
071 + "public final class %s implements Serializable {%n" //
072 + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n" //
073 + " private final ExtendedLoggerWrapper logger;%n" //
074 + "%n" //
075 ;
076 }
077
078 @Override
079 String constructor() {
080 return "" //
081 + "%n" //
082 + " private %s(final Logger logger) {%n" //
083 + " this.logger = new ExtendedLoggerWrapper((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n" //
084 + " }%n" //
085 ;
086 }
087
088 @Override
089 Class<?> generator() {
090 return CustomLogger.class;
091 }
092 },
093 EXTEND {
094 @Override
095 String imports() {
096 return "" //
097 + "import org.apache.logging.log4j.Level;%n" //
098 + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n" //
099 + "import org.apache.logging.log4j.Marker;%n" //
100 + "import org.apache.logging.log4j.message.Message;%n" //
101 + "import org.apache.logging.log4j.message.MessageFactory;%n" //
102 + "import org.apache.logging.log4j.spi.AbstractLogger;%n" //
103 + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n" //
104 + "%n";
105 }
106
107 @Override
108 String declaration() {
109 return "" //
110 + "/**%n" //
111 + " * Extended Logger interface with convenience methods for%n" //
112 + " * %s%n" //
113 + " */%n" //
114 + "public final class %s extends ExtendedLoggerWrapper {%n" //
115 + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n" //
116 + " private final ExtendedLoggerWrapper logger;%n" //
117 + "%n" //
118 ;
119 }
120
121 @Override
122 String constructor() {
123 return "" //
124 + "%n" //
125 + " private %s(final Logger logger) {%n" //
126 + " super((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n" //
127 + " this.logger = this;%n" //
128 + " }%n" //
129 ;
130 }
131
132 @Override
133 Class<?> generator() {
134 return ExtendedLogger.class;
135 }
136 };
137 abstract String imports();
138
139 abstract String declaration();
140
141 abstract String constructor();
142
143 abstract Class<?> generator();
144 }
145
146 static final String FQCN_FIELD = "" //
147 + " private static final String FQCN = %s.class.getName();%n";
148
149 static final String LEVEL_FIELD = "" //
150 + " private static final Level %s = Level.forName(\"%s\", %d);%n";
151
152 static final String FACTORY_METHODS = "" //
153 + "%n" //
154 + " /**%n" //
155 + " * Returns a custom Logger with the name of the calling class.%n" //
156 + " * %n" //
157 + " * @return The custom Logger for the calling class.%n" //
158 + " */%n" //
159 + " public static CLASSNAME create() {%n" //
160 + " final Logger wrapped = LogManager.getLogger();%n" //
161 + " return new CLASSNAME(wrapped);%n" //
162 + " }%n" //
163 + "%n" //
164 + " /**%n" //
165 + " * Returns a custom Logger using the fully qualified name of the Class as%n" //
166 + " * the Logger name.%n" //
167 + " * %n" //
168 + " * @param loggerName The Class whose name should be used as the Logger name.%n" //
169 + " * If null it will default to the calling class.%n" //
170 + " * @return The custom Logger.%n" //
171 + " */%n" //
172 + " public static CLASSNAME create(final Class<?> loggerName) {%n" //
173 + " final Logger wrapped = LogManager.getLogger(loggerName);%n" //
174 + " return new CLASSNAME(wrapped);%n" //
175 + " }%n" //
176 + "%n" //
177 + " /**%n" //
178 + " * Returns a custom Logger using the fully qualified name of the Class as%n" //
179 + " * the Logger name.%n" //
180 + " * %n" //
181 + " * @param loggerName The Class whose name should be used as the Logger name.%n" //
182 + " * If null it will default to the calling class.%n" //
183 + " * @param messageFactory The message factory is used only when creating a%n" //
184 + " * logger, subsequent use does not change the logger but will log%n" //
185 + " * a warning if mismatched.%n" //
186 + " * @return The custom Logger.%n" //
187 + " */%n" //
188 + " public static CLASSNAME create(final Class<?> loggerName, final MessageFactory factory) {%n" //
189 + " final Logger wrapped = LogManager.getLogger(loggerName, factory);%n" //
190 + " return new CLASSNAME(wrapped);%n" //
191 + " }%n" //
192 + "%n" //
193 + " /**%n" //
194 + " * Returns a custom Logger using the fully qualified class name of the value%n" //
195 + " * as the Logger name.%n" //
196 + " * %n" //
197 + " * @param value The value whose class name should be used as the Logger%n" //
198 + " * name. If null the name of the calling class will be used as%n" //
199 + " * the logger name.%n" //
200 + " * @return The custom Logger.%n" //
201 + " */%n" //
202 + " public static CLASSNAME create(final Object value) {%n" //
203 + " final Logger wrapped = LogManager.getLogger(value);%n" //
204 + " return new CLASSNAME(wrapped);%n" //
205 + " }%n" //
206 + "%n" //
207 + " /**%n" //
208 + " * Returns a custom Logger using the fully qualified class name of the value%n" //
209 + " * as the Logger name.%n" //
210 + " * %n" //
211 + " * @param value The value whose class name should be used as the Logger%n" //
212 + " * name. If null the name of the calling class will be used as%n" //
213 + " * the logger name.%n" //
214 + " * @param messageFactory The message factory is used only when creating a%n" //
215 + " * logger, subsequent use does not change the logger but will log%n" //
216 + " * a warning if mismatched.%n" //
217 + " * @return The custom Logger.%n" //
218 + " */%n" //
219 + " public static CLASSNAME create(final Object value, final MessageFactory factory) {%n" //
220 + " final Logger wrapped = LogManager.getLogger(value, factory);%n" //
221 + " return new CLASSNAME(wrapped);%n" //
222 + " }%n" //
223 + "%n" //
224 + " /**%n" //
225 + " * Returns a custom Logger with the specified name.%n" //
226 + " * %n" //
227 + " * @param name The logger name. If null the name of the calling class will%n" //
228 + " * be used.%n" //
229 + " * @return The custom Logger.%n" //
230 + " */%n" //
231 + " public static CLASSNAME create(final String name) {%n" //
232 + " final Logger wrapped = LogManager.getLogger(name);%n" //
233 + " return new CLASSNAME(wrapped);%n" //
234 + " }%n" //
235 + "%n" //
236 + " /**%n" //
237 + " * Returns a custom Logger with the specified name.%n" //
238 + " * %n" //
239 + " * @param name The logger name. If null the name of the calling class will%n" //
240 + " * be used.%n" //
241 + " * @param messageFactory The message factory is used only when creating a%n" //
242 + " * logger, subsequent use does not change the logger but will log%n" //
243 + " * a warning if mismatched.%n" //
244 + " * @return The custom Logger.%n" //
245 + " */%n" //
246 + " public static CLASSNAME create(final String name, final MessageFactory factory) {%n" //
247 + " final Logger wrapped = LogManager.getLogger(name, factory);%n" //
248 + " return new CLASSNAME(wrapped);%n" //
249 + " }%n" //
250 ;
251 static final String METHODS = "" //
252 + "%n" //
253 + " /**%n" //
254 + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" //
255 + " * %n" //
256 + " * @param marker the marker data specific to this log statement%n" //
257 + " * @param msg the message string to be logged%n" //
258 + " */%n" //
259 + " public void methodName(final Marker marker, final Message msg) {%n" //
260 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, (Throwable) null);%n" //
261 + " }%n" //
262 + "%n" //
263 + " /**%n" //
264 + " * Logs a message with the specific Marker at the {@code CUSTOM_LEVEL} level.%n" //
265 + " * %n" //
266 + " * @param marker the marker data specific to this log statement%n" //
267 + " * @param msg the message string to be logged%n" //
268 + " * @param t A Throwable or null.%n" //
269 + " */%n" //
270 + " public void methodName(final Marker marker, final Message msg, final Throwable t) {%n" //
271 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, msg, t);%n" //
272 + " }%n" //
273 + "%n" //
274 + " /**%n" //
275 + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
276 + " * %n" //
277 + " * @param marker the marker data specific to this log statement%n" //
278 + " * @param message the message object to log.%n" //
279 + " */%n" //
280 + " public void methodName(final Marker marker, final Object message) {%n" //
281 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" //
282 + " }%n" //
283 + "%n" //
284 + " /**%n" //
285 + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
286 + " * the {@link Throwable} {@code t} passed as parameter.%n" //
287 + " * %n" //
288 + " * @param marker the marker data specific to this log statement%n" //
289 + " * @param message the message to log.%n" //
290 + " * @param t the exception to log, including its stack trace.%n" //
291 + " */%n" //
292 + " public void methodName(final Marker marker, final Object message, final Throwable t) {%n" //
293 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" //
294 + " }%n" //
295 + "%n" //
296 + " /**%n" //
297 + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
298 + " * %n" //
299 + " * @param marker the marker data specific to this log statement%n" //
300 + " * @param message the message object to log.%n" //
301 + " */%n" //
302 + " public void methodName(final Marker marker, final String message) {%n" //
303 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, (Throwable) null);%n" //
304 + " }%n" //
305 + "%n" //
306 + " /**%n" //
307 + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" //
308 + " * %n" //
309 + " * @param marker the marker data specific to this log statement%n" //
310 + " * @param message the message to log; the format depends on the message factory.%n" //
311 + " * @param params parameters to the message.%n" //
312 + " * @see #getMessageFactory()%n" //
313 + " */%n" //
314 + " public void methodName(final Marker marker, final String message, final Object... params) {%n" //
315 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, params);%n" //
316 + " }%n" //
317 + "%n" //
318 + " /**%n" //
319 + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
320 + " * the {@link Throwable} {@code t} passed as parameter.%n" //
321 + " * %n" //
322 + " * @param marker the marker data specific to this log statement%n" //
323 + " * @param message the message to log.%n" //
324 + " * @param t the exception to log, including its stack trace.%n" //
325 + " */%n" //
326 + " public void methodName(final Marker marker, final String message, final Throwable t) {%n" //
327 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, marker, message, t);%n" //
328 + " }%n" //
329 + "%n" //
330 + " /**%n" //
331 + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" //
332 + " * %n" //
333 + " * @param msg the message string to be logged%n" //
334 + " */%n" //
335 + " public void methodName(final Message msg) {%n" //
336 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, (Throwable) null);%n" //
337 + " }%n" //
338 + "%n" //
339 + " /**%n" //
340 + " * Logs the specified Message at the {@code CUSTOM_LEVEL} level.%n" //
341 + " * %n" //
342 + " * @param msg the message string to be logged%n" //
343 + " * @param t A Throwable or null.%n" //
344 + " */%n" //
345 + " public void methodName(final Message msg, final Throwable t) {%n" //
346 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, msg, t);%n" //
347 + " }%n" //
348 + "%n" //
349 + " /**%n" //
350 + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
351 + " * %n" //
352 + " * @param message the message object to log.%n" //
353 + " */%n" //
354 + " public void methodName(final Object message) {%n" //
355 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" //
356 + " }%n" //
357 + "%n" //
358 + " /**%n" //
359 + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
360 + " * the {@link Throwable} {@code t} passed as parameter.%n" //
361 + " * %n" //
362 + " * @param message the message to log.%n" //
363 + " * @param t the exception to log, including its stack trace.%n" //
364 + " */%n" //
365 + " public void methodName(final Object message, final Throwable t) {%n" //
366 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" //
367 + " }%n" //
368 + "%n" //
369 + " /**%n" //
370 + " * Logs a message object with the {@code CUSTOM_LEVEL} level.%n" //
371 + " * %n" //
372 + " * @param message the message object to log.%n" //
373 + " */%n" //
374 + " public void methodName(final String message) {%n" //
375 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, (Throwable) null);%n" //
376 + " }%n" //
377 + "%n" //
378 + " /**%n" //
379 + " * Logs a message with parameters at the {@code CUSTOM_LEVEL} level.%n" //
380 + " * %n" //
381 + " * @param message the message to log; the format depends on the message factory.%n" //
382 + " * @param params parameters to the message.%n" //
383 + " * @see #getMessageFactory()%n" //
384 + " */%n" //
385 + " public void methodName(final String message, final Object... params) {%n" //
386 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, params);%n" //
387 + " }%n" //
388 + "%n" //
389 + " /**%n" //
390 + " * Logs a message at the {@code CUSTOM_LEVEL} level including the stack trace of%n" //
391 + " * the {@link Throwable} {@code t} passed as parameter.%n" //
392 + " * %n" //
393 + " * @param message the message to log.%n" //
394 + " * @param t the exception to log, including its stack trace.%n" //
395 + " */%n" //
396 + " public void methodName(final String message, final Throwable t) {%n" //
397 + " logger.logIfEnabled(FQCN, CUSTOM_LEVEL, null, message, t);%n" //
398 + " }%n" //
399 ;
400
401 private Generate() {
402 }
403
404 /**
405 * Generates source code for custom logger wrappers that only provide convenience methods for the specified custom
406 * levels, not for the standard built-in levels.
407 */
408 public static final class CustomLogger {
409 /**
410 * Generates source code for custom logger wrappers that only provide convenience methods for the specified
411 * custom levels, not for the standard built-in levels.
412 *
413 * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
414 * level to generate convenience methods for
415 */
416 public static void main(final String[] args) {
417 generate(args, Type.CUSTOM);
418 }
419
420 private CustomLogger() {
421 }
422 }
423
424 /**
425 * Generates source code for extended logger wrappers that provide convenience methods for the specified custom
426 * levels, and by extending {@code org.apache.logging.log4j.spi.ExtendedLoggerWrapper}, inherit the convenience
427 * methods for the built-in levels provided by the {@code Logger} interface.
428 */
429 public static final class ExtendedLogger {
430 /**
431 * Generates source code for extended logger wrappers that provide convenience methods for the specified custom
432 * levels.
433 *
434 * @param args className of the custom logger to generate, followed by a NAME=intLevel pair for each custom log
435 * level to generate convenience methods for
436 */
437 public static void main(final String[] args) {
438 generate(args, Type.EXTEND);
439 }
440
441 private ExtendedLogger() {
442 }
443 }
444
445 static class LevelInfo {
446 final String name;
447 final int intLevel;
448
449 LevelInfo(final String description) {
450 final String[] parts = description.split("=");
451 name = parts[0];
452 intLevel = Integer.parseInt(parts[1]);
453 }
454
455 public static List<LevelInfo> parse(final List<String> values, final Class<?> generator) {
456 final List<LevelInfo> result = new ArrayList<Generate.LevelInfo>(values.size());
457 for (int i = 0; i < values.size(); i++) {
458 try {
459 result.add(new LevelInfo(values.get(i)));
460 } catch (final Exception ex) {
461 System.err.println("Cannot parse custom level '" + values.get(i) + "': " + ex.toString());
462 usage(System.err, generator);
463 System.exit(-1);
464 }
465 }
466 return result;
467 }
468 }
469
470 private static void generate(final String[] args, final Type type) {
471 generate(args, type, System.out);
472 }
473
474 static void generate(final String[] args, final Type type, final PrintStream printStream) {
475 if (!validate(args)) {
476 usage(printStream, type.generator());
477 System.exit(-1);
478 }
479 final List<String> values = new ArrayList<String>(Arrays.asList(args));
480 final String classFQN = values.remove(0);
481 final List<LevelInfo> levels = LevelInfo.parse(values, type.generator());
482 printStream.println(generateSource(classFQN, levels, type));
483 }
484
485 static boolean validate(final String[] args) {
486 if (args.length < 2) {
487 return false;
488 }
489 return true;
490 }
491
492 private static void usage(final PrintStream out, final Class<?> generator) {
493 out.println("Usage: java " + generator.getName() + " className LEVEL1=intLevel1 [LEVEL2=intLevel2...]");
494 out.println(" Where className is the fully qualified class name of the custom/extended logger to generate,");
495 out.println(" followed by a space-separated list of custom log levels.");
496 out.println(" For each custom log level, specify NAME=intLevel (without spaces).");
497 }
498
499 static String generateSource(final String classNameFQN, final List<LevelInfo> levels, final Type type) {
500 final StringBuilder sb = new StringBuilder(10000 * levels.size());
501 final int lastDot = classNameFQN.lastIndexOf('.');
502 final String pkg = classNameFQN.substring(0, lastDot >= 0 ? lastDot : 0);
503 if (!pkg.isEmpty()) {
504 sb.append(String.format(PACKAGE_DECLARATION, pkg));
505 }
506 sb.append(String.format(type.imports(), ""));
507 final String className = classNameFQN.substring(classNameFQN.lastIndexOf('.') + 1);
508 final String javadocDescr = javadocDescription(levels);
509 sb.append(String.format(type.declaration(), javadocDescr, className));
510 sb.append(String.format(FQCN_FIELD, className));
511 for (final LevelInfo level : levels) {
512 sb.append(String.format(LEVEL_FIELD, level.name, level.name, level.intLevel));
513 }
514 sb.append(String.format(type.constructor(), className));
515 sb.append(String.format(FACTORY_METHODS.replaceAll("CLASSNAME", className), ""));
516 for (final LevelInfo level : levels) {
517 final String methodName = camelCase(level.name);
518 final String phase1 = METHODS.replaceAll("CUSTOM_LEVEL", level.name);
519 final String phase2 = phase1.replaceAll("methodName", methodName);
520 sb.append(String.format(phase2, ""));
521 }
522
523 sb.append(String.format("}%n", ""));
524 return sb.toString();
525 }
526
527 static String javadocDescription(final List<LevelInfo> levels) {
528 if (levels.size() == 1) {
529 return "the " + levels.get(0).name + " custom log level.";
530 }
531 final StringBuilder sb = new StringBuilder(512);
532 sb.append("the ");
533 String sep = "";
534 for (int i = 0; i < levels.size(); i++) {
535 sb.append(sep);
536 sb.append(levels.get(i).name);
537 sep = (i == levels.size() - 2) ? " and " : ", ";
538 }
539 sb.append(" custom log levels.");
540 return sb.toString();
541 }
542
543 static String camelCase(final String customLevel) {
544 final StringBuilder sb = new StringBuilder(customLevel.length());
545 boolean lower = true;
546 for (final char ch : customLevel.toCharArray()) {
547 if (ch == '_') {
548 lower = false;
549 continue;
550 }
551 sb.append(lower ? Character.toLowerCase(ch) : Character.toUpperCase(ch));
552 lower = true;
553 }
554 return sb.toString();
555 }
556 }