com.senzing.util.LoggingUtilities Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of senzing-commons Show documentation
Show all versions of senzing-commons Show documentation
Utility classes and functions common to multiple Senzing projects.
The newest version!
package com.senzing.util;
import com.senzing.g2.engine.G2Fallible;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Provides logging utilities.
*/
public class LoggingUtilities {
/**
* Used for good measure to avoid interlacing of out debug output.
*/
private static final Object STDOUT_MONITOR = new Object();
/**
* Used for good measure to avoid interlacing of out debug output.
*/
private static final Object STDERR_MONITOR = new Object();
/**
* The date-time pattern for the build number.
*/
private static final String LOG_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";
/**
* The time zone used for the time component of the build number.
*/
private static final ZoneId LOG_DATE_ZONE = ZoneId.of("UTC");
/**
* The {@link DateTimeFormatter} for interpretting the build number as a
* LocalDateTime instance.
*/
private static final DateTimeFormatter LOG_DATE_FORMATTER
= DateTimeFormatter.ofPattern(LOG_DATE_PATTERN).withZone(LOG_DATE_ZONE);
/**
* The base product ID to log with if the calling package is not overidden.
*/
public static final String BASE_PRODUCT_ID = "5025";
/**
* The system property for indicating that debug logging should be enabled.
*/
public static final String DEBUG_SYSTEM_PROPERTY = "com.senzing.debug";
/**
* The last logged exception in this thread.
*/
private static final ThreadLocal LAST_LOGGED_EXCEPTION
= new ThreadLocal<>();
/**
* The {@link Map} of package prefixes to product ID's.
*/
private static final Map PRODUCT_ID_MAP
= new LinkedHashMap<>();
/**
* Private default constructor
*/
private LoggingUtilities() {
// do nothing
}
/**
* Sets the product ID to use when logging messages for classes in the
* specified package name.
*
* @param packageName The package name.
* @param productId The product ID to use for the package.
*/
public static void setProductIdForPackage(String packageName,
String productId)
{
synchronized (PRODUCT_ID_MAP) {
PRODUCT_ID_MAP.put(packageName, productId);
}
}
/**
* Gets the product ID for the package name of the class performing the
* logging.
*
* @param packageName The package name.
*
* @return The product ID to log with.
*/
public static String getProductIdForPackage(String packageName) {
synchronized (PRODUCT_ID_MAP) {
do {
// check if we have a product ID for the package
if (PRODUCT_ID_MAP.containsKey(packageName)) {
return PRODUCT_ID_MAP.get(packageName);
}
// check if the package name begins with com.senzing and get next part
int prefixLength = "com.senzing.".length();
if (packageName.startsWith("com.senzing.")
&& packageName.length() > prefixLength)
{
int index = packageName.indexOf(".", prefixLength);
if (index < 0) index = packageName.length();
return packageName.substring(prefixLength, index);
}
// strip off the last part of the package name
int index = packageName.lastIndexOf('.');
if (index <= 0) break;
if (index == (packageName.length() - 1)) break;
packageName = packageName.substring(0, index);
} while (packageName.length() > 0 && !packageName.equals("com.senzing"));
// return the base product ID if we get here
return BASE_PRODUCT_ID;
}
}
/**
* Checks if debug logging is enabled.
*
* @return true
if debug logging is enabled, and
* false
if not.
*/
public static boolean isDebugLogging() {
String value = System.getProperty(DEBUG_SYSTEM_PROPERTY);
if (value == null) return false;
return (value.trim().equalsIgnoreCase(Boolean.TRUE.toString()));
}
/**
* Produces a single or multi-line error log message with a consistent prefix
* and timestamp.
*
* @param lines The lines of text to log, which may be objects that will be
* converted to text via {@link Object#toString()}.
*/
public static void logError(Object... lines)
{
log(System.err, STDERR_MONITOR, "ERROR", lines, null);
}
/**
* Produces a multi-line error log message with a consistent prefix
* and timestamp that will conclude with the stack trace for the specified
* {@link Throwable}.
*
* @param throwable The {@link Throwable} whose stack trace should be logged.
* @param lines The lines of text to log, which may be objects that will be
* converted to text via {@link Object#toString()}.
*/
public static void logError(Throwable throwable, Object... lines)
{
log(System.err, STDERR_MONITOR, "ERROR", lines, throwable);
}
/**
* Produces a single or multi-line warning log message with a consistent
* prefix and timestamp.
*
* @param lines The lines of text to log, which may be objects that will be
* converted to text via {@link Object#toString()}.
*/
public static void logWarning(Object... lines)
{
log(System.err, STDERR_MONITOR, "WARNING", lines, null);
}
/**
* Produces a multi-line warning log message with a consistent prefix
* and timestamp that will conclude with the stack trace for the specified
* {@link Throwable}.
*
* @param throwable The {@link Throwable} whose stack trace should be logged.
* @param lines The lines of text to log, which may be objects that will be
* converted to text via {@link Object#toString()}.
*/
public static void logWarning(Throwable throwable, Object... lines)
{
log(System.err, STDERR_MONITOR, "WARNING", lines, throwable);
}
/**
* Produces a single or multi-line error log message with a consistent prefix
* and timestamp.
*
* @param lines The lines of text to log, which may be objects that will be
* converted to text via {@link Object#toString()}.
*/
public static void logInfo(Object... lines)
{
log(System.out, STDOUT_MONITOR, "INFO", lines, null);
}
/**
* Produces a single or multi-line debug message with a consistent prefix
* and timestamp IF {@linkplain #isDebugLogging() debug logging} is turned on.
*
* @param lines The lines of text to log, which may be objects that will be
* converted to text via {@link Object#toString()}.
*/
public static void logDebug(Object... lines)
{
if (!isDebugLogging()) return;
log(System.out, STDOUT_MONITOR, "DEBUG", lines, null);
}
/**
* Produces a single or multi-line debug message with a consistent prefix
* and timestamp IF {@linkplain #isDebugLogging() debug logging} is turned on.
*
* @param lines The lines of text to log, which may be objects that will be
* converted to text via {@link Object#toString()}.
* @deprecated Use {@link #logDebug(Object...)} instead.
* @see #logDebug(Object...)
*/
public static void debugLog(String... lines)
{
if (!isDebugLogging()) return;
log(System.out, STDOUT_MONITOR, "DEBUG", lines, null);
}
/**
* Produces a single or multi-line log message with a consistent prefix
* and timestamp using the specified {@link PrintStream}, {@link Object} to
* synchronize on, {@link String} log type and lines of text to log.
*
* @param ps The {@link PrintStream} to write to.
* @param monitor The {@link Object} to synchronize on when writing and
* flushing the {@link PrintStream}.
* @param logType The logging type such as "DEBUG"
or
* "ERROR"
.
* @param lines The lines of text to log.
* @param throwable The exception to log.
*/
private static void log(PrintStream ps,
Object monitor,
String logType,
Object[] lines,
Throwable throwable)
{
Thread currentThread = Thread.currentThread();
StackTraceElement[] stackTrace = currentThread.getStackTrace();
StackTraceElement caller = stackTrace[3];
String callingClass = caller.getClassName();
int index = callingClass.lastIndexOf(".");
String packageName = callingClass.substring(0, index);
callingClass = callingClass.substring(index+1);
String productId = getProductIdForPackage(packageName);
StringBuilder sb = new StringBuilder();
String timestamp
= LOG_DATE_FORMATTER.format(Instant.now().atZone(LOG_DATE_ZONE));
sb.append(timestamp).append(" senzing-").append(productId)
.append(" (").append(logType).append(")")
.append(" [").append(Thread.currentThread().getId())
.append("|").append(callingClass).append(".")
.append(caller.getMethodName()).append(":")
.append(caller.getLineNumber()).append("] ")
.append(multilineFormat(lines));
// handle the stack trace if a throwable is provided
if (throwable != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
sb.append(sw.toString());
}
synchronized (monitor) {
ps.print(sb);
ps.flush();
}
}
/**
* Formats an array of {@link StackTraceElement} instances using {@link
* #formatStackTrace(StackTraceElement)} with a single element per line.
*
* @param stackTrace The array of {@link StackTraceElement} instances to format.
*
* @return The formatted {@link String} describing the array of {@link
* StackTraceElement} instances or null
if the specified
* array is null
.
*/
public static String formatStackTrace(StackTraceElement[] stackTrace) {
if (stackTrace == null) return null;
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
for (StackTraceElement elem : stackTrace) {
pw.println(formatStackTrace(elem));
}
return sw.toString();
}
/**
* Formats a single {@link StackTraceElement} in the same format as they would appear
* in an exception stack trace.
*
* @param elem The {@link StackTraceElement} to format.
*
* @return The formatted {@link String} describing the {@link StackTraceElement}.
*/
public static String formatStackTrace(StackTraceElement elem) {
StringBuilder sb = new StringBuilder();
sb.append(" at ");
// handle a null element
if (elem == null) {
sb.append("[unknown: null]");
return sb.toString();
}
String moduleName = elem.getModuleName();
if (moduleName != null && moduleName.length() > 0) {
sb.append(moduleName).append("/");
}
sb.append(elem.getClassName());
sb.append(".");
sb.append(elem.getMethodName());
sb.append("(");
String fileName = elem.getFileName();
sb.append((fileName == null) ? "[unknown file]" : fileName);
sb.append(":");
int lineNumber = elem.getLineNumber();
if (lineNumber < 0) {
sb.append("[unknown line]");
} else {
sb.append(lineNumber);
}
sb.append(")");
return sb.toString();
}
/**
* Formats a multiline message.
*
* @param lines The lines to be formatted.
*
* @return The formatted multiline {@link String}.
*/
public static String multilineFormat(Object... lines) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
for (Object line : lines) {
pw.println("" + line);
}
pw.flush();
return sw.toString();
}
/**
* Formats an error message from a {@link G2Fallible} instance, including
* the details (which may contain sensitive PII information) as part of
* the result.
*
* @param operation The name of the operation from the native API interface
* that was attempted but failed.
* @param fallible The {@link G2Fallible} from which to extract the error
* message.
* @return The multi-line formatted log message.
*/
public static String formatError(String operation, G2Fallible fallible)
{
return formatError(operation, fallible, true);
}
/**
* Formats an error message from a {@link G2Fallible} instance, optionally
* including the details (which may contain sensitive PII information) as
* part of the result.
*
* @param operation The name of the operation from the native API interface
* that was attempted but failed.
* @param fallible The {@link G2Fallible} from which to extract the error
* message.
* @param includeDetails true
to include the details of the failure
* failure in the resultant message, and false
* to exclude them (usually to avoid logging sensitive
* information).
* @return The multi-line formatted log message.
*/
public static String formatError(String operation,
G2Fallible fallible,
boolean includeDetails)
{
int errorCode = fallible.getLastExceptionCode();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
pw.println();
pw.println("Operation Failed : " + operation);
pw.println("Error Code : " + errorCode);
if (includeDetails) {
String message = fallible.getLastException();
pw.println("Reason : " + message);
}
pw.println();
pw.flush();
return sw.toString();
}
/**
* Logs an error message from a {@link G2Fallible} instance, including the
* details (which may contain sensitive PII information) as part of the
* result.
*
* @param operation The name of the operation from the native API interface
* that was attempted but failed.
* @param fallible The {@link G2Fallible} from which to extract the error
* message.
*/
public static void logError(String operation,
G2Fallible fallible)
{
logError(operation, fallible, true);
}
/**
* Logs an error message from a {@link G2Fallible} instance, optionally
* including the details (which may contain sensitive PII information) as
* part of the result.
*
* @param operation The name of the operation from the native API interface
* that was attempted but failed.
* @param fallible The {@link G2Fallible} from which to extract the error
* message.
* @param includeDetails true
to include the details of the failure
* failure in the resultant message, and false
* to exclude them (usually to avoid logging sensitive
* information).
*/
public static void logError(String operation,
G2Fallible fallible,
boolean includeDetails)
{
String message = formatError(operation, fallible, includeDetails);
System.err.println(message);
}
/**
* Convert a throwable to a {@link Long} value so we don't keep a reference
* to what could be a complex exception object.
* @param t The throwable to convert.
* @return The long hash representation to identify the throwable instance.
*/
private static Long throwableToLong(Throwable t) {
if (t == null) return null;
if (t.getClass() == RuntimeException.class && t.getCause() != null) {
t = t.getCause();
}
long hash1 = (long) System.identityHashCode(t);
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.flush();
long hash2 = (long) sw.toString().hashCode();
return ((hash1 << 32) | hash2);
}
/**
* Checks if the specified {@link Throwable} is the last logged exception
* or if the class of specified {@link Throwable} is {@link RuntimeException}
* and it has a {@linkplain Throwable#getCause()} then if the cause is the
* last logged exception. This is handy for telling if the exception has already been logged by a
* deeper level of the stack trace.
* @param t The {@link Throwable} to check.
* @return true
if it is the last logged exception, otherwise
* false
.
*/
public static boolean isLastLoggedException(Throwable t) {
if (t == null) return false;
if (LAST_LOGGED_EXCEPTION.get() == null) return false;
long value = throwableToLong(t);
return (LAST_LOGGED_EXCEPTION.get() == value);
}
/**
* Sets the specified {@link Throwable} as the last logged exception.
* This is handy for telling if the exception has already been logged by a
* deeper level of the stack trace.
*
* @param t The {@link Throwable} to set as the last logged exception.
*/
public static void setLastLoggedException(Throwable t) {
LAST_LOGGED_EXCEPTION.set(throwableToLong(t));
}
/**
* Sets the last logged exception and rethrows the specified exception.
* @param t The {@link Throwable} describing the exception.
* @throws T To rethrow the specified exception.
* @param The type of the specified exception which is then thrown.
*/
public static void setLastLoggedAndThrow(T t)
throws T
{
setLastLoggedException(t);
throw t;
}
/**
* Conditionally logs to stderr the specified {@link Throwable} is not the {@linkplain
* #isLastLoggedException(Throwable) last logged exception} and then rethrows
* it.
*
* @param t The {@link Throwable} to log and throw.
* @return Never returns anything since it always throws.
* @throws T To rethrow the specified exception.
* @param The type of the specified exception which is then thrown.
*/
public static T logOnceAndThrow(T t)
throws T
{
if (!isLastLoggedException(t)) {
t.printStackTrace();
}
setLastLoggedAndThrow(t);
return null; // we never get here
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy