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}