1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.logging.log4j.core.tools;
19
20 import java.io.PrintStream;
21 import java.util.ArrayList;
22 import java.util.Arrays;
23 import java.util.List;
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 public final class Generate {
45
46 static final String PACKAGE_DECLARATION = "package %s;%n%n";
47
48 static enum Type {
49 CUSTOM {
50 @Override
51 String imports() {
52 return ""
53 + "import java.io.Serializable;%n"
54 + "import org.apache.logging.log4j.Level;%n"
55 + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n"
56 + "import org.apache.logging.log4j.Marker;%n"
57 + "import org.apache.logging.log4j.message.Message;%n"
58 + "import org.apache.logging.log4j.message.MessageFactory;%n"
59 + "import org.apache.logging.log4j.spi.AbstractLogger;%n"
60 + "import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;%n"
61 + "%n";
62 }
63
64 @Override
65 String declaration() {
66 return ""
67 + "/**%n"
68 + " * Custom Logger interface with convenience methods for%n"
69 + " * %s%n"
70 + " */%n"
71 + "public final class %s implements Serializable {%n"
72 + " private static final long serialVersionUID = " + System.nanoTime() + "L;%n"
73 + " private final ExtendedLoggerWrapper logger;%n"
74 + "%n"
75 ;
76 }
77
78 @Override
79 String constructor() {
80 return ""
81 + "%n"
82 + " private %s(final Logger logger) {%n"
83 + " this.logger = new ExtendedLoggerWrapper((AbstractLogger) logger, logger.getName(), logger.getMessageFactory());%n"
84 + " }%n"
85 ;
86 }
87
88 @Override
89 Class<?> generator() {
90 return CustomLogger.class;
91 }
92 },
93 EXTEND {
94 @Override
95 String imports() {
96 return ""
97 + "import org.apache.logging.log4j.Level;%n"
98 + "import org.apache.logging.log4j.LogManager;%n" + "import org.apache.logging.log4j.Logger;%n"
99 + "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
406
407
408 public static final class CustomLogger {
409
410
411
412
413
414
415
416 public static void main(final String[] args) {
417 generate(args, Type.CUSTOM);
418 }
419
420 private CustomLogger() {
421 }
422 }
423
424
425
426
427
428
429 public static final class ExtendedLogger {
430
431
432
433
434
435
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 }