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