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