/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.apache.drill.common; import java.util.Arrays; import java.util.LinkedList; import org.slf4j.Logger; /** * Utility class that can be used to log activity within a class * for later logging and debugging. Supports recording events and * recording the stack at the time they occur. */ public class HistoricalLog { private static class Event { private final String note; // the event text private final StackTrace stackTrace; // where the event occurred private final long time; public Event(final String note) { this.note = note; this.time = System.nanoTime(); stackTrace = new StackTrace(); } } private final LinkedList<Event> history = new LinkedList<>(); private final String idString; // the formatted id string private Event firstEvent; // the first stack trace recorded private final int limit; // the limit on the number of events kept /** * Constructor. The format string will be formatted and have its arguments * substituted at the time this is called. * * @param idStringFormat {@link String#format} format string that can be used * to identify this object in a log. Including some kind of unique identifier * that can be associated with the object instance is best. * @param args for the format string, or nothing if none are required */ public HistoricalLog(final String idStringFormat, Object... args) { this(Integer.MAX_VALUE, idStringFormat, args); } /** * Constructor. The format string will be formatted and have its arguments * substituted at the time this is called. * * <p>This form supports the specification of a limit that will limit the * number of historical entries kept (which keeps down the amount of memory * used). With the limit, the first entry made is always kept (under the * assumption that this is the creation site of the object, which is usually * interesting), and then up to the limit number of entries are kept after that. * Each time a new entry is made, the oldest that is not the first is dropped. * </p> * * @param limit the maximum number of historical entries that will be kept, * not including the first entry made * @param idStringFormat {@link String#format} format string that can be used * to identify this object in a log. Including some kind of unique identifier * that can be associated with the object instance is best. * @param args for the format string, or nothing if none are required */ public HistoricalLog(final int limit, final String idStringFormat, Object... args) { this.limit = limit; this.idString = String.format(idStringFormat, args); } /** * Record an event. Automatically captures the stack trace at the time this is * called. The format string will be formatted and have its arguments substituted * at the time this is called. * * @param noteFormat {@link String#format} format string that describes the event * @param args for the format string, or nothing if none are required */ public synchronized void recordEvent(final String noteFormat, Object... args) { final String note = String.format(noteFormat, args); final Event event = new Event(note); if (firstEvent == null) { firstEvent = event; } if (history.size() == limit) { history.removeFirst(); } history.add(event); } /** * Write the history of this object to the given {@link StringBuilder}. The history * includes the identifying string provided at construction time, and all the recorded * events with their stack traces. * * @param sb {@link StringBuilder} to write to */ public void buildHistory(final StringBuilder sb, boolean includeStackTrace) { buildHistory(sb, 0, includeStackTrace); } /** * Write the history of this object to the given {@link StringBuilder}. The history * includes the identifying string provided at construction time, and all the recorded * events with their stack traces. * * @param sb {@link StringBuilder} to write to * @param additional an extra string that will be written between the identifying * information and the history; often used for a current piece of state */ /** * * @param sb * @param indexLevel * @param includeStackTrace */ public synchronized void buildHistory(final StringBuilder sb, int indent, boolean includeStackTrace) { final char[] indentation = new char[indent]; final char[] innerIndentation = new char[indent + 2]; Arrays.fill(indentation, ' '); Arrays.fill(innerIndentation, ' '); sb.append(indentation) .append("event log for: ") .append(idString) .append('\n'); if (firstEvent != null) { sb.append(innerIndentation) .append(firstEvent.time) .append(' ') .append(firstEvent.note) .append('\n'); if (includeStackTrace) { firstEvent.stackTrace.writeToBuilder(sb, indent + 2); } for(final Event event : history) { if (event == firstEvent) { continue; } sb.append(innerIndentation) .append(" ") .append(event.time) .append(' ') .append(event.note) .append('\n'); if (includeStackTrace) { event.stackTrace.writeToBuilder(sb, indent + 2); sb.append('\n'); } } } } /** * Write the history of this object to the given {@link Logger}. The history * includes the identifying string provided at construction time, and all the recorded * events with their stack traces. * * @param logger {@link Logger} to write to */ public void logHistory(final Logger logger) { final StringBuilder sb = new StringBuilder(); buildHistory(sb, 0, true); logger.debug(sb.toString()); } }