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