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         * Reset the logger prefix
223         *
224         * @param log the named logger
225         * @return the named logger with reset prefix
226         */
227        public static XLog resetPrefix(XLog log) {
228            log.setMsgPrefix(Info.get().createPrefix());
229            return log;
230        }
231    
232        /**
233         * Mask for logging to the standard log.
234         */
235        public static final int STD = 1;
236    
237        /**
238         * Mask for tee logging to the OPS log.
239         */
240        public static final int OPS = 4;
241    
242        private static final int ALL = STD | OPS;
243    
244        private static final int[] LOGGER_MASKS = {STD, OPS};
245    
246        //package private for testing purposes.
247        Log[] loggers;
248    
249        private String prefix = "";
250    
251        /**
252         * Create a <code>XLog</code> with no prefix.
253         *
254         * @param log Log instance to use for logging.
255         */
256        public XLog(Log log) {
257            this(log, "");
258        }
259    
260        /**
261         * Create a <code>XLog</code> with a common prefix. <p/> The prefix will be prepended to all log messages.
262         *
263         * @param log Log instance to use for logging.
264         * @param prefix common prefix to use for all log messages.
265         */
266        public XLog(Log log, String prefix) {
267            this.prefix = prefix;
268            loggers = new Log[2];
269            loggers[0] = log;
270            loggers[1] = LogFactory.getLog("oozieops");
271        }
272    
273        /**
274         * Return the common prefix.
275         *
276         * @return the common prefix.
277         */
278        public String getMsgPrefix() {
279            return prefix;
280        }
281    
282        /**
283         * Set the common prefix.
284         *
285         * @param prefix the common prefix to set.
286         */
287        public void setMsgPrefix(String prefix) {
288            this.prefix = (prefix != null) ? prefix : "";
289        }
290    
291        //All the methods from the commonsLogging Log interface will log to the default logger only.
292    
293        /**
294         * Log a debug message to the common <code>Log</code>.
295         *
296         * @param o message.
297         */
298        @Override
299        public void debug(Object o) {
300            log(Level.DEBUG, STD, "{0}", o);
301        }
302    
303        /**
304         * Log a debug message and <code>Exception</code> to the common <code>Log</code>.
305         *
306         * @param o message.
307         * @param throwable exception.
308         */
309        @Override
310        public void debug(Object o, Throwable throwable) {
311            log(Level.DEBUG, STD, "{0}", o, throwable);
312        }
313    
314        /**
315         * Log a error message to the common <code>Log</code>.
316         *
317         * @param o message.
318         */
319        @Override
320        public void error(Object o) {
321            log(Level.ERROR, STD, "{0}", o);
322        }
323    
324        /**
325         * Log a error message and <code>Exception</code> to the common <code>Log</code>.
326         *
327         * @param o message.
328         * @param throwable exception.
329         */
330        @Override
331        public void error(Object o, Throwable throwable) {
332            log(Level.ERROR, STD, "{0}", o, throwable);
333        }
334    
335        /**
336         * Log a fatal message to the common <code>Log</code>.
337         *
338         * @param o message.
339         */
340        @Override
341        public void fatal(Object o) {
342            log(Level.FATAL, STD, "{0}", o);
343        }
344    
345        /**
346         * Log a fatal message and <code>Exception</code> to the common <code>Log</code>.
347         *
348         * @param o message.
349         * @param throwable exception.
350         */
351        @Override
352        public void fatal(Object o, Throwable throwable) {
353            log(Level.FATAL, STD, "{0}", o, throwable);
354        }
355    
356        /**
357         * Log a info message to the common <code>Log</code>.
358         *
359         * @param o message.
360         */
361        @Override
362        public void info(Object o) {
363            log(Level.INFO, STD, "{0}", o);
364        }
365    
366        /**
367         * Log a info message and <code>Exception</code> to the common <code>Log</code>.
368         *
369         * @param o message.
370         * @param throwable exception.
371         */
372        @Override
373        public void info(Object o, Throwable throwable) {
374            log(Level.INFO, STD, "{0}", o, throwable);
375        }
376    
377        /**
378         * Log a trace message to the common <code>Log</code>.
379         *
380         * @param o message.
381         */
382        @Override
383        public void trace(Object o) {
384            log(Level.TRACE, STD, "{0}", o);
385        }
386    
387        /**
388         * Log a trace message and <code>Exception</code> to the common <code>Log</code>.
389         *
390         * @param o message.
391         * @param throwable exception.
392         */
393        @Override
394        public void trace(Object o, Throwable throwable) {
395            log(Level.TRACE, STD, "{0}", o, throwable);
396        }
397    
398        /**
399         * Log a warn message to the common <code>Log</code>.
400         *
401         * @param o message.
402         */
403        @Override
404        public void warn(Object o) {
405            log(Level.WARN, STD, "{0}", o);
406        }
407    
408        /**
409         * Log a warn message and <code>Exception</code> to the common <code>Log</code>.
410         *
411         * @param o message.
412         * @param throwable exception.
413         */
414        @Override
415        public void warn(Object o, Throwable throwable) {
416            log(Level.WARN, STD, "{0}", o, throwable);
417        }
418    
419        /**
420         * Return if debug logging is enabled.
421         *
422         * @return <code>true</code> if debug logging is enable, <code>false</code> if not.
423         */
424        @Override
425        public boolean isDebugEnabled() {
426            return isEnabled(Level.DEBUG, ALL);
427        }
428    
429        /**
430         * Return if error logging is enabled.
431         *
432         * @return <code>true</code> if error logging is enable, <code>false</code> if not.
433         */
434        @Override
435        public boolean isErrorEnabled() {
436            return isEnabled(Level.ERROR, ALL);
437        }
438    
439        /**
440         * Return if fatal logging is enabled.
441         *
442         * @return <code>true</code> if fatal logging is enable, <code>false</code> if not.
443         */
444        @Override
445        public boolean isFatalEnabled() {
446            return isEnabled(Level.FATAL, ALL);
447        }
448    
449        /**
450         * Return if info logging is enabled.
451         *
452         * @return <code>true</code> if info logging is enable, <code>false</code> if not.
453         */
454        @Override
455        public boolean isInfoEnabled() {
456            return isEnabled(Level.INFO, ALL);
457        }
458    
459        /**
460         * Return if trace logging is enabled.
461         *
462         * @return <code>true</code> if trace logging is enable, <code>false</code> if not.
463         */
464        @Override
465        public boolean isTraceEnabled() {
466            return isEnabled(Level.TRACE, ALL);
467        }
468    
469        /**
470         * Return if warn logging is enabled.
471         *
472         * @return <code>true</code> if warn logging is enable, <code>false</code> if not.
473         */
474        @Override
475        public boolean isWarnEnabled() {
476            return isEnabled(Level.WARN, ALL);
477        }
478    
479        public enum Level {
480            FATAL, ERROR, INFO, WARN, DEBUG, TRACE
481        }
482    
483        private boolean isEnabled(Level level, int loggerMask) {
484            for (int i = 0; i < loggers.length; i++) {
485                if ((LOGGER_MASKS[i] & loggerMask) != 0) {
486                    boolean enabled = false;
487                    switch (level) {
488                        case FATAL:
489                            enabled = loggers[i].isFatalEnabled();
490                            break;
491                        case ERROR:
492                            enabled = loggers[i].isErrorEnabled();
493                            break;
494                        case INFO:
495                            enabled = loggers[i].isInfoEnabled();
496                            break;
497                        case WARN:
498                            enabled = loggers[i].isWarnEnabled();
499                            break;
500                        case DEBUG:
501                            enabled = loggers[i].isDebugEnabled();
502                            break;
503                        case TRACE:
504                            enabled = loggers[i].isTraceEnabled();
505                            break;
506                    }
507                    if (enabled) {
508                        return true;
509                    }
510                }
511            }
512            return false;
513        }
514    
515    
516        private void log(Level level, int loggerMask, String msgTemplate, Object... params) {
517            loggerMask |= STD;
518            if (isEnabled(level, loggerMask)) {
519                String prefix = getMsgPrefix();
520                prefix = (prefix != null && prefix.length() > 0) ? prefix + " " : "";
521    
522                String msg = prefix + format(msgTemplate, params);
523                Throwable throwable = getCause(params);
524    
525                for (int i = 0; i < LOGGER_MASKS.length; i++) {
526                    if (isEnabled(level, loggerMask & LOGGER_MASKS[i])) {
527                        Log log = loggers[i];
528                        switch (level) {
529                            case FATAL:
530                                log.fatal(msg, throwable);
531                                break;
532                            case ERROR:
533                                log.error(msg, throwable);
534                                break;
535                            case INFO:
536                                log.info(msg, throwable);
537                                break;
538                            case WARN:
539                                log.warn(msg, throwable);
540                                break;
541                            case DEBUG:
542                                log.debug(msg, throwable);
543                                break;
544                            case TRACE:
545                                log.trace(msg, throwable);
546                                break;
547                        }
548                    }
549                }
550            }
551        }
552    
553        /**
554         * Log a fatal message <code>Exception</code> to the common <code>Log</code>.
555         *
556         * @param msgTemplate message template.
557         * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
558         */
559        public void fatal(String msgTemplate, Object... params) {
560            log(Level.FATAL, STD, msgTemplate, params);
561        }
562    
563        /**
564         * Log a error message <code>Exception</code> to the common <code>Log</code>.
565         *
566         * @param msgTemplate message template.
567         * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
568         */
569        public void error(String msgTemplate, Object... params) {
570            log(Level.ERROR, STD, msgTemplate, params);
571        }
572    
573        /**
574         * Log a info message <code>Exception</code> to the common <code>Log</code>.
575         *
576         * @param msgTemplate message template.
577         * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
578         */
579        public void info(String msgTemplate, Object... params) {
580            log(Level.INFO, STD, msgTemplate, params);
581        }
582    
583        /**
584         * Log a warn message <code>Exception</code> to the common <code>Log</code>.
585         *
586         * @param msgTemplate message template.
587         * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
588         */
589        public void warn(String msgTemplate, Object... params) {
590            log(Level.WARN, STD, msgTemplate, params);
591        }
592    
593        /**
594         * Log a debug message <code>Exception</code> to the common <code>Log</code>.
595         *
596         * @param msgTemplate message template.
597         * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
598         */
599        public void debug(String msgTemplate, Object... params) {
600            log(Level.DEBUG, STD, msgTemplate, params);
601        }
602    
603        /**
604         * Log a trace message <code>Exception</code> to the common <code>Log</code>.
605         *
606         * @param msgTemplate message template.
607         * @param params parameters for the message template. If the last parameter is an exception it is logged as such.
608         */
609        public void trace(String msgTemplate, Object... params) {
610            log(Level.TRACE, STD, msgTemplate, params);
611        }
612    
613        /**
614         * Tee Log a fatal 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 fatal(int loggerMask, String msgTemplate, Object... params) {
621            log(Level.FATAL, loggerMask, msgTemplate, params);
622        }
623    
624        /**
625         * Tee Log a error 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 error(int loggerMask, String msgTemplate, Object... params) {
632            log(Level.ERROR, loggerMask, msgTemplate, params);
633        }
634    
635        /**
636         * Tee Log a info 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 info(int loggerMask, String msgTemplate, Object... params) {
643            log(Level.INFO, loggerMask, msgTemplate, params);
644        }
645    
646        /**
647         * Tee Log a warn 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 warn(int loggerMask, String msgTemplate, Object... params) {
654            log(Level.WARN, loggerMask, msgTemplate, params);
655        }
656    
657        /**
658         * Tee Log a debug 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 debug(int loggerMask, String msgTemplate, Object... params) {
665            log(Level.DEBUG, loggerMask, msgTemplate, params);
666        }
667    
668        /**
669         * Tee Log a trace message <code>Exception</code> to the common log and specified <code>Log</code>s.
670         *
671         * @param loggerMask log mask, it is a bit mask, possible values are <code>APP</code> and <code>OPS</code>.
672         * @param msgTemplate message template.
673         * @param params parameters for the message template.
674         */
675        public void trace(int loggerMask, String msgTemplate, Object... params) {
676            log(Level.TRACE, loggerMask, msgTemplate, params);
677        }
678    
679        /**
680         * Utility method that does uses the <code>StringFormat</code> to format the message template using the provided
681         * parameters. <p/> In addition to the <code>StringFormat</code> syntax for message templates, it supports
682         * <code>{E}</code> for ENTER. <p/> The last parameter is ignored for the formatting if it is an Exception.
683         *
684         * @param msgTemplate message template.
685         * @param params paramaters to use in the template. If the last parameter is an Exception, it is ignored.
686         * @return formatted message.
687         */
688        public static String format(String msgTemplate, Object... params) {
689            ParamChecker.notEmpty(msgTemplate, "msgTemplate");
690            msgTemplate = msgTemplate.replace("{E}", System.getProperty("line.separator"));
691            if (params != null && params.length > 0) {
692                msgTemplate = MessageFormat.format(msgTemplate, params);
693            }
694            return msgTemplate;
695        }
696    
697        /**
698         * Utility method that extracts the <code>Throwable</code>, if present, from the parameters.
699         *
700         * @param params parameters.
701         * @return a <code>Throwable</code> instance if it is the last parameter, <code>null</code> otherwise.
702         */
703        public static Throwable getCause(Object... params) {
704            Throwable throwable = null;
705            if (params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) {
706                throwable = (Throwable) params[params.length - 1];
707            }
708            return throwable;
709        }
710    
711    }