/* * Copyright 2014-2019 JKOOL, LLC. * * 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.jkoolcloud.tnt4j; import java.io.IOException; import java.lang.management.ManagementFactory; import java.lang.management.ThreadMXBean; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import com.jkoolcloud.tnt4j.config.DefaultConfigFactory; import com.jkoolcloud.tnt4j.config.TrackerConfig; import com.jkoolcloud.tnt4j.core.*; import com.jkoolcloud.tnt4j.dump.*; import com.jkoolcloud.tnt4j.selector.TrackingSelector; import com.jkoolcloud.tnt4j.sink.*; import com.jkoolcloud.tnt4j.source.Source; import com.jkoolcloud.tnt4j.source.SourceType; import com.jkoolcloud.tnt4j.tracker.*; import com.jkoolcloud.tnt4j.utils.Useconds; import com.jkoolcloud.tnt4j.utils.Utils; /** * <p> * {@code TrackingLogger} is a helper class with calls to {@link Tracker} logging interface. * </p> * Application should use this helper class instead of obtaining a {@link Tracker} logger instance per thread using * {@link TrackerFactory}. {@code TrackingLogger} obtains the {@link Tracker} logger instance and stores it in thread * local associated for each thread. * * <p> * A {@link TrackingEvent} represents a specific tracking event that application creates for every discrete activity * such as JDBC, JMS, SOAP or any other relevant application activity. Application developers must obtain a * {@link Tracker} instance via {@link TrackerFactory}, create instances of {@link TrackingEvent} and use {@code log()} * calls to report tracking activities, events and log messages. * * <p> * {@link TrackingActivity} {@code start()/stop()} method calls used to mark application activity boundaries. * Applications must create instances of {@link TrackingEvent} using {@code TrackingLogger.newEvent()} method to time * individual sub-activities and report them using {@code TrackerLogger.tnt()} method call. * </p> * * <p> * Instrumenting typical application logic: * </p> * * <pre> * {@code * TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(source); * TrackingLogger tracker = TrackingLogger.getInstance(config.build()); // register and obtain Tracker logger instance * TrackingActivity activity = tracker.newActivity(); // create a new activity instance * activity.start(); // start application activity timing * TrackingEvent event = tracker.newEvent(OpLevel.INFO, "SQL-SELECT", "SQL customer lookup"); // create a tracking event * TrackingEvent jms_event = tracker.newEvent(OpLevel.INFO, OpType.SEND, "JmsSend", "correlator", "Sending Message"); // create a tracking event * event.start(); // start timing a tracking event * try { * ... * ... * event.stop(); // stop timing tracking event * jms_event.start(); * ... * ... * jms_event.stop(); // stop timing tracking event * } catch (SQLException e) { * event.stop(e); // stop timing tracking event and associate an exception * jms_event.stop(e); // stop timing tracking event and associate an exception * ... * } finally { * activity.stop(); // end activity timing * activity.tnt(event); // track and trace tracking event within given activity * activity.tnt(jms_event); // track and trace tracking event within given activity * tracker.tnt(activity); // report a tracking activity * } * } * </pre> * * Source may take advantage of {@code TrackingLogger} conditional logging using {@code TrackingLogger.isSet()} based on * applications specific tokens. Below is an example of conditional logging: * * <pre> * {@code * TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(source); * TrackingLogger tracker = TrackingLogger.getInstance(config.build()); // register and obtain Tracker logger instance * TrackingActivity activity = tracker.newActivity(); // create a new activity instance * activity.start(); // start application activity timing * TrackingEvent event = tracker.newEvent(OpLevel.NOTICE, "SQL-SELECT", "SQL customer lookup"); // create a tracking event * TrackingEvent jms_event = tracker.newEvent(OpLevel.NOTICE, OpType.SEND, "JmsSend", "correlator", "Sending Message"); // create a tracking event * event.start(); // start timing a tracking event * try { * ... * ... * event.stop(); // stop timing tracking event * jms_event.start(); * ... * ... * jms_event.stop(); // stop timing tracking event * } catch (SQLException e) { * event.stop(e); // stop timing tracking event and associate an exception * jms_event.stop(e); // stop timing tracking event and associate an exception * ... * } finally { * activity.stop(); // end activity timing * // conditional logging using isSet() method to check if a given token matches * if (tracker.isSet(OpLevel.INFO, "com.jkoolcloud.appl.corr", "correlator")) { * activity.tnt(event); // track and trace tracking event within given activity * activity.tnt(jms_event); // track and trace tracking event within given activity * } * tracker.tnt(activity); // report a tracking activity * } * } * </pre> * * {@code TrackingLogger} provides a capability to simplify and automate application specific dump handling. An * application dump is a collection of application's internal metrics that can be used for problem diagnostics. Source * must create an instance of {@code DumpProvider} and register it with {@code TrackingLogger} optionally associate it * with a given dump destination {@code DumpSink}(where dump is written to). Dumps can be generated using * {@code TrackingLogger.dump()} or can be triggered on JVM shutdown using {@code TrackingLogger.dumpOnShutdown(true)}. * By default, {@code TrackingLogger} uses file based {@code DefaultDumpSinkFactory} to generate instances of * {@code DumpSink}. * * <pre> * {@code * // associated dump provider with a default dump destination (file) * TrackingLogger.addDumpProvider(new MyDumpProvider()); * TrackingLogger.dumpOnShutdown(true); * ... * // associated dump provider with a user define dump file * TrackingLogger.addDumpProvider(TrackingLogger.getDumpDestinationFactory().getInstance("my-dump.log"), new MyDumpProvider()); * TrackingLogger.dumpOnShutdown(true); * ... * TrackingLogger.dumpState(); // MyDumpProvider will be called when dumpState() is called. * } * </pre> * * * @see OpLevel * @see OpType * @see Tracker * @see TrackingEvent * @see TrackingActivity * @see TrackerFactory * @see DumpProvider * @see DumpSink * @see DumpListener * @see SinkErrorListener * * @version $Revision: 21 $ * */ public class TrackingLogger implements Tracker { private static final String TRACKER_CONFIG = System.getProperty("tnt4j.tracking.logger.config"); private static final String TRACKER_SOURCE = System.getProperty("tnt4j.tracking.logger.source", TrackingLogger.class.getName()); private static final ConcurrentHashMap<DumpProvider, List<DumpSink>> DUMP_DEST_TABLE = new ConcurrentHashMap<>(49); private static final Map<TrackingLogger, StackTraceElement[]> TRACKERS = Collections .synchronizedMap(new WeakHashMap<>(89)); private static final List<DumpProvider> DUMP_PROVIDERS = new ArrayList<>(10); private static final List<DumpSink> DUMP_DESTINATIONS = new ArrayList<>(10); private static final List<DumpListener> DUMP_LISTENERS = new ArrayList<>(10); private static final DumpHook dumpHook = new DumpHook(); private static final FlushShutdown flushShutdown = new FlushShutdown(); private static DumpSinkFactory dumpFactory = null; private static DumpSink defaultDumpSink = null; private static TrackerFactory factory = null; private Tracker logger; private TrackingSelector selector; static { // load configuration and initialize default factories initJavaTiming(); TrackerConfig config = DefaultConfigFactory.getInstance() .getConfig(TRACKER_SOURCE, SourceType.APPL, TRACKER_CONFIG).build(); DefaultEventSinkFactory.setDefaultEventSinkFactory(config.getDefaultEvenSinkFactory()); factory = config.getTrackerFactory(); dumpFactory = config.getDumpSinkFactory(); defaultDumpSink = dumpFactory.getInstance(); boolean enableDefaultDumpProviders = config.getBoolean("tracker.dump.provider.default", Boolean.getBoolean("tnt4j.dump.provider.default")); boolean dumpOnVmHook = config.getBoolean("tracker.dump.on.vm.shutdown", Boolean.getBoolean("tnt4j.dump.on.vm.shutdown")); boolean dumpOnException = config.getBoolean("tracker.dump.on.exception", Boolean.getBoolean("tnt4j.dump.on.exception")); boolean flushOnVmHook = config.getBoolean("tracker.flush.on.vm.shutdown", Boolean.getBoolean("tnt4j.flush.on.vm.shutdown")); if (enableDefaultDumpProviders) { addDumpProvider(defaultDumpSink, new PropertiesDumpProvider(Utils.VM_NAME)); addDumpProvider(defaultDumpSink, new MXBeanDumpProvider(Utils.VM_NAME)); addDumpProvider(defaultDumpSink, new ThreadDumpProvider(Utils.VM_NAME)); addDumpProvider(defaultDumpSink, new ThreadDeadlockDumpProvider(Utils.VM_NAME)); addDumpProvider(defaultDumpSink, new LoggerDumpProvider(Utils.VM_NAME)); } if (dumpOnVmHook) { dumpOnShutdown(dumpOnVmHook); } if (dumpOnException) { dumpOnUncaughtException(); } if (flushOnVmHook) { flushOnShutdown(flushOnVmHook); } } /** Cannot instantiate. */ private TrackingLogger(Tracker trg) { logger = trg; selector = logger.getTrackingSelector(); } /** * Check and enable java timing for use by activities * */ private static void initJavaTiming() { ThreadMXBean tmbean = ManagementFactory.getThreadMXBean(); boolean cpuTimingSupported = tmbean.isCurrentThreadCpuTimeSupported(); if (cpuTimingSupported) { tmbean.setThreadCpuTimeEnabled(cpuTimingSupported); } boolean contTimingSupported = tmbean.isThreadContentionMonitoringSupported(); if (contTimingSupported) { tmbean.setThreadContentionMonitoringEnabled(contTimingSupported); } } @Override protected void finalize() throws Throwable { try { close(); } finally { super.finalize(); } } private void checkState() { if (logger == null) { throw new IllegalStateException("tracker not initialized"); } } private static void registerTracker(TrackingLogger tracker) { TRACKERS.put(tracker, Thread.currentThread().getStackTrace()); } /** * Obtain an allocation stack trace for the specified logger instance * * @param logger * instance * * @return an allocation stack trace for the logger instance */ public static StackTraceElement[] getTrackerStackTrace(TrackingLogger logger) { return TRACKERS.get(logger); } /** * Obtain an a list of all registered/active logger instances. * * @return a list of all active tracking logger instances */ public static List<TrackingLogger> getAllTrackers() { synchronized (TRACKERS) { ArrayList<TrackingLogger> copy = new ArrayList<>(TRACKERS.size()); for (TrackingLogger logger : TRACKERS.keySet()) { if (logger != null) { copy.add(logger); } } return copy; } } /** * Flush all available trackers * */ public static void flushAll() { List<TrackingLogger> trackers = getAllTrackers(); for (TrackingLogger logger : trackers) { try { EventSink sink = logger.getEventSink(); sink.flush(); } catch (IOException e) { } } } /** * Shutdown all available trackers and sinks. */ public static void shutdownAll() { List<TrackingLogger> trackers = getAllTrackers(); for (TrackingLogger logger : trackers) { shutdown(logger); } } /** * Shutdown defined tracking logger. * * @param logger * tracking logger to shut down */ public static void shutdown(TrackingLogger logger) { try { EventSink sink = logger.getEventSink(); if (sink instanceof IOShutdown) { IOShutdown shut = (IOShutdown) sink; shut.shutdown(null); } } catch (IOException e) { } } /** * Obtain a stack trace list for all tracker allocations to determine where the tracker instances have been * instantiated * * @return a list of stack traces for each allocated tracker */ public static List<StackTraceElement[]> getAllTrackerStackTrace() { synchronized (TRACKERS) { ArrayList<StackTraceElement[]> copy = new ArrayList<>(TRACKERS.size()); for (StackTraceElement[] trace : TRACKERS.values()) { if (trace != null) { copy.add(trace); } } return copy; } } /** * Obtain an instance of {@code TrackingLogger} logger. * * @param config * tracking configuration to be used to create a tracker instance * @return tracking logger instance * @see TrackerConfig */ public static TrackingLogger getInstance(TrackerConfig config) { TrackingLogger tracker = new TrackingLogger(factory.getInstance(config)); registerTracker(tracker); return tracker; } /** * Obtain an instance of {@code TrackingLogger} logger. * * @param sourceName * application source name associated with this logger * @return tracking logger instance * @see TrackerConfig */ public static TrackingLogger getInstance(String sourceName) { TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(sourceName); TrackingLogger tracker = new TrackingLogger(factory.getInstance(config.build())); registerTracker(tracker); return tracker; } /** * Obtain an instance of {@code TrackingLogger} logger. * * @param sourceName * application source name associated with this logger * @param configMap * configuration map containing source/properties configuration * @return tracking logger instance * @see TrackerConfig */ public static TrackingLogger getInstance(String sourceName, Map<String, Properties> configMap) { TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(sourceName, SourceType.APPL, configMap); TrackingLogger tracker = new TrackingLogger(factory.getInstance(config.build())); registerTracker(tracker); return tracker; } /** * Obtain an instance of {@code TrackingLogger} logger. * * @param sourceName * application source name associated with this logger * @param type * application source type associated with this logger * @return tracking logger instance * @see TrackerConfig */ public static TrackingLogger getInstance(String sourceName, SourceType type) { TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(sourceName, type); TrackingLogger tracker = new TrackingLogger(factory.getInstance(config.build())); registerTracker(tracker); return tracker; } /** * Obtain an instance of {@code TrackingLogger} logger. * * @param sourceName * application source name associated with this logger * @param type * application source type associated with this logger * @param configMap * configuration map containing source/properties configuration * @return tracking logger instance * @see TrackerConfig */ public static TrackingLogger getInstance(String sourceName, SourceType type, Map<String, Properties> configMap) { TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(sourceName, type, configMap); TrackingLogger tracker = new TrackingLogger(factory.getInstance(config.build())); registerTracker(tracker); return tracker; } /** * Obtain an instance of {@code TrackingLogger} logger based on a given class. * * @param clazz * application class used as source name * @return tracking logger instance * @see TrackerConfig */ public static TrackingLogger getInstance(Class<?> clazz) { TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(clazz); TrackingLogger tracker = new TrackingLogger(factory.getInstance(config.build())); registerTracker(tracker); return tracker; } /** * Obtain an instance of {@code TrackingLogger} logger based on a given class. * * @param clazz * application class used as source name * @param configMap * configuration map containing source/properties configuration * @return tracking logger instance * @see TrackerConfig */ public static TrackingLogger getInstance(Class<?> clazz, Map<String, Properties> configMap) { TrackerConfig config = DefaultConfigFactory.getInstance().getConfig(clazz, SourceType.APPL, configMap); TrackingLogger tracker = new TrackingLogger(factory.getInstance(config.build())); registerTracker(tracker); return tracker; } /** * Register a user defined tracker factory. Default is {@code DefaultTrackerFactory}. * * @param fac * User defined tracker factory * @see TrackerFactory * @see DefaultTrackerFactory */ public static void setTrackerFactory(TrackerFactory fac) { factory = (fac != null ? fac : factory); } /** * Register a user defined dump destination factory used to generate instances of {@code DumpSink}. Default is * {@code DefaultDumpSinkFactory}. * * @param defFac * User default dump destination factory * @param defDest * User default dump destination * @see DumpSink * @see DumpSinkFactory * @see DefaultDumpSinkFactory */ public static void setDefaultDumpConfig(DumpSinkFactory defFac, DumpSink defDest) { dumpFactory = (defFac != null ? defFac : dumpFactory); defaultDumpSink = (defDest != null ? defDest : defaultDumpSink); } /** * Return currently registered dump sink factory. Default is {@code DefaultDumpSinkFactory}. * * @return currently registered dump sink factory * @see DumpSinkFactory * @see DefaultDumpSinkFactory */ public static DumpSinkFactory getDumpSinkFactory() { return dumpFactory; } /** * Determine of a particular sev/key/value combination is trackable. Use this method to determine if tracking is * enabled/disabled for a specific key/value pair. Example, checking if order id "723772" is trackable: * * {@code logger.isSet(OpLevel.INFO, "orderapp.order.id", "723772");} * * @param sev * severity of to be checked * @param key * key associated with tracking activity * @param value * associated value with a given key * * @return true of combination is set, false otherwise * @see OpLevel */ public boolean isSet(OpLevel sev, Object key, Object value) { if (logger != null) { return selector.isSet(sev, key, value); } return false; } /** * Determine of a particular sev/key is trackable. Use this method to determine if tracking is enabled/disabled for * a specific severity. This call is equivalent to {@code logger.isSet(sev, key, null);} * * @param sev * severity of to be checked * * @param key * key to be checked for being trackable * * @return true of combination is set, false otherwise * @see OpLevel */ public boolean isSet(OpLevel sev, Object key) { if (logger != null) { return selector.isSet(sev, key); } return false; } /** * Determine if a particular sev for the registered application name used in {@code TrackingLogger.getInstance()} * call. Use this method to determine if tracking is enabled/disabled for a specific severity. This call is * equivalent to {@code logger.getTracker().getEventSink().isSet(sev)} * * @param sev * severity of to be checked * * @return true of combination is set, false otherwise * @see OpLevel */ public boolean isSet(OpLevel sev) { if (logger != null) { return logger.getEventSink().isSet(sev); } return false; } /** * Set sev/key/value combination for tracking * * @param sev * severity of to be checked * @param key * key associated with tracking activity * @param value * associated value with a given key * * @see OpLevel */ public void set(OpLevel sev, Object key, Object value) { if (logger != null) { selector.set(sev, key, value); } } /** * Set sev/key combination for tracking. This is the same as calling {@code set(sev, key, null)}, where value is * null. * * @param sev * severity of to be checked * @param key * key associated with tracking activity * * @see OpLevel */ public void set(OpLevel sev, Object key) { if (logger != null) { selector.set(sev, key); } } /** * Get value associated with a given key from the tracking selector repository. * * @param key * key associated with tracking activity * @return value for specified key, or {@code null} if key not found */ public Object get(Object key) { if (logger != null) { return selector.get(key); } return null; } /** * Obtain a list of keys available in the selector * * @return iterator containing all available keys */ public Iterator<? extends Object> getKeys() { if (logger != null) { return selector.getKeys(); } return null; } /** * Close this instance of {@code TrackingLogger}. Existing {@link Tracker} logger (if already opened) is closed and * released. * * @see TrackerConfig */ @Override public void close() { if (logger != null) { factory.close(logger); TRACKERS.remove(this); } } /** * Log a single message with a given severity level and a number of user supplied arguments. Message pattern is * based on the format defined by {@code MessageFormat}. This logging type is more efficient than string * concatenation. * * <pre> * {@code * logger.log(OpLevel.DEBUG, "My message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param level * severity level * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat * @throws IllegalStateException * when tracker is not initialized */ @Override public void log(OpLevel level, String msg, Object... args) { checkState(); logger.log(level, msg, args); } /** * Log a single DEBUG message and a number of user supplied arguments. Message pattern is based on the format * defined by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.debug("My message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void debug(String msg, Object... args) { log(OpLevel.DEBUG, msg, args); } /** * Log a single TRACE message and a number of user supplied arguments. Message pattern is based on the format * defined by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.trace("My message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void trace(String msg, Object... args) { log(OpLevel.TRACE, msg, args); } /** * Log a single ERROR message and a number of user supplied arguments. Message pattern is based on the format * defined by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.error("My error message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void error(String msg, Object... args) { log(OpLevel.ERROR, msg, args); } /** * Log a single FATAL message and a number of user supplied arguments. Message pattern is based on the format * defined by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.fatal("My error message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void fatal(String msg, Object... args) { log(OpLevel.FATAL, msg, args); } /** * Log a single HALT message and a number of user supplied arguments. Message pattern is based on the format defined * by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.halt("My error message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void halt(String msg, Object... args) { log(OpLevel.HALT, msg, args); } /** * Log a single WARNING message and a number of user supplied arguments. Message pattern is based on the format * defined by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.warn("My message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void warn(String msg, Object... args) { log(OpLevel.WARNING, msg, args); } /** * Log a single INFO message and a number of user supplied arguments. Message pattern is based on the format defined * by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.info("My message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void info(String msg, Object... args) { log(OpLevel.INFO, msg, args); } /** * Log a single NOTICE message and a number of user supplied arguments. Message pattern is based on the format * defined by {@code MessageFormat}. This logging type is more efficient than string concatenation. * * <pre> * {@code * logger.notice("My message arg={0}, arg={1}", parm1, parm2); * } * </pre> * * @param msg * message or message pattern * @param args * user defined arguments supplied along side given message * @see OpLevel * @see java.text.MessageFormat */ public void notice(String msg, Object... args) { log(OpLevel.NOTICE, msg, args); } /** * Report a single tracking activity. Call after instance of {@link TrackingActivity} has been completed using * {@code TrackingActivity.stop()} and {@code TrackingActivity.tnt()} calls. * * @param activity * tracking activity to be reported * @throws IllegalStateException * when tracker is not initialized * @see TrackingActivity */ @Override public void tnt(TrackingActivity activity) { if (activity == null) { return; } checkState(); logger.tnt(activity); } /** * Report a single tracking event as a single activity. Call after instance of {@link TrackingEvent} has been * completed using {@code TrackingEvent.stop()} call. * * @param event * tracking event to be reported as a single activity * @throws IllegalStateException * when tracker is not initialized * @see TrackingEvent */ @Override public void tnt(TrackingEvent event) { if (event == null) { return; } checkState(); logger.tnt(event); } /** * Report a single snapshot. * * @param snapshot * snapshot to be tracked and logged * @throws IllegalStateException * when tracker is not initialized * @see Snapshot * @see Property */ @Override public void tnt(Snapshot snapshot) { if (snapshot == null) { return; } checkState(); logger.tnt(snapshot); } /** * Report a single tracking event * * @param severity * severity level of the reported message * @param opName * operation name associated with the event message * @param correlator * event correlator * @param msg * event text message * @param args * argument list, exception passed along side given message * @throws IllegalStateException * when tracker is not initialized * @see TrackingActivity * @see OpLevel */ public void tnt(OpLevel severity, String opName, String correlator, String msg, Object... args) { tnt(severity, OpType.CALL, opName, correlator, 0, msg, args); } /** * Report a single tracking event * * @param severity * severity level of the reported message * @param opType * operation type * @param opName * operation name associated with the event message * @param correlator * event correlator * @param elapsed * elapsed time of the event in microseconds. * @param msg * event text message * @param args * argument list, exception passed along side given message * @throws IllegalStateException * when tracker is not initialized * @see TrackingActivity * @see OpLevel */ public void tnt(OpLevel severity, OpType opType, String opName, String correlator, long elapsed, String msg, Object... args) { tnt(severity, opType, opName, correlator, null, elapsed, msg, args); } /** * Report a single tracking event * * @param severity * severity level of the reported message * @param opType * operation type * @param opName * operation name associated with the event message * @param correlator * event correlator * @param tag * message tag * @param elapsed * elapsed time of the event in microseconds. * @param msg * event text message * @param args * argument list, exception passed along side given message * @throws IllegalStateException * when tracker is not initialized * @see TrackingActivity * @see OpLevel */ public void tnt(OpLevel severity, OpType opType, String opName, String correlator, String tag, long elapsed, String msg, Object... args) { checkState(); TrackingEvent event = logger.newEvent(severity, opType, opName, correlator, tag, msg, args); Throwable ex = Utils.getThrowable(args); event.stop(ex != null ? OpCompCode.WARNING : OpCompCode.SUCCESS, 0, ex, Useconds.CURRENT.get(), elapsed); logger.tnt(event); } /** * Report a single tracking event using a binary message body * * @param severity * severity level of the reported message * @param opName * operation name associated with the event message * @param correlator * event correlator * @param msg * event binary message * @param args * argument list, exception passed along side given message * @throws IllegalStateException * when tracker is not initialized * @see TrackingActivity * @see OpLevel */ public void tnt(OpLevel severity, String opName, String correlator, byte[] msg, Object... args) { tnt(severity, OpType.CALL, opName, correlator, 0, msg, args); } /** * Report a single tracking event using a binary message body * * @param severity * severity level of the reported message * @param opType * operation type * @param opName * operation name associated with the event message * @param correlator * event correlator * @param elapsed * elapsed time of the event in microseconds. * @param msg * event binary message * @param args * argument list, exception passed along side given message * @throws IllegalStateException * when tracker is not initialized * @see TrackingActivity * @see OpLevel */ public void tnt(OpLevel severity, OpType opType, String opName, String correlator, long elapsed, byte[] msg, Object... args) { tnt(severity, opType, opName, correlator, null, elapsed, msg, args); } /** * Report a single tracking event using a binary message body * * @param severity * severity level of the reported message * @param opType * operation type * @param opName * operation name associated with the event message * @param correlator * event correlator * @param tag * message tag * @param elapsed * elapsed time of the event in microseconds. * @param msg * event binary message * @param args * argument list, exception passed along side given message * @throws IllegalStateException * when tracker is not initialized * @see TrackingActivity * @see OpLevel */ public void tnt(OpLevel severity, OpType opType, String opName, String correlator, String tag, long elapsed, byte[] msg, Object... args) { checkState(); TrackingEvent event = logger.newEvent(severity, opType, opName, correlator, tag, msg, args); Throwable ex = Utils.getThrowable(args); event.stop(ex != null ? OpCompCode.WARNING : OpCompCode.SUCCESS, 0, ex, Useconds.CURRENT.get(), elapsed); logger.tnt(event); } @Override public Dataset newDataset(String name) { checkState(); return logger.newDataset(name); } @Override public Dataset newDataset(String cat, String name) { checkState(); return logger.newDataset(cat, name); } @Override public Snapshot newSnapshot(String name) { checkState(); return logger.newSnapshot(name); } @Override public Snapshot newSnapshot(String cat, String name) { checkState(); return logger.newSnapshot(cat, name); } @Override public Snapshot newSnapshot(String cat, String name, OpLevel level) { checkState(); return logger.newSnapshot(cat, name, level); } @Override public Property newProperty(String key, Object val) { checkState(); return logger.newProperty(key, val); } @Override public Property newProperty(String key, Object val, String valType) { checkState(); return logger.newProperty(key, val, valType); } @Override public TrackingActivity newActivity() { checkState(); return logger.newActivity(OpLevel.INFO); } @Override public TrackingActivity newActivity(OpLevel level) { checkState(); return logger.newActivity(level); } @Override public TrackingActivity newActivity(OpLevel level, String name) { checkState(); return logger.newActivity(level, name); } @Override public TrackingActivity newActivity(OpLevel level, String name, String signature) { checkState(); return logger.newActivity(level, name, signature); } @Override public TrackingEvent newEvent(String opName, String msg, Object... args) { checkState(); return logger.newEvent(opName, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, String opName, String correlator, String msg, Object... args) { checkState(); return logger.newEvent(severity, opName, correlator, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, OpType opType, String opName, String correlator, String tag, String msg, Object... args) { checkState(); return logger.newEvent(severity, opType, opName, correlator, tag, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, String opName, String correlator, byte[] msg, Object... args) { checkState(); return logger.newEvent(severity, opName, correlator, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, OpType opType, String opName, String correlator, String tag, byte[] msg, Object... args) { checkState(); return logger.newEvent(severity, opType, opName, correlator, tag, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, String opName, Collection<String> correlators, String msg, Object... args) { checkState(); return logger.newEvent(severity, opName, correlators, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, OpType opType, String opName, Collection<String> correlators, Collection<String> tags, String msg, Object... args) { checkState(); return logger.newEvent(severity, opType, opName, correlators, tags, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, String opName, Collection<String> correlators, byte[] msg, Object... args) { checkState(); return logger.newEvent(severity, opName, correlators, msg, args); } @Override public TrackingEvent newEvent(OpLevel severity, OpType opType, String opName, Collection<String> correlators, Collection<String> tags, byte[] msg, Object... args) { checkState(); return logger.newEvent(severity, opType, opName, correlators, tags, msg, args); } /** * Returns currently registered {@link Tracker} logger associated with the current thread. {@link Tracker} logger is * associated with the current thread after the register() call. {@link Tracker} logger instance is not thread safe. * * @return {@link Tracker} logger associated with the current thread or null of non available. * @see Tracker */ public Tracker getTracker() { return logger; } /** * Register a tracking filter associated with the tracker. Tracking filter allows consolidation of all conditional * tracking logic into a single class. * * @see TrackingFilter * @throws IllegalStateException * when tracker is not initialized */ @Override public void setTrackingFilter(TrackingFilter filter) { checkState(); logger.setTrackingFilter(filter); } /** * Add a sink log listener, which is triggered log activities occurs when writing to the event sink. * * @param listener * user supplied sink log listener * @see SinkErrorListener * @throws IllegalStateException * when tracker is not initialized */ public void addSinkLogEventListener(SinkLogEventListener listener) { checkState(); logger.getEventSink().addSinkLogEventListener(listener); } /** * Remove a sink log listener, which is triggered log activities occurs when writing to the event sink. * * @param listener * user supplied sink log listener * @throws IllegalStateException * when tracker is not initialized * @see SinkErrorListener */ public void removeSinkLogEventListener(SinkLogEventListener listener) { checkState(); logger.getEventSink().removeSinkLogEventListener(listener); } /** * Add and register a sink error listener, which is triggered error occurs when writing to the event sink. * * @param listener * user supplied sink error listener * @see SinkErrorListener * @throws IllegalStateException * when tracker is not initialized */ public void addSinkErrorListener(SinkErrorListener listener) { checkState(); logger.getEventSink().addSinkErrorListener(listener); } /** * Remove a sink error listener, which is triggered error occurs when writing to the event sink. * * @param listener * user supplied sink error listener * @see SinkErrorListener */ public void removeSinkErrorListener(SinkErrorListener listener) { checkState(); logger.getEventSink().removeSinkErrorListener(listener); } /** * Add and register a sink filter, which is used to filter out events written to the underlying sink. Sink event * listeners get called every time an event/activity or message is written to the underlying event sink. * * @param filter * user supplied sink filter * @see SinkEventFilter * @throws IllegalStateException * when tracker is not initialized */ public void addSinkEventFilter(SinkEventFilter filter) { checkState(); logger.getEventSink().addSinkEventFilter(filter); } /** * Remove sink filter, which is used to filter out events written to the underlying sink. * * @param filter * user supplied sink filter * @throws IllegalStateException * when tracker is not initialized * @see SinkEventFilter */ public void removeSinkEventFilter(SinkEventFilter filter) { checkState(); logger.getEventSink().removeSinkEventFilter(filter); } /** * Add and register a dump listener, which is triggered when dump is generated by {@code dump()} call. * * @param lst * user supplied dump listener * @see DumpListener */ public static void addDumpListener(DumpListener lst) { DUMP_LISTENERS.add(lst); } /** * Remove a dump listener, which is triggered when dump is generated by {@code dump()} call. * * @param lst * user supplied dump listener * @see DumpListener */ public static void removeDumpListener(DumpListener lst) { DUMP_LISTENERS.remove(lst); } /** * Add and register a dump provider. Instances of {@code DumpProvider} provide implementation for underlying classes * that generate application specific dumps. By default supplied dump provider is associated with a default * {@code DumpSink}. * * @param dp * user supplied dump provider * * @see DumpProvider * @see DumpSink */ public static void addDumpProvider(DumpProvider dp) { addDumpProvider(defaultDumpSink, dp); } /** * Add and register a dump provider with a user specified {@code DumpSink}. Instances of {@code DumpProvider} * interface provide implementation for underlying classes that generate application specific dumps. This dump * provider will be triggered for the specified {@code DumpSink} only. Instance of {@code DumpSink} can be created * by {@code DumpDestinatonFactory}. By default {@code PropertiesDumpProvider}, {@code MXBeanDumpProvider}, * {@code ThreadDumpProvider}, {@code ThreadDeadlockDumpProvider} are auto registered with {@code FileDumpSink} * during initialization of {@code TrackingLogger} class. * * @param df * user supplied dump destination associated with dump provider * @param dp * user supplied dump provider * * @see DumpProvider * @see DumpSink * @see DumpSinkFactory * @see PropertiesDumpProvider * @see MXBeanDumpProvider * @see ThreadDumpProvider * @see ThreadDeadlockDumpProvider */ public static synchronized void addDumpProvider(DumpSink df, DumpProvider dp) { // add to dump->dest table second List<DumpSink> destList = DUMP_DEST_TABLE.get(dp); if (destList == null) { destList = new ArrayList<>(10); DUMP_PROVIDERS.add(dp); } boolean exists = destList.contains(df); if (!exists) { destList.add(df); } exists = DUMP_DESTINATIONS.contains(df); if (!exists) { DUMP_DESTINATIONS.add(df); } DUMP_DEST_TABLE.putIfAbsent(dp, destList); } /** * Generate dumps backed by registered {@code DumpProvider} instances written to registered {@code DumpSink} * instances. The method first opens all registered dump destinations and then iterates over all dump providers to * obtain dumps of instance {@code DumpCollection}. Registered instances of {@code DumpListener} are triggered for * before, after, error, complete conditions during this call. * * @see DumpListener * @see DumpCollection * @see DumpProvider * @see DumpSink * @see DumpSinkFactory */ public static synchronized void dumpState() { dumpState(null); } /** * Generate dumps backed by registered {@code DumpProvider} instances written to registered {@code DumpSink} * instances. The method first opens all registered dump destinations and then iterates over all dump providers to * obtain dumps of instance {@code DumpCollection}. Registered instances of {@code DumpListener} are triggered for * before, after, error, complete conditions during this call. * * @param reason * reason why dump is generated * * @see DumpListener * @see DumpCollection * @see DumpProvider * @see DumpSink * @see DumpSinkFactory */ public static synchronized void dumpState(Throwable reason) { try { openDumpSinks(); for (DumpProvider dumpProvider : DUMP_PROVIDERS) { List<DumpSink> dlist = DUMP_DEST_TABLE.get(dumpProvider); DumpCollection dump = null; Throwable error = reason; try { dump = dumpProvider.getDump(); if (dump != null && reason != null) { dump.setReason(reason); } notifyDumpListeners(DumpProvider.DUMP_BEFORE, dumpProvider, dump, dlist, reason); if (dump != null) { for (DumpSink dest : dlist) { dest.write(dump); } } } catch (Throwable ex) { ex.initCause(reason); error = ex; } finally { notifyDumpListeners(DumpProvider.DUMP_AFTER, dumpProvider, dump, dlist, error); } } } finally { closeDumpSinks(); } } /** * Enable/disable VM shutdown hook that will automatically trigger a dump. * * @param flag * enable/disable VM shutdown hook that triggers a dump */ public static void dumpOnShutdown(boolean flag) { if (flag) { Runtime.getRuntime().addShutdownHook(dumpHook); } else { Runtime.getRuntime().removeShutdownHook(dumpHook); } } /** * Enable/disable VM shutdown hook that will automatically flush all registered trackers. * * @param flag * enable/disable VM shutdown hook that triggers a flush */ public static void flushOnShutdown(boolean flag) { if (flag) { Runtime.getRuntime().addShutdownHook(flushShutdown); } else { Runtime.getRuntime().removeShutdownHook(flushShutdown); } } /** * Enable/disable {@code UncaughtExceptionHandler} hook that will automatically trigger a dump on uncaught thread * exceptions for all threads. * */ public static void dumpOnUncaughtException() { Thread.setDefaultUncaughtExceptionHandler(dumpHook); } private static void openDumpSinks() { for (DumpSink dest : DUMP_DESTINATIONS) { try { dest.open(); } catch (Throwable ex) { notifyDumpListeners(DumpProvider.DUMP_ERROR, dest, null, DUMP_DESTINATIONS, ex); } } } private static void closeDumpSinks() { try { notifyDumpListeners(DumpProvider.DUMP_COMPLETE, Thread.currentThread(), null, DUMP_DESTINATIONS); } finally { for (DumpSink dest : DUMP_DESTINATIONS) { try { dest.close(); } catch (Throwable ex) { ArrayList<DumpSink> list = new ArrayList<>(1); list.add(dest); notifyDumpListeners(DumpProvider.DUMP_ERROR, Thread.currentThread(), null, list, ex); } } } } private static void notifyDumpListeners(int type, Object source, DumpCollection dump, List<DumpSink> dlist) { notifyDumpListeners(type, source, dump, dlist, null); } private static void notifyDumpListeners(int type, Object source, DumpCollection dump, List<DumpSink> dlist, Throwable ex) { synchronized (DUMP_LISTENERS) { for (DumpListener dls : DUMP_LISTENERS) { dls.onDumpEvent(new DumpEvent(source, type, dump, dlist, ex)); } } } @Override public TrackingActivity[] getActivityStack() { checkState(); return logger.getActivityStack(); } @Override public TrackerConfig getConfiguration() { checkState(); return logger.getConfiguration(); } @Override public TrackingActivity getCurrentActivity() { checkState(); return logger.getCurrentActivity(); } @Override public TrackingActivity getRootActivity() { checkState(); return logger.getRootActivity(); } @Override public EventSink getEventSink() { checkState(); return logger.getEventSink(); } @Override public Source getSource() { checkState(); return logger.getSource(); } @Override public int getStackSize() { checkState(); return logger.getStackSize(); } @Override public StackTraceElement[] getStackTrace() { checkState(); return logger.getStackTrace(); } @Override public TrackingSelector getTrackingSelector() { checkState(); return logger.getTrackingSelector(); } @Override public boolean isOpen() { checkState(); return logger.isOpen(); } @Override public void open() throws IOException { checkState(); logger.open(); } @Override public void reopen() throws IOException { close(); open(); } @Override public Map<String, Object> getStats() { checkState(); return logger.getStats(); } @Override public KeyValueStats getStats(Map<String, Object> stats) { checkState(); return logger.getStats(stats); } @Override public void resetStats() { checkState(); logger.resetStats(); } @Override public Tracker setKeepThreadContext(boolean flag) { checkState(); return logger.setKeepThreadContext(flag); } @Override public boolean getKeepThreadContext() { checkState(); return logger.getKeepThreadContext(); } @Override public String getId() { checkState(); return logger.getId(); } @Override public String newUUID() { checkState(); return logger.newUUID(); } @Override public String newUUID(Object obj) { checkState(); return logger.newUUID(obj); } @Override public String toString() { return super.toString() + "{logger: " + logger + "}"; } }