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