package dmg.cells.nucleus;
import org.slf4j.MDC;
import org.dcache.util.NDC;
/**
* The Cell Diagnostic Context, a utility class for working with the
* Log4j NDC and MDC.
*
* Notice that the MDC is automatically inherited by child threads
* upon creation. Thus the domain, cell name, and session identifier
* is inherited. The same is not true for the NDC, which needs to be
* explicitly copied. Special care must be taken when using shared
* thread pools, as this can span several cells. For those the MDC
* should be initialised per task, not per thread.
*
* The class serves two purposes:
*
* - It contains a number of static methods for manipulating the MDC
* and NDC.
*
* - CDC instances capture the Cells related MDC values and the NDC.
* These can be applied to other threads. This is useful when using
* worker threads that should inherit the context of the task
* creation point.
*
* CDC implements AutoCloseable and will restore the captured values
* when closed(). This allows the following useful patterns:
*
* try (CDC ignored = new CDC()) {
* // temporarily modify the CDC - it will auto restore
* }
*
* and
*
* try (CDC ignored = cdc.restore()) {
* // temporarily use a previously captured cdc
* }
*/
public class CDC implements AutoCloseable
{
public static final String MDC_DOMAIN = "cells.domain";
public static final String MDC_CELL = "cells.cell";
public static final String MDC_SESSION = "cells.session";
private final NDC _ndc;
private final String _session;
private final String _cell;
private final String _domain;
/**
* Captures the cells diagnostic context of the calling thread.
*/
public CDC()
{
_session = MDC.get(MDC_SESSION);
_cell = MDC.get(MDC_CELL);
_domain = MDC.get(MDC_DOMAIN);
_ndc = NDC.cloneNdc();
}
/**
* Wrapper around <code>MDC.put</code> and
* <code>MDC.remove</code>. <code>value</code> is allowed to e
* null.
*/
private static void setMdc(String key, String value)
{
if (value != null) {
MDC.put(key, value);
} else {
MDC.remove(key);
}
}
private void apply()
{
setMdc(MDC_DOMAIN, _domain);
setMdc(MDC_CELL, _cell);
setMdc(MDC_SESSION, _session);
if (_ndc == null) {
NDC.clear();
} else {
NDC.set(_ndc);
}
}
@Override
public void close()
{
apply();
}
/**
* Restore the cells diagnostic context to the calling thread. The old
* diagnostic context is captured and returned.
*/
public CDC restore()
{
CDC cdc = new CDC();
apply();
return cdc;
}
/**
* Execute the given runnable within the context of this CDC.
*/
public void execute(Runnable r)
{
String session = MDC.get(MDC_SESSION);
String cell = MDC.get(MDC_CELL);
String domain = MDC.get(MDC_DOMAIN);
NDC ndc = NDC.cloneNdc();
try {
apply();
r.run();
} finally {
setMdc(MDC_DOMAIN, domain);
setMdc(MDC_CELL, cell);
setMdc(MDC_SESSION, session);
NDC.set(ndc);
}
}
/**
* Returns the cell name stored in the MDC of the calling
* thread.
*/
public static String getCellName()
{
return MDC.get(MDC_CELL);
}
/**
* Returns the domain name stored in the MDC of the calling
* thread.
*/
public static String getDomainName()
{
return MDC.get(MDC_DOMAIN);
}
/**
* Returns the session identifier stored in the MDC of the calling
* thread.
*/
public static String getSession()
{
return MDC.get(MDC_SESSION);
}
/**
* Sets the session in the MDC for the calling thread.
*
* @param session Session identifier.
*/
public static void setSession(String session)
{
setMdc(MDC_SESSION, session);
}
/**
* Setup the cell diagnostic context of the calling
* thread. Threads created from the calling thread automatically
* inherit this information. The old diagnostic context is
* captured and returned.
*/
public static CDC reset(CellNucleus cell)
{
return reset(cell.getCellName(), cell.getCellDomainName());
}
public static CDC reset(CellAddressCore address)
{
return reset(address.getCellName(), address.getCellDomainName());
}
/**
* Setup the cell diagnostic context of the calling
* thread. Threads created from the calling thread automatically
* inherit this information. The old diagnostic context is
* captured and returned.
*/
public static CDC reset(String cellName, String domainName)
{
CDC cdc = new CDC();
setMdc(MDC_CELL, cellName);
setMdc(MDC_DOMAIN, domainName);
MDC.remove(MDC_SESSION);
NDC.clear();
return cdc;
}
/**
* Returns the message description added to the NDC as part of the
* message related diagnostic context.
*/
protected static String getMessageContext(CellMessage envelope)
{
Object sessionObject = envelope.getSession();
String session = (sessionObject == null) ? null : sessionObject.toString();
String cellName = envelope.getSourcePath().getCellName();
Object msg = envelope.getMessageObject();
String context = (msg instanceof HasDiagnosticContext) ? ((HasDiagnosticContext) msg).getDiagnosticContext() : null;
StringBuilder s = new StringBuilder(((session == null) ? 0 : session.length() + 1) +
cellName.length() +
((context == null) ? 0 : 1 + context.length()));
if (session != null) {
s.append(session).append(' ');
}
s.append(cellName);
if (context != null) {
s.append(' ').append(context);
}
return s.toString();
}
/**
* Setup message related diagnostic context for the calling
* thread. Adds information about a message to the MDC and NDC.
*
* @see clearMessageContext
*/
public static void setMessageContext(CellMessage envelope)
{
Object session = envelope.getSession();
NDC.push(getMessageContext(envelope));
setMdc(MDC_SESSION, (session == null) ? null : session.toString());
}
/**
* Clears the diagnostic context entries added by
* <code>setMessageContext</code>. For this to work, the NDC has
* to be in the same state as when <code>setMessageContext</code>
* returned.
*
* @see setMessageContext
*/
public static void clearMessageContext()
{
MDC.remove(MDC_SESSION);
NDC.pop();
}
/**
* Clears all cells related MDC entries and the NDC.
*/
public static void clear()
{
MDC.remove(MDC_DOMAIN);
MDC.remove(MDC_CELL);
MDC.remove(MDC_SESSION);
NDC.clear();
}
}