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 }