001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *      http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.oozie.util;
019    
020    
021    import org.apache.commons.logging.Log;
022    import org.apache.commons.logging.LogFactory;
023    
024    import java.text.MessageFormat;
025    import java.util.ArrayList;
026    import java.util.HashMap;
027    import java.util.List;
028    import 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     */
035    public 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    }