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