/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.waveprotocol.wave.util.logging; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Maps; import java.util.Map; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; /** * Standard logging class, wraps a java.util.logging.Logger, * augmenting it with convenience methods. Also provides a mapped diagnostic * context for each thread. This context information is prepended to messages * logged from that thread. * * The functionality to infer the caller of a logging method has been replicated * from {@link LogRecord} because there was no easy way to use it correctly from * this wrapper class. * * */ public class Log { /** * Version of LogRecord that can correctly infer its caller. */ @VisibleForTesting static class MyLogRecord extends LogRecord { /** * Finds the caller of this class. Algorithm derived from code in * {@link java.util.logging.Logger}. This method necessary because it is not * possible to wrap logger in a way that allows the caller to be derived. * * @return information about the function that called into this class, or * <code>null</code> if not found. */ @VisibleForTesting static StackTraceElement findCaller() { // Get the stack trace. StackTraceElement stack[] = (new Throwable()).getStackTrace(); String logClassName = Log.class.getName(); // Scan down to a call to Log int i = 0; while (i < stack.length // \u2620 && !logClassName.equals(stack[i].getClassName())) { i++; } // Scan down through further to first one to call Log while (i < stack.length // \u2620 && logClassName.equals(stack[i].getClassName())) { i++; } return (i < stack.length ? stack[i] : null); } /** Mimics {@link LogRecord}'s needToInferCaller from superclass. */ private boolean needToInferCaller = true; /** Constructor. Mimics signature of superclass. */ public MyLogRecord(Level level, String msg, Throwable t) { super(level, msg); setThrown(t); } /** {@inheritDoc} */ @Override public String getSourceClassName() { if (needToInferCaller) { inferCaller(); } return super.getSourceClassName(); } /** {@inheritDoc} */ @Override public String getSourceMethodName() { if (needToInferCaller) { inferCaller(); } return super.getSourceMethodName(); } /** Infers the caller's source class and method name. */ private void inferCaller() { needToInferCaller = false; StackTraceElement caller = findCaller(); if (caller != null) { super.setSourceClassName(caller.getClassName()); super.setSourceMethodName(caller.getMethodName()); } else { super.setSourceClassName(null); super.setSourceMethodName(null); } } /** {@inheritDoc} */ @Override public void setSourceClassName(String sourceClassName) { needToInferCaller = false; super.setSourceClassName(sourceClassName); } /** {@inheritDoc} */ @Override public void setSourceMethodName(String sourceMethodName) { needToInferCaller = false; super.setSourceMethodName(sourceMethodName); } } /** Per-thread mapped diagnostic context. */ private static final ThreadLocal<Map<String, String>> context = new ThreadLocal<Map<String, String>>() { /** {@inheritDoc} */ @Override protected Map<String, String> initialValue() { // predictable iteration order gives nicer log messages return Maps.newLinkedHashMap(); } }; /** * Creates or lazily loads a logger. Currently just creates an instance but * allows us to do more clever stuff in the future. */ public static Log get(Class<? extends Object> clazz) { return new Log(clazz); } /** The underlying logger to which this object logs messages. */ private final Logger logger; /** * Creates a new logging object. * * @deprecated use {@link #get(Class)} instead. */ @Deprecated private Log(Class<? extends Object> clazz) { this(Logger.getLogger(clazz.getName())); } /** * Creates a new logging object that will ultimately log records through the * given logger. */ public Log(Logger logger) { this.logger = logger; } /** * Logs a message at level CONFIG. * * @param msg The message to log. */ public void config(String msg) { log(Level.CONFIG, msg, null); } /** * Logs a message at level CONFIG. * * @param msg The message to log. * @param t The throwable to log with the message. */ public void config(String msg, Throwable t) { log(Level.CONFIG, msg, t); } private String contextualiseMessage(String message) { Map<String, String> contextData = context.get(); if (contextData.isEmpty()) { return message; } else { StringBuilder result = new StringBuilder(); result.append(message); result.append(" -- "); result.append(contextData); return result.toString(); } } /** * Logs a message at level FINE. * * @param msg The message to log. */ public void fine(String msg) { log(Level.FINE, msg, null); } /** * Logs a message at level FINE. * * @param msg The message to log. * @param t The throwable to log with the message. */ public void fine(String msg, Throwable t) { log(Level.FINE, msg, t); } /** * Logs a message at level FINER. * * @param msg The message to log. */ public void finer(String msg) { log(Level.FINER, msg, null); } /** * Logs a message at level FINER. * * @param msg The message to log. * @param t The throwable to log with the message. */ public void finer(String msg, Throwable t) { log(Level.FINER, msg, t); } /** * Logs a message at level FINEST. * * @param msg The message to log. */ public void finest(String msg) { log(Level.FINEST, msg, null); } /** * Logs a message at level FINEST. * * @param msg The message to log. * @param t The throwable to log with the message. */ public void finest(String msg, Throwable t) { log(Level.FINEST, msg, t); } /** * Gets the underlying logger. */ public Logger getLogger() { return logger; } /** * Logs a message at level INFO. * * @param msg The message to log. */ public void info(String msg) { log(Level.INFO, msg, null); } /** * Logs a message at level INFO. * * @param msg The message to log. * @param t The throwable to log with the message. */ public void info(String msg, Throwable t) { log(Level.INFO, msg, t); } /** * Whether CONFIG messages are being output. */ public boolean isConfigLoggable() { return logger.isLoggable(Level.CONFIG); } /** * Whether FINE messages are being output. */ public boolean isFineLoggable() { return logger.isLoggable(Level.FINE); } /** * Whether FINER messages are being output. */ public boolean isFinerLoggable() { return logger.isLoggable(Level.FINER); } /** * Whether FINEST messages are being output. */ public boolean isFinestLoggable() { return logger.isLoggable(Level.FINEST); } /** * Whether INFO messages are being output. */ public boolean isInfoLoggable() { return logger.isLoggable(Level.INFO); } /** * Whether SEVERE messages are being output. */ public boolean isSevereLoggable() { return logger.isLoggable(Level.SEVERE); } /** * Whether WARNING messages are being output. */ public boolean isWarningLoggable() { return logger.isLoggable(Level.WARNING); } /** * Generic log a message, prepended with the current thread's log context, at * a given level. */ public void log(Level level, String msg, Throwable t) { if (!logger.isLoggable(level)) { return; } LogRecord record = new MyLogRecord(level, contextualiseMessage(msg), t); logger.log(record); } /** Saves a key/value pair into this thread's log context. */ public void putContext(String key, String value) { context.get().put(key, value); } /** * Removes a key from this thread's log context. * * @param key The key to remove. * @return true if there was a value previously associated with the key, false * if this call resulted in no changes being made. */ public boolean removeContext(String key) { return context.get().remove(key) != null; } /** * Logs a message at level SEVERE. * * @param msg The message to log. */ public void severe(String msg) { log(Level.SEVERE, msg, null); } /** * Logs a message at level SEVERE along with an exception that can be * extracted from the logs and reported. * * @param msg The message to log. * @param t The throwable to log with the message. */ public void severe(String msg, Throwable t) { log(Level.SEVERE, msg, t); } /** * Logs a message at level WARNING. * * @param msg The message to log. */ public void warning(String msg) { log(Level.WARNING, msg, null); } /** * Logs a message at level WARNING along with an exception that can be * extracted from the logs and reported. * * @param msg The message to log. * @param t The throwable to log with the message. */ public void warning(String msg, Throwable t) { log(Level.WARNING, msg, t); } }