com.peterphi.std.util.tracing.Tracing Maven / Gradle / Ivy
package com.peterphi.std.util.tracing;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public final class Tracing
{
private static final Logger log = Logger.getLogger(Tracing.class);
private static ThreadLocal THREAD_LOCAL = new ThreadLocal<>();
public static boolean DEFAULT_VERBOSE = false;
public String id;
public int ops = 0;
public boolean verbose = DEFAULT_VERBOSE;
private Tracing()
{
}
public static Tracing peek()
{
return THREAD_LOCAL.get();
}
public static Tracing get()
{
Tracing tracing = peek();
if (tracing == null)
{
tracing = new Tracing();
THREAD_LOCAL.set(tracing);
}
return tracing;
}
public static void stop(final String id)
{
Tracing tracing = peek();
if (tracing != null)
{
if (StringUtils.equals(tracing.id, id))
{
if (tracing.verbose)
log("End trace");
clear();
}
else
{
if (tracing.verbose)
log("End sub-trace", id);
}
}
}
public static void clear()
{
THREAD_LOCAL.remove();
MDC.clear();
}
public static void start(final String id)
{
start(id, DEFAULT_VERBOSE);
}
public static void start(final String id, final boolean verbose)
{
final Tracing tracing = get();
// N.B. do not overwrite existing tracing, but do log it
if (tracing.id == null)
{
tracing.id = id;
tracing.verbose = verbose || log.isTraceEnabled();
tracing.ops = 0;
if (id != null)
MDC.put(TracingConstants.MDC_TRACE_ID, id);
if (tracing.verbose)
log("Start trace");
}
else
{
if (tracing.verbose)
log("Start sub-trace", id);
}
}
/**
* Allocate an operation ID within a tracing block, returning null if we are not within a tracing block
*
* @return
*/
public static String newOperationId()
{
return log();
}
/**
* Wrap a function call in a trace block; designed for use in a parallel stream
*
* @param
* the type of the input to the function
* @param
* the type of the result of the function
*
* @return
*/
public static Function wrap(final Function function)
{
final Tracing tracing = Tracing.get();
final String traceId = tracing.newOperationId();
return wrap(traceId, tracing.isVerbose(), function);
}
/**
* Wrap a function call in a trace block; designed for use in a parallel stream
*
* @param
* the type of the input to the function
* @param
* the type of the result of the function
*
* @return
*/
public static Function wrap(final String id, final boolean verbose, final Function function)
{
return (t) -> {
try
{
Tracing.start(id, verbose);
return function.apply(t);
}
finally
{
Tracing.stop(id);
}
};
}
public static String log(final String name, final Supplier detail)
{
if (detail == null)
return log(StringUtils.trim(name));
else
return log(StringUtils.trim(name), (Object) detail);
}
/**
* If verbose tracing is enabled, log an operation
*
* @param detail an array of items; will be reduced to String and concatenated together; if a Supplier is in the list, it will be invoked
*
* @return an operation identifier (if we're within a tracing block)
*/
public static String log(final Object... detail)
{
final Tracing tracing = peek();
if (tracing != null)
{
final String eventId = tracing.id + "/" + (++tracing.ops);
if ((tracing.verbose || log.isTraceEnabled()))
{
logMessage(eventId, detail);
}
return eventId;
}
else
{
return null;
}
}
/**
* Log an additional message about an ongoing operation
*
* @param operationId
* the operation id returned by either {@link #log(String, Supplier)} or {@link #newOperationId()}
* @param name
* @param detail
*/
public static void logOngoing(final String operationId, final String name, final Object... detail)
{
if (operationId != null && isVerbose())
{
logMessage(operationId, StringUtils.trim(name), detail);
}
}
/**
* @param operationId
* @param name
* @param detail
* an array of items; will be reduced to String and concatenated together; if a Supplier is in the list, it will be invoked
*/
private static void logMessage(final String operationId, final Object... detail)
{
if (detail == null)
return; // nothing to supply
// Reduce all inputs to string, special-casing Supplier if present
final String detailStr = Arrays.stream(detail).map(o -> {
try
{
if (o instanceof Supplier)
{
return Objects.toString(((Supplier) o).get());
}
else if (o instanceof Object[])
{
return Arrays.toString((Object[])o);
}
else {
return Objects.toString(o);
}
}
catch (Throwable t)
{
return "";
}
}).collect(Collectors.joining(" ", "Trace{" + operationId + "} ", ""));
log.warn(detailStr);
}
public static String getTraceId()
{
final Tracing tracing = peek();
if (tracing != null)
return tracing.id;
else
return null;
}
public static boolean isVerbose()
{
final Tracing tracing = peek();
if (tracing != null)
return tracing.verbose || log.isTraceEnabled();
else
return false;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy