| /******************************************************************************* |
| * Copyright (c) 2016 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.common.core.log; |
| |
| import java.text.DecimalFormat; |
| import java.text.Format; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.eclipse.jdt.annotation.Nullable; |
| |
| /** |
| * Logger helper |
| * |
| * This is a logger helper, it will allow entry-exit analysis to be much easier. |
| * |
| * The events are saved in a JSON-like message in the phase of the event. It is |
| * an event type but with extra information associated to it. Typical types can |
| * be the following. |
| * <ul> |
| * <li>Durations |
| * <ul> |
| * <li><strong>B</strong>, Begin</li> |
| * <li><strong>E</strong>, End</li> |
| * <li><strong>X</strong>, Complete, this is an event with a duration field</li> |
| * <li><strong>i</strong>, Instant / Info</li> |
| * </ul> |
| * </li> |
| * <li>Asynchronous nested messages |
| * <ul> |
| * <li><strong>b</strong>, nested begin</li> |
| * <li><strong>n</strong>, nested info</li> |
| * <li><strong>e</strong>, nested end</li> |
| * </ul> |
| * </li> |
| * <li>Flows |
| * <ul> |
| * <li><strong>s</strong>, flow begin</li> |
| * <li><strong>t</strong>, flow step (info)</li> |
| * <li><strong>f</strong>, flow end</li> |
| * </ul> |
| * </li> |
| * <li>Object tracking |
| * <ul> |
| * <li><strong>N</Strong>, Object created</li> |
| * <li><strong>D</Strong>, Object destroyed</li> |
| * </ul> |
| * </li> |
| * <li>Mark Events - events that generate markers |
| * <ul> |
| * <li><strong>R</strong>, Marker event</li> |
| * </ul> |
| * </li> |
| * <li>CounterEvents - events that count items |
| * <ul> |
| * <li><strong>C</strong>, Counter event</li> |
| * </ul> |
| * </li> |
| * </ul> |
| * <p> |
| * To use <strong>durations</strong> and/or <strong>flows</strong>, see |
| * {@link ScopeLog} and {@link FlowScopeLog}. These 2 concepts are related. |
| * Durations would typically be used to instrument simple methods, while flows |
| * would be preferred if there are links to be made with other threads. |
| * <p> |
| * To use <strong>Asynchronous nested messages</strong>, see |
| * {@link #traceAsyncStart(Logger, Level, String, String, int, Object...)}, and |
| * {@link #traceAsyncEnd(Logger, Level, String, String, int, Object...)} |
| * <p> |
| * To use <strong>Object tracking</strong>, see |
| * {@link #traceObjectCreation(Logger, Level, Object)} and |
| * {@link #traceObjectDestruction(Logger, Level, Object)} |
| * |
| * The design philosophy of this class is very heavily inspired by the trace |
| * event format of Google. The full specification is available <a |
| * href=https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/edit?pli=1#>here</a>. |
| * <p> |
| * |
| * The main goals are clarity of output and simplicity for the developer. |
| * Performance is a nice to have, but is not the main concern of this helper. A |
| * minor performance impact compared to simply logging the events is to be |
| * expected. |
| * |
| * @author Matthew Khouzam |
| * @since 3.0 |
| * @noinstantiate This class is not intended to be instantiated by clients. It |
| * is a helper class. |
| */ |
| public final class TraceCompassLogUtils { |
| |
| private static final Format FORMAT = new DecimalFormat("#.###"); //$NON-NLS-1$ |
| |
| /* |
| * Field names |
| */ |
| private static final String ARGS = "args"; //$NON-NLS-1$ |
| private static final String NAME = "name"; //$NON-NLS-1$ |
| private static final String CATEGORY = "cat"; //$NON-NLS-1$ |
| private static final String ID = "id"; //$NON-NLS-1$ |
| private static final String TID = "tid"; //$NON-NLS-1$ |
| private static final String PID = "pid"; //$NON-NLS-1$ |
| private static final String TIMESTAMP = "ts"; //$NON-NLS-1$ |
| private static final String PHASE = "ph"; //$NON-NLS-1$ |
| |
| private static final String ARGS_ERROR_MESSAGE = "Data should be in the form of key, value, key1, value1, ... TraceCompassScopeLog was supplied "; //$NON-NLS-1$ |
| private static final AtomicInteger ID_GENERATOR = new AtomicInteger(0); |
| |
| private TraceCompassLogUtils() { |
| // do nothing |
| } |
| |
| /** |
| * Scope Logger helper. This will automatically log entry and exit of the |
| * scope. This scope log will be shown under any scope enclosing it, but |
| * will not be the source, or destination of any link to other scopes. If |
| * relations should be done with other scopes, the {@link FlowScopeLog} |
| * class is more appropriate. |
| * |
| * Usage: |
| * |
| * <pre> |
| * {@code usage of ScopeLog} |
| * try (ScopeLog linksLogger = new ScopeLog(LOGGER, Level.CONFIG, "Perform Query")) { //$NON-NLS-1$ |
| * ss.updateAllReferences(); |
| * dataStore.addAll(ss.query(ts, trace)); |
| * } |
| * </pre> |
| * <p> |
| * will generate the following trace |
| * |
| * <pre> |
| * {@code trace output} |
| * INFO: {"ts":12345,"ph":"B",tid:1,"name:Perform Query"} |
| * INFO: {"ts":"12366,"ph":"E","tid":1} |
| * </pre> |
| */ |
| public static class ScopeLog implements AutoCloseable { |
| |
| private final long fThreadId; |
| private final Logger fLogger; |
| private final Level fLevel; |
| private final Map<String, Object> fData = new HashMap<>(); |
| |
| /** |
| * Scope logger constructor |
| * |
| * @param log |
| * the JUL logger to log to |
| * @param level |
| * the log level see {@link Level} |
| * @param label |
| * The label of the event pair |
| * @param args |
| * Additional messages to pass for this scope, should be in |
| * pairs key, value, key2, value2.... typically arguments. |
| * Note that these arguments will be logged only at the |
| * beginning of the scope |
| */ |
| public ScopeLog(Logger log, Level level, String label, Object... args) { |
| long time = System.nanoTime(); |
| fLogger = log; |
| fLevel = level; |
| fThreadId = Thread.currentThread().getId(); |
| fLogger.log(fLevel, (() -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'B', time, fThreadId); |
| appendName(sb, label); |
| appendArgs(sb, args); |
| sb.append('}'); |
| return sb.toString(); |
| })); |
| } |
| |
| /** |
| * Add a tag to the scope logger, will be written at the exit. This can |
| * save space on the trace by having a field appended to an event rather |
| * than writing a whole new event for a small chunk of data. |
| * |
| * If the timing information is important than it would be more |
| * appropriate to call |
| * {@link TraceCompassLogUtils#traceInstant(Logger, Level, String, Object...)} |
| * |
| * @param name |
| * the name of the field |
| * @param value |
| * The value of the field. |
| */ |
| public void addData(String name, Object value) { |
| fData.put(name, value); |
| } |
| |
| @Override |
| public void close() { |
| long time = System.nanoTime(); |
| fLogger.log(fLevel, (() -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'E', time, fThreadId); |
| return appendArgs(sb, fData).append('}').toString(); |
| })); |
| } |
| } |
| |
| /** |
| * Builder class for the {@link FlowScopeLog}. One can either set a category |
| * or a parent scope before building the flow scope log. If none is set, a |
| * default category called "null" will be used. |
| * |
| * @author Geneviève Bastien |
| */ |
| public static class FlowScopeLogBuilder { |
| |
| private final Logger fLogger; |
| private final Level fLevel; |
| private final String fLabel; |
| private final Object[] fArgs; |
| private int fId = Integer.MIN_VALUE; |
| private @Nullable String fCategory = null; |
| private @Nullable FlowScopeLog fParent = null; |
| private boolean fHasParent = false; |
| |
| /** |
| * Flow scope log builder constructor |
| * |
| * @param logger |
| * the JUL logger |
| * @param level |
| * the log level see {@link Level} |
| * @param label |
| * The label of the event pair |
| * @param args |
| * the messages to pass, should be in pairs key, value, key2, |
| * value2.... typically arguments |
| */ |
| public FlowScopeLogBuilder(Logger logger, Level level, String label, Object... args) { |
| fLogger = logger; |
| fLevel = level; |
| fLabel = label; |
| fArgs = args; |
| } |
| |
| /** |
| * Set a category for the flow scope. When building the scope, an ID |
| * will be automatically generated. |
| * |
| * This method is mutually exclusive with |
| * {@link #setParentScope(FlowScopeLog)}. Calling both will throw an |
| * exception. |
| * |
| * @param category |
| * The category of this flow |
| * @return This builder |
| */ |
| public FlowScopeLogBuilder setCategory(String category) { |
| if (fParent != null) { |
| throw new IllegalStateException("FlowScopeLogBuilder: Cannot set a category if a parent has already been set"); //$NON-NLS-1$ |
| } |
| fCategory = category; |
| return this; |
| } |
| |
| /** |
| * Set a category and ID for the flow scope. When building the scope |
| * |
| * This method is mutually exclusive with |
| * {@link #setParentScope(FlowScopeLog)}. Calling both will throw an |
| * exception. |
| * |
| * @param category |
| * The category of this flow |
| * @param id |
| * The ID of this flow |
| * @return This builder |
| */ |
| public FlowScopeLogBuilder setCategoryAndId(String category, int id) { |
| if (fParent != null) { |
| throw new IllegalStateException("FlowScopeLogBuilder: Cannot set a category if a parent has already been set"); //$NON-NLS-1$ |
| } |
| fCategory = category; |
| fId = id; |
| // Id is already set, so assume this scope has a parent, even if the |
| // parent object is not available |
| fHasParent = true; |
| return this; |
| } |
| |
| /** |
| * Set a parent scope for the flow scope to build. The scope will have |
| * the same category and ID as the parent scope. |
| * |
| * This method is mutually exclusive with {@link #setCategory(String)} |
| * and {@link #setCategoryAndId(String, int)}. Calling both will throw |
| * an exception. |
| * |
| * @param parent |
| * The parent scope |
| * @return This builder |
| */ |
| public FlowScopeLogBuilder setParentScope(FlowScopeLog parent) { |
| if (fCategory != null) { |
| throw new IllegalStateException("FlowScopeLogBuilder: Cannot set a parent scope if a category has already been set"); //$NON-NLS-1$ |
| } |
| fParent = parent; |
| return this; |
| } |
| |
| /** |
| * Build the flow scope log |
| * |
| * @return The flow scope log |
| */ |
| public FlowScopeLog build() { |
| FlowScopeLog parent = fParent; |
| if (parent != null) { |
| // Has a parent scope, so step in flow |
| return new FlowScopeLog(fLogger, fLevel, fLabel, parent.fCategory, parent.fId, false, fArgs); |
| } |
| return new FlowScopeLog(fLogger, fLevel, fLabel, String.valueOf(fCategory), (fId == Integer.MIN_VALUE ? ID_GENERATOR.incrementAndGet() : fId), !fHasParent, fArgs); |
| } |
| |
| } |
| |
| /** |
| * Flow Scope Logger helper. It will automatically log entry and exit of the |
| * scope. It can be used with other flow scopes to follow the program flow |
| * across threads. To do so, these scopes save more data, so take more disk |
| * space. If there is no inter-process/thread communication to follow, the |
| * {@link ScopeLog} class would be more appropriate. |
| * |
| * Usage: this can be used to track asynchronous threads communication. This |
| * can be used in scatter-gather/map-reduce operations as well as threads |
| * that trigger a UI Thread operation. |
| * |
| * <pre> |
| * {@code usage of FlowScopeLog} |
| * try (FlowScopeLog linksLogger = new FlowScopeLog(LOGGER, Level.CONFIG, "Perform Query", "category", 0x100)) { //$NON-NLS-1$ |
| * Display.asynchExec(()->{ |
| * try(FlowScopeLog linksLogger2 = new FlowScopeLog(LOGGER, Level.CONFIG, "Update UI", "category", linksLogger.getId()) { |
| * linksLogger.step("updating ui"); |
| * }; |
| * linksLogger.step("forked thread"); |
| * } |
| * </pre> |
| * <p> |
| * will generate the following trace (order not guaranteed) |
| * |
| * <pre> |
| * {@code trace output} |
| * INFO: {"ts":12345,"ph":"s",tid:1,"name":"Perform Query", "cat":"category", "id":256} |
| * INFO: {"ts":12346","ph":"t",tid:1,"name":"forked thread","cat":"category", "id":256} |
| * INFO: {"ts":"12366,"ph":"f","tid":1,"cat":"category", "id":256} |
| * INFO: {"ts":12400,"ph":"s",tid:0,"name":"Update UI","cat":"category", "id":256} |
| * INFO: {"ts":12416","ph":"t",tid:0,"name":"updating ui", "cat":"category", "id":256} |
| * INFO: {"ts":"12420,"ph":"f","tid":0,"cat":"category", "id":256} |
| * </pre> |
| */ |
| public static class FlowScopeLog implements AutoCloseable { |
| |
| private final long fThreadId; |
| private final Logger fLogger; |
| private final Level fLevel; |
| private final int fId; |
| private final String fCategory; |
| private final Map<String, Object> fData = new HashMap<>(); |
| |
| /** |
| * Flow scope logger constructor |
| * |
| * @param log |
| * the JUL logger |
| * @param level |
| * the log level see {@link Level} |
| * @param label |
| * The label of the event pair |
| * @param category |
| * the category of the flow events |
| * @param id |
| * The id of the flow |
| * @param startFlow |
| * Whether this flow scope object is the start of a flow, or |
| * a step |
| * @param args |
| * the messages to pass, should be in pairs key, value, key2, |
| * value2.... typically arguments |
| */ |
| private FlowScopeLog(Logger log, Level level, String label, String category, int id, boolean startFlow, Object... args) { |
| long time = System.nanoTime(); |
| fId = id; |
| fLogger = log; |
| fLevel = level; |
| fCategory = category; |
| fThreadId = Thread.currentThread().getId(); |
| fLogger.log(fLevel, (() -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'B', time, fThreadId); |
| appendName(sb, label); |
| appendArgs(sb, args); |
| sb.append('}'); |
| return sb.toString(); |
| })); |
| // Add a flow event, either start or step in enclosing scope |
| fLogger.log(fLevel, (() -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, startFlow ? 's' : 't', time, fThreadId); |
| appendName(sb, label); |
| appendCategory(sb, category); |
| appendId(sb, fId); |
| appendArgs(sb, args); |
| sb.append('}'); |
| return sb.toString(); |
| })); |
| } |
| |
| /** |
| * Flow step, it will add a stop point for an arrow |
| * |
| * @param label |
| * The label for this step |
| * @param args |
| * the arguments to log |
| */ |
| public void step(String label, Object... args) { |
| long time = System.nanoTime(); |
| fLogger.log(fLevel, (() -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 't', time, fThreadId); |
| appendName(sb, label); |
| appendCategory(sb, fCategory); |
| appendId(sb, fId); |
| appendArgs(sb, args); |
| sb.append('}'); |
| return sb.toString(); |
| })); |
| } |
| |
| /** |
| * Add a tag to the scope logger, will be written at the exit. This can |
| * save space on the trace by having a field appended to an event rather |
| * than writing a whole new event for a small chunk of data. |
| * |
| * |
| * If the timing information is important, then it would be more |
| * appropriate to call {@link #step(String, Object...)} |
| * |
| * @param name |
| * the name of the field |
| * @param value |
| * The value of the field. |
| */ |
| public void addData(String name, Object value) { |
| fData.put(name, value); |
| } |
| |
| /** |
| * Get the ID for this scope. The ID can be injected to other components |
| * that can use it for the scope loggers |
| * |
| * @return The ID of this scope |
| */ |
| public int getId() { |
| return fId; |
| } |
| |
| @Override |
| public void close() { |
| long time = System.nanoTime(); |
| fLogger.log(fLevel, (() -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'E', time, fThreadId); |
| appendArgs(sb, fData); |
| sb.append('}'); |
| return sb.toString(); |
| })); |
| } |
| } |
| |
| /** |
| * Trace Object Creation, logs the beginning of an object's life cycle. |
| * Typically one can put this in the object's constructor. However if an |
| * object is mutable, it can be tracked through phases with this method, |
| * then the object can be re-used, however, the resulting analyses may yield |
| * erroneous data if precautions are not taken. |
| * |
| * For mutable objects, save the return value of the call. This will be |
| * passed to the destruction of the object and then it can be matched. |
| * |
| * @param logger |
| * The JUL logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param item |
| * the Object to trace |
| * @return The unique ID of this object (there may be collisions) |
| */ |
| public static int traceObjectCreation(Logger logger, Level level, Object item) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| int identityHashCode = System.identityHashCode(item); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'N', time, threadId); |
| appendName(sb, item.getClass().getSimpleName()); |
| appendId(sb, identityHashCode); |
| return sb.append('}').toString(); |
| }); |
| return identityHashCode; |
| } |
| |
| /** |
| * Trace Object Destruction, logs the end of an object's life cycle. |
| * Typically one can put this in the object's Dispose(). However if an |
| * object is mutable, it can be tracked through phases with this method, |
| * then the object can be re-used, however, the resulting analyses may yield |
| * erroneous data if precautions are not taken. |
| * |
| * @param logger |
| * The JUL logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param item |
| * the Object to trace |
| */ |
| public static void traceObjectDestruction(Logger logger, Level level, Object item) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'D', time, threadId); |
| appendName(sb, item.getClass().getSimpleName()); |
| appendId(sb, System.identityHashCode(item)); |
| return sb.append('}').toString(); |
| }); |
| } |
| |
| /** |
| * Trace Object Destruction, logs the end of an object's life cycle. |
| * Typically one can put this in the object's Dispose(). However if an |
| * object is mutable, it can be tracked through phases with this method, |
| * then the object can be re-used, however, the resulting analyses may be |
| * |
| * @param logger |
| * The JUL logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param item |
| * the Object to trace |
| * @param uniqueId |
| * The unique ID |
| */ |
| public static void traceObjectDestruction(Logger logger, Level level, Object item, int uniqueId) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'D', time, threadId); |
| appendName(sb, item.getClass().getSimpleName()); |
| appendId(sb, uniqueId); |
| return sb.append('}').toString(); |
| }); |
| } |
| |
| /** |
| * Asynchronous events are used to specify asynchronous operations, such as |
| * an asynchronous (or synchronous) draw, or a network operation. Call this |
| * method at the beginning of such an operation. |
| * |
| * @param logger |
| * The JUL logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param name |
| * The name of the asynchronous message |
| * @param category |
| * the category of the asynchronous event |
| * @param id |
| * The unique ID of a transaction |
| * @param args |
| * Additional arguments to log |
| */ |
| public static void traceAsyncStart(Logger logger, Level level, @Nullable String name, @Nullable String category, int id, Object... args) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'b', time, threadId); |
| appendName(sb, name); |
| appendCategory(sb, category); |
| appendId(sb, id); |
| return appendArgs(sb, args).append('}').toString(); |
| }); |
| } |
| |
| /** |
| * Asynchronous events are used to specify asynchronous operations, such as |
| * an asynchronous (or synchronous) draw, or a network operation. Call this |
| * method to augment the asynchronous event with nested information. |
| * |
| * @param logger |
| * The JUL logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param name |
| * The name of the asynchronous message |
| * @param category |
| * the category of the asynchronous event |
| * @param id |
| * The unique ID of a transaction |
| * @param args |
| * Additional arguments to log |
| */ |
| public static void traceAsyncNested(Logger logger, Level level, @Nullable String name, @Nullable String category, int id, Object... args) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'n', time, threadId); |
| appendName(sb, name); |
| appendCategory(sb, category); |
| appendId(sb, id); |
| return appendArgs(sb, args).append('}').toString(); |
| }); |
| } |
| |
| /** |
| * Asynchronous events are used to specify asynchronous operations, such as |
| * an asynchronous (or synchronous) draw, or a network operation. Call this |
| * method at the end of such an operation. |
| * |
| * @param logger |
| * The JUL logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param name |
| * The name of the asynchronous message |
| * @param category |
| * the category of the asynchronous event |
| * @param id |
| * The unique ID of a transaction |
| * @param args |
| * Additional arguments to log |
| */ |
| public static void traceAsyncEnd(Logger logger, Level level, @Nullable String name, @Nullable String category, int id, Object... args) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'e', time, threadId); |
| appendName(sb, name); |
| appendCategory(sb, category); |
| appendId(sb, id); |
| return appendArgs(sb, args).append('}').toString(); |
| }); |
| } |
| |
| /** |
| * Instant events, created to indicate an item of interest has occurred, |
| * similar to a standard System.out.println() or a |
| * Java.util.Logger#log(Level). This one provides an event in a more |
| * structured way. This should be the method to call to save data that |
| * should have a zero duration, as it will ensure a log format that can then |
| * be parsed by a trace type. |
| * |
| * @param logger |
| * The JUL logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param name |
| * The name of the asynchronous message |
| * @param args |
| * Additional arguments to log |
| */ |
| public static void traceInstant(Logger logger, Level level, String name, Object... args) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'i', time, threadId); |
| appendName(sb, name); |
| return appendArgs(sb, args).append('}').toString(); |
| }); |
| } |
| |
| /** |
| * The counter events can track a value or multiple values as they change |
| * over time. |
| * |
| * @param logger |
| * The Logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param name |
| * The name of the asynchronous message |
| * @param args |
| * The counters to log in the format : "title", value |
| */ |
| public static void traceCounter(Logger logger, Level level, @Nullable String name, Object... args) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'C', time, threadId); |
| appendName(sb, name); |
| return appendArgs(sb, args).append('}').toString(); |
| }); |
| } |
| |
| /** |
| * The Marker events are events with a duration that define a region of |
| * interest. These regions can be displayed in views as Markers or other |
| * indicators. |
| * |
| * @param logger |
| * The Logger |
| * @param level |
| * The {@link Level} of this event. |
| * @param name |
| * The name of the marker message message |
| * @param duration |
| * How long the marker should last |
| * @param args |
| * The counters to log in the format : "title", value, note |
| * "color" and an rbga will be used |
| */ |
| public static void traceMarker(Logger logger, Level level, @Nullable String name, long duration, Object... args) { |
| long time = System.nanoTime(); |
| long threadId = Thread.currentThread().getId(); |
| logger.log(level, () -> { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| appendCommon(sb, 'R', time, threadId); |
| appendName(sb, name); |
| sb.append(','); |
| writeObject(sb, "dur", duration); //$NON-NLS-1$ |
| return appendArgs(sb, args).append('}').toString(); |
| }); |
| } |
| |
| // ------------------------------------------------------------------------- |
| // Helpers |
| // ------------------------------------------------------------------------- |
| |
| /* |
| * USE ME FIRST |
| */ |
| private static StringBuilder appendCommon(StringBuilder appendTo, char phase, long time, long threadId) { |
| writeObject(appendTo, TIMESTAMP, FORMAT.format((double) time / 1000)).append(','); // $NON-NLS-1$ |
| writeObject(appendTo, PHASE, phase).append(','); |
| writeObject(appendTo, TID, threadId).append(','); |
| return writeObject(appendTo, PID, threadId); // $NON-NLS-1$ |
| } |
| |
| private static StringBuilder appendName(StringBuilder sb, @Nullable String name) { |
| if (name != null) { |
| sb.append(','); |
| writeObject(sb, NAME, name); |
| } |
| return sb; |
| } |
| |
| private static StringBuilder appendCategory(StringBuilder sb, @Nullable String category) { |
| if (category != null) { |
| sb.append(','); |
| writeObject(sb, CATEGORY, category); |
| } |
| return sb; |
| } |
| |
| private static StringBuilder appendId(StringBuilder sb, int id) { |
| return sb.append(',') |
| .append('"') |
| .append(ID) |
| .append("\":\"0x") //$NON-NLS-1$ |
| .append(Integer.toHexString(id)) |
| .append('"'); |
| } |
| |
| private static StringBuilder appendArgs(StringBuilder sb, Map<String, Object> args) { |
| if (!args.isEmpty()) { |
| sb.append(',') |
| .append('"') |
| .append(ARGS) |
| .append('"') |
| .append(':'); |
| Object[] argsArray = new Object[2 * args.size()]; |
| Iterator<Entry<String, Object>> entryIter = args.entrySet().iterator(); |
| for (int i = 0; i < args.size(); i++) { |
| Entry<String, Object> entry = entryIter.next(); |
| argsArray[i] = entry.getKey(); |
| argsArray[i + 1] = entry.getValue(); |
| } |
| getArgs(sb, argsArray); |
| } |
| return sb; |
| } |
| |
| private static StringBuilder appendArgs(StringBuilder sb, Object... args) { |
| if (args.length > 0) { |
| sb.append(',') |
| .append('"') |
| .append(ARGS) |
| .append('"') |
| .append(':'); |
| getArgs(sb, args); |
| } |
| return sb; |
| } |
| |
| private static StringBuilder getArgs(StringBuilder appendTo, Object[] data) { |
| if (data.length == 0) { |
| return appendTo; |
| } |
| Set<String> tester = new HashSet<>(); |
| appendTo.append('{'); |
| if (data.length == 1) { |
| // not in contract, but let's assume here that people are still new |
| // at this |
| appendTo.append("\"msg\":\"").append(data[0]).append('"'); //$NON-NLS-1$ |
| } else { |
| if (data.length % 2 != 0) { |
| throw new IllegalArgumentException( |
| ARGS_ERROR_MESSAGE + "an odd number of messages" + Arrays.asList(data).toString()); //$NON-NLS-1$ |
| } |
| for (int i = 0; i < data.length - 1; i += 2) { |
| Object value = data[i + 1]; |
| String keyVal = String.valueOf(data[i]); |
| if (tester.contains(keyVal)) { |
| throw new IllegalArgumentException(ARGS_ERROR_MESSAGE + "an duplicate field names : " + keyVal); //$NON-NLS-1$ |
| } |
| tester.add(keyVal); |
| if (i > 0) { |
| appendTo.append(','); |
| } |
| writeObject(appendTo, keyVal, value); |
| } |
| } |
| |
| return appendTo.append('}'); |
| } |
| |
| private static StringBuilder writeObject(StringBuilder appendTo, Object key, @Nullable Object value) { |
| appendTo.append('"').append(key).append('"').append(':'); |
| if (value instanceof Number) { |
| appendTo.append(value); |
| } else { |
| appendTo.append('"').append(String.valueOf(value)).append('"'); |
| } |
| return appendTo; |
| } |
| |
| } |