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    }