// Copyright (C) 2009 Google Inc.
//
// Licensed 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 com.google.enterprise.connector.logging;
import java.util.EmptyStackException;
import java.util.Stack;
/**
* Traditional Nested Diagnostic Context. The interface is similar
* to the NDC class presented by the widely-used log4j.
*
* <p>This maintains a stack of diagnostic context messages that
* is maintained on a per-thread basis. It is primarily used
* to add per-thread context while logging.
*
* <p>When entering a new context, a thread might call {@link #push(String)}
* or {@link #pushAppend(String)} to add a new context message at the top
* of the stack. When leaving the context, the thread should call
* {@link #pop} to restore the previous context message.
*
* <p>If the context message stack is no longer needed, it may be
* cleared by calling {@link #clear}. This discards any messages
* left on the stack, but retains the ThreadLocal used to maintain
* the stack. This is useful if the thread will be reused in the
* future, and desire diagnostic context. For instance, threads
* in a thread-pool handling servlet requests could create a new
* context when a thread is taken from the pool and given a request
* packet. The context message could uniquely identify the request
* by requester, port, cookie, etc. Once the request has been
* serviced, the thread could clear the NDC stack just before being
* returned to the thread-pool.
*
* <p>When a thread will be unlikely to use a diagnostic context in
* the future, it should call {@link #remove} to release the ThreadLocal
* resources used by the context. This is especially important if
* when the thread exits, so these resources may get garbage collected.
*/
public class NDC {
private static final String EMPTY_STRING = "";
private static final ThreadLocal<Stack<String>> stack =
new ThreadLocal<Stack<String>>();
/**
* Clear any nested diagnostic information, if any, but maintain the
* NDC ThreadLocal. This is useful for threads that will be reused
* with a diagnostic context, such as those in a thread-pool.
*/
public static void clear() {
getContextStack().clear();
}
/**
* Return the current nesting depth of this diagnostic context.
*/
public static int getDepth() {
return getContextStack().size();
}
/**
* Push new diagnostic context information for the current thread.
*
* @param message The new diagnostic context information.
*/
public static void push(String message) {
getContextStack().push(message);
}
/**
* Push new diagnostic context information for the current thread.
* The new context information is formed by appending the supplied
* message to the current context. If there is no current context,
* the new context is simply the supplied message.
*
* @param message The new diagnostic context information to append
* to the current context.
*/
public static void pushAppend(String message) {
String current = peek();
if (current.length() > 0) {
push(current + " " + message);
} else {
push(message);
}
}
/**
* Append the supplied message to the current diagnostic context,
* if one exists. If there is no current context, do nothing.
*
* @param message The new diagnostic context information to append
* to the current context.
*/
public static void append(String message) {
try {
String current = getContextStack().pop();
if (current.length() > 0) {
push(current + " " + message);
} else {
push(message);
}
} catch (EmptyStackException ignored) {
// Do nothing.
}
}
/**
* Looks at the last diagnostic context at the top of the context
* stack without removing it.
*
* @return The value that was pushed last. If the stack is empty
* the empty string is returned.
*/
public static String peek() {
try {
return getContextStack().peek();
} catch (EmptyStackException e) {
return EMPTY_STRING;
}
}
/**
* Remove the String at the top of the context stack and return it.
*
* @return The value that was pushed last. If the stack is empty
* the empty string is returned.
*/
public static String pop() {
try {
return getContextStack().pop();
} catch (EmptyStackException e) {
return EMPTY_STRING;
}
}
/**
* Remove the diagnostic context from the thread. This clears the
* context stack and removes the NDC ThreadLocal.
*
* <p>Each thread that created a diagnostic context by calling
* {@link #push push()} should call this method before exiting.
* Otherwise, the memory used by the thread cannot be reclaimed by the VM.
*/
public static void remove() {
stack.remove();
}
/**
* Return this thread's context stack. If this thread has no
* context stack, create one.
*
* @return The contextStack.
*/
private static Stack<String> getContextStack() {
Stack<String> ndcStack = stack.get();
if (ndcStack == null) {
ndcStack = new Stack<String>();
stack.set(ndcStack);
}
return ndcStack;
}
}