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 018package org.apache.logging.log4j.core.tools; 019 020import java.io.PrintStream; 021import java.util.ArrayList; 022import java.util.Arrays; 023import 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 */ 044public 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}