001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *      http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.oozie.util;
020
021
022import org.apache.commons.logging.Log;
023import org.apache.commons.logging.LogFactory;
024
025import java.text.MessageFormat;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031/**
032 * The <code>XLog</code> class extends the functionality of the Apache common-logging <code>Log</code> interface. <p>
033 * It provides common prefix support, message templating with variable parameters and selective tee logging to multiple
034 * logs. <p> It provides also the LogFactory functionality.
035 */
036public class XLog implements Log {
037
038    public static final String INSTRUMENTATION_LOG_NAME = "oozieinstrumentation";
039
040    /**
041     * <code>LogInfo</code> stores contextual information to create log prefixes. <p> <code>LogInfo</code> uses a
042     * <code>ThreadLocal</code> to propagate the context. <p> <code>LogInfo</code> context parameters are configurable
043     * singletons.
044     */
045    public static class Info {
046        private static String template = "";
047        private String prefix = "";
048        private static List<String> parameterNames = new ArrayList<String>();
049
050        private static ThreadLocal<Info> tlLogInfo = new ThreadLocal<Info>() {
051            @Override
052            protected Info initialValue() {
053                return new Info();
054            }
055
056        };
057
058        /**
059         * Define a <code>LogInfo</code> context parameter. <p> The parameter name and its contextual value will be
060         * used to create all prefixes.
061         *
062         * @param name name of the context parameter.
063         */
064        public static void defineParameter(String name) {
065            ParamChecker.notEmpty(name, "name");
066            int count = parameterNames.size();
067            if (count > 0) {
068                template += " ";
069            }
070            template += name + "[{" + count + "}]";
071            parameterNames.add(name);
072        }
073
074        /**
075         * Remove all defined context parameters.
076         */
077        public static void reset() {
078            template = "";
079            parameterNames.clear();
080        }
081
082        /**
083         * Return the <code>LogInfo</code> instance in context.
084         *
085         * @return The thread local instance of LogInfo
086         */
087        public static Info get() {
088            return tlLogInfo.get();
089        }
090
091        /**
092         * Remove the <code>LogInfo</code> instance in context.
093         */
094        public static void remove() {
095            tlLogInfo.remove();
096        }
097
098        private Map<String, String> parameters = new HashMap<String, String>();
099
100        /**
101         * Constructs an empty LogInfo.
102         */
103        public Info() {
104        }
105
106
107        /**
108         * Construct a new LogInfo object from an existing one.
109         *
110         * @param logInfo LogInfo object to copy parameters from.
111         */
112        public Info(Info logInfo) {
113            setParameters(logInfo);
114        }
115
116        /**
117         * Clear all parameters set in this logInfo instance.
118         */
119        public void clear() {
120            parameters.clear();
121            resetPrefix();
122        }
123
124        /**
125         * Set a parameter value in the <code>LogInfo</code> context.
126         *
127         * @param name parameter name.
128         * @param value parameter value.
129         */
130        public void setParameter(String name, String value) {
131            if (!parameterNames.contains(name)) {
132                throw new IllegalArgumentException(format("Parameter[{0}] not defined", name));
133            }
134            parameters.put(name, value);
135        }
136
137        /**
138         * Returns the specified parameter.
139         *
140         * @param name parameter name.
141         * @return the parameter value.
142         */
143        public String getParameter(String name) {
144            return parameters.get(name);
145        }
146
147        /**
148         * Clear a parameter value from the <code>LogInfo</code> context.
149         *
150         * @param name parameter name.
151         */
152        public void clearParameter(String name) {
153            if (!parameterNames.contains(name)) {
154                throw new IllegalArgumentException(format("Parameter[{0}] not defined", name));
155            }
156            parameters.remove(name);
157        }
158
159        /**
160         * Set all the parameter values from the given <code>LogInfo</code>.
161         *
162         * @param logInfo <code>LogInfo</code> to copy all parameter values from.
163         */
164        public void setParameters(Info logInfo) {
165            parameters.clear();
166            parameters.putAll(logInfo.parameters);
167        }
168
169        /**
170         * Create the <code>LogInfo</code> prefix using the current parameter values.
171         *
172         * @return the <code>LogInfo</code> prefix.
173         */
174        public String createPrefix() {
175            String[] params = new String[parameterNames.size()];
176            for (int i = 0; i < params.length; i++) {
177                params[i] = parameters.get(parameterNames.get(i));
178                if (params[i] == null) {
179                    params[i] = "-";
180                }
181            }
182            return MessageFormat.format(template, (Object[]) params);
183        }
184
185        public String resetPrefix() {
186            return prefix = createPrefix();
187        }
188
189        public String getPrefix() {
190            return prefix;
191        }
192
193
194    }
195
196    /**
197     * Return the named logger.
198     *
199     * @param name logger name.
200     * @return the named logger.
201     */
202    public static XLog getLog(String name) {
203        return new XLog(LogFactory.getLog(name));
204    }
205
206    /**
207     * Return the named logger.
208     *
209     * @param clazz from which the logger name will be derived.
210     * @return the named logger.
211     */
212    public static XLog getLog(Class clazz) {
213        return new XLog(LogFactory.getLog(clazz));
214    }
215
216    /**
217     * Reset the logger prefix
218     *
219     * @param log the named logger
220     * @return the named logger with reset prefix
221     */
222    public static XLog resetPrefix(XLog log) {
223        log.setMsgPrefix(Info.get().createPrefix());
224        return log;
225    }
226
227    /**
228     * Mask for logging to the standard log.
229     */
230    public static final int STD = 1;
231
232    /**
233     * Mask for tee logging to the OPS log.
234     */
235    public static final int OPS = 4;
236
237    private static final int ALL = STD | OPS;
238
239    private static final int[] LOGGER_MASKS = {STD, OPS};
240
241    //package private for testing purposes.
242    Log[] loggers;
243
244    private String prefix = null;
245
246    /**
247     * Create a <code>XLog</code> with no prefix.
248     *
249     * @param log Log instance to use for logging.
250     */
251    public XLog(Log log) {
252        loggers = new Log[2];
253        loggers[0] = log;
254        loggers[1] = LogFactory.getLog("oozieops");
255    }
256
257    /**
258     * Return the common prefix.
259     *
260     * @return the common prefix.
261     */
262    public String getMsgPrefix() {
263        return prefix;
264    }
265
266    /**
267     * Set the common prefix.
268     *
269     * @param prefix the common prefix to set.
270     */
271    public void setMsgPrefix(String prefix) {
272        this.prefix = prefix;
273    }
274
275    //All the methods from the commonsLogging Log interface will log to the default logger only.
276
277    /**
278     * Log a debug message to the common <code>Log</code>.
279     *
280     * @param o message.
281     */
282    @Override
283    public void debug(Object o) {
284        log(Level.DEBUG, STD, "{0}", o);
285    }
286
287    /**
288     * Log a debug message and <code>Exception</code> to the common <code>Log</code>.
289     *
290     * @param o message.
291     * @param throwable exception.
292     */
293    @Override
294    public void debug(Object o, Throwable throwable) {
295        log(Level.DEBUG, STD, "{0}", o, throwable);
296    }
297
298    /**
299     * Log a error message to the common <code>Log</code>.
300     *
301     * @param o message.
302     */
303    @Override
304    public void error(Object o) {
305        log(Level.ERROR, STD, "{0}", o);
306    }
307
308    /**
309     * Log a error message and <code>Exception</code> to the common <code>Log</code>.
310     *
311     * @param o message.
312     * @param throwable exception.
313     */
314    @Override
315    public void error(Object o, Throwable throwable) {
316        log(Level.ERROR, STD, "{0}", o, throwable);
317    }
318
319    /**
320     * Log a fatal message to the common <code>Log</code>.
321     *
322     * @param o message.
323     */
324    @Override
325    public void fatal(Object o) {
326        log(Level.FATAL, STD, "{0}", o);
327    }
328
329    /**
330     * Log a fatal message and <code>Exception</code> to the common <code>Log</code>.
331     *
332     * @param o message.
333     * @param throwable exception.
334     */
335    @Override
336    public void fatal(Object o, Throwable throwable) {
337        log(Level.FATAL, STD, "{0}", o, throwable);
338    }
339
340    /**
341     * Log a info message to the common <code>Log</code>.
342     *
343     * @param o message.
344     */
345    @Override
346    public void info(Object o) {
347        log(Level.INFO, STD, "{0}", o);
348    }
349
350    /**
351     * Log a info message and <code>Exception</code> to the common <code>Log</code>.
352     *
353     * @param o message.
354     * @param throwable exception.
355     */
356    @Override
357    public void info(Object o, Throwable throwable) {
358        log(Level.INFO, STD, "{0}", o, throwable);
359    }
360
361    /**
362     * Log a trace message to the common <code>Log</code>.
363     *
364     * @param o message.
365     */
366    @Override
367    public void trace(Object o) {
368        log(Level.TRACE, STD, "{0}", o);
369    }
370
371    /**
372     * Log a trace message and <code>Exception</code> to the common <code>Log</code>.
373     *
374     * @param o message.
375     * @param throwable exception.
376     */
377    @Override
378    public void trace(Object o, Throwable throwable) {
379        log(Level.TRACE, STD, "{0}", o, throwable);
380    }
381
382    /**
383     * Log a warn message to the common <code>Log</code>.
384     *
385     * @param o message.
386     */
387    @Override
388    public void warn(Object o) {
389        log(Level.WARN, STD, "{0}", o);
390    }
391
392    /**
393     * Log a warn message and <code>Exception</code> to the common <code>Log</code>.
394     *
395     * @param o message.
396     * @param throwable exception.
397     */
398    @Override
399    public void warn(Object o, Throwable throwable) {
400        log(Level.WARN, STD, "{0}", o, throwable);
401    }
402
403    /**
404     * Return if debug logging is enabled.
405     *
406     * @return <code>true</code> if debug logging is enable, <code>false</code> if not.
407     */
408    @Override
409    public boolean isDebugEnabled() {
410        return isEnabled(Level.DEBUG, ALL);
411    }
412
413    /**
414     * Return if error logging is enabled.
415     *
416     * @return <code>true</code> if error logging is enable, <code>false</code> if not.
417     */
418    @Override
419    public boolean isErrorEnabled() {
420        return isEnabled(Level.ERROR, ALL);
421    }
422
423    /**
424     * Return if fatal logging is enabled.
425     *
426     * @return <code>true</code> if fatal logging is enable, <code>false</code> if not.
427     */
428    @Override
429    public boolean isFatalEnabled() {
430        return isEnabled(Level.FATAL, ALL);
431    }
432
433    /**
434     * Return if info logging is enabled.
435     *
436     * @return <code>true</code> if info logging is enable, <code>false</code> if not.
437     */
438    @Override
439    public boolean isInfoEnabled() {
440        return isEnabled(Level.INFO, ALL);
441    }
442
443    /**
444     * Return if trace logging is enabled.
445     *
446     * @return <code>true</code> if trace logging is enable, <code>false</code> if not.
447     */
448    @Override
449    public boolean isTraceEnabled() {
450        return isEnabled(Level.TRACE, ALL);
451    }
452
453    /**
454     * Return if warn logging is enabled.
455     *
456     * @return <code>true</code> if warn logging is enable, <code>false</code> if not.
457     */
458    @Override
459    public boolean isWarnEnabled() {
460        return isEnabled(Level.WARN, ALL);
461    }
462
463    public enum Level {
464        FATAL, ERROR, INFO, WARN, DEBUG, TRACE
465    }
466
467    private boolean isEnabled(Level level, int loggerMask) {
468        for (int i = 0; i < loggers.length; i++) {
469            if ((LOGGER_MASKS[i] & loggerMask) != 0) {
470                boolean enabled = false;
471                switch (level) {
472                    case FATAL:
473                        enabled = loggers[i].isFatalEnabled();
474                        break;
475                    case ERROR:
476                        enabled = loggers[i].isErrorEnabled();
477                        break;
478                    case INFO:
479                        enabled = loggers[i].isInfoEnabled();
480                        break;
481                    case WARN:
482                        enabled = loggers[i].isWarnEnabled();
483                        break;
484                    case DEBUG:
485                        enabled = loggers[i].isDebugEnabled();
486                        break;
487                    case TRACE:
488                        enabled = loggers[i].isTraceEnabled();
489                        break;
490                }
491                if (enabled) {
492                    return true;
493                }
494            }
495        }
496        return false;
497    }
498
499
500    private void log(Level level, int loggerMask, String msgTemplate, Object... params) {
501        loggerMask |= STD;
502        if (isEnabled(level, loggerMask)) {
503            String prefix = getMsgPrefix() != null ? getMsgPrefix() : Info.get().getPrefix();
504            prefix = (prefix != null && prefix.length() > 0) ? prefix + " " : "";
505
506            String msg = prefix + format(msgTemplate, params);
507            Throwable throwable = getCause(params);
508
509            for (int i = 0; i < LOGGER_MASKS.length; i++) {
510                if (isEnabled(level, loggerMask & LOGGER_MASKS[i])) {
511                    Log log = loggers[i];
512                    switch (level) {
513                        case FATAL:
514                            log.fatal(msg, throwable);
515                            break;
516                        case ERROR:
517                            log.error(msg, throwable);
518                            break;
519                        case INFO:
520                            log.info(msg, throwable);
521                            break;
522                        case WARN:
523                            log.warn(msg, throwable);
524                            break;
525                        case DEBUG:
526                            log.debug(msg, throwable);
527                            break;
528                        case TRACE:
529                            log.trace(msg, throwable);
530                            break;
531                    }
532                }
533            }
534        }
535    }
536
537    /**
538     * Log a fatal message <code>Exception</code> to the common <code>Log</code>.
539     *
540     * @param msgTemplate message template.
541     * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
542     */
543    public void fatal(String msgTemplate, Object... params) {
544        log(Level.FATAL, STD, msgTemplate, params);
545    }
546
547    /**
548     * Log a error message <code>Exception</code> to the common <code>Log</code>.
549     *
550     * @param msgTemplate message template.
551     * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
552     */
553    public void error(String msgTemplate, Object... params) {
554        log(Level.ERROR, STD, msgTemplate, params);
555    }
556
557    /**
558     * Log a info message <code>Exception</code> to the common <code>Log</code>.
559     *
560     * @param msgTemplate message template.
561     * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
562     */
563    public void info(String msgTemplate, Object... params) {
564        log(Level.INFO, STD, msgTemplate, params);
565    }
566
567    /**
568     * Log a warn message <code>Exception</code> to the common <code>Log</code>.
569     *
570     * @param msgTemplate message template.
571     * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
572     */
573    public void warn(String msgTemplate, Object... params) {
574        log(Level.WARN, STD, msgTemplate, params);
575    }
576
577    /**
578     * Log a debug message <code>Exception</code> to the common <code>Log</code>.
579     *
580     * @param msgTemplate message template.
581     * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
582     */
583    public void debug(String msgTemplate, Object... params) {
584        log(Level.DEBUG, STD, msgTemplate, params);
585    }
586
587    /**
588     * Log a trace message <code>Exception</code> to the common <code>Log</code>.
589     *
590     * @param msgTemplate message template.
591     * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
592     */
593    public void trace(String msgTemplate, Object... params) {
594        log(Level.TRACE, STD, msgTemplate, params);
595    }
596
597    /**
598     * Tee Log a fatal message <code>Exception</code> to the common log and specified <code>Log</code>s.
599     *
600     * @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
601     * @param msgTemplate message template.
602     * @param params parameters for the message template.
603     */
604    public void fatal(int loggerMask, String msgTemplate, Object... params) {
605        log(Level.FATAL, loggerMask, msgTemplate, params);
606    }
607
608    /**
609     * Tee Log a error message <code>Exception</code> to the common log and specified <code>Log</code>s.
610     *
611     * @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
612     * @param msgTemplate message template.
613     * @param params parameters for the message template.
614     */
615    public void error(int loggerMask, String msgTemplate, Object... params) {
616        log(Level.ERROR, loggerMask, msgTemplate, params);
617    }
618
619    /**
620     * Tee Log a info message <code>Exception</code> to the common log and specified <code>Log</code>s.
621     *
622     * @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
623     * @param msgTemplate message template.
624     * @param params parameters for the message template.
625     */
626    public void info(int loggerMask, String msgTemplate, Object... params) {
627        log(Level.INFO, loggerMask, msgTemplate, params);
628    }
629
630    /**
631     * Tee Log a warn message <code>Exception</code> to the common log and specified <code>Log</code>s.
632     *
633     * @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
634     * @param msgTemplate message template.
635     * @param params parameters for the message template.
636     */
637    public void warn(int loggerMask, String msgTemplate, Object... params) {
638        log(Level.WARN, loggerMask, msgTemplate, params);
639    }
640
641    /**
642     * Tee Log a debug message <code>Exception</code> to the common log and specified <code>Log</code>s.
643     *
644     * @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
645     * @param msgTemplate message template.
646     * @param params parameters for the message template.
647     */
648    public void debug(int loggerMask, String msgTemplate, Object... params) {
649        log(Level.DEBUG, loggerMask, msgTemplate, params);
650    }
651
652    /**
653     * Tee Log a trace message <code>Exception</code> to the common log and specified <code>Log</code>s.
654     *
655     * @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
656     * @param msgTemplate message template.
657     * @param params parameters for the message template.
658     */
659    public void trace(int loggerMask, String msgTemplate, Object... params) {
660        log(Level.TRACE, loggerMask, msgTemplate, params);
661    }
662
663    /**
664     * Utility method that does uses the <code>StringFormat</code> to format the message template using the provided
665     * parameters. <p> In addition to the <code>StringFormat</code> syntax for message templates, it supports
666     * <code>{E}</code> for ENTER. <p> The last parameter is ignored for the formatting if it is an Exception.
667     *
668     * @param msgTemplate message template.
669     * @param params paramaters to use in the template. If the last parameter is an Exception, it is ignored.
670     * @return formatted message.
671     */
672    public static String format(String msgTemplate, Object... params) {
673        ParamChecker.notEmpty(msgTemplate, "msgTemplate");
674        msgTemplate = msgTemplate.replace("{E}", System.getProperty("line.separator"));
675        msgTemplate = replaceEmptyPositions(msgTemplate);
676        if (params != null && params.length > 0) {
677            msgTemplate = MessageFormat.format(msgTemplate, params);
678        }
679        return msgTemplate;
680    }
681
682    private static String replaceEmptyPositions(String msgTemplate) {
683        int pos = 0;
684
685        while (msgTemplate.contains("{}")) {
686            msgTemplate = msgTemplate.replace("{}", String.format("{%d}", pos));
687            pos++;
688        }
689
690        return msgTemplate;
691    }
692
693    /**
694     * Utility method that extracts the <code>Throwable</code>, if present, from the parameters.
695     *
696     * @param params parameters.
697     * @return a <code>Throwable</code> instance if it is the last parameter, <code>null</code> otherwise.
698     */
699    public static Throwable getCause(Object... params) {
700        Throwable throwable = null;
701        if (params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) {
702            throwable = (Throwable) params[params.length - 1];
703        }
704        return throwable;
705    }
706
707}