1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.utils; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.util.Log; 23 24 import java.io.PrintWriter; 25 import java.lang.annotation.Retention; 26 import java.lang.annotation.RetentionPolicy; 27 import java.text.SimpleDateFormat; 28 import java.util.ArrayDeque; 29 import java.util.ArrayList; 30 import java.util.Date; 31 import java.util.List; 32 import java.util.Locale; 33 34 /** 35 * Logs human-readable events for debugging purposes. 36 */ 37 public class EventLogger { 38 39 /** Prefix for the title added at the beginning of a {@link #dump(PrintWriter)} operation */ 40 private static final String DUMP_TITLE_PREFIX = "Events log: "; 41 42 /** Identifies the source of events. */ 43 @Nullable private final String mTag; 44 45 /** Stores the events using a ring buffer. */ 46 private final ArrayDeque<Event> mEvents; 47 48 /** 49 * The maximum number of events to keep in {@code mEvents}. 50 * 51 * <p>Calling {@link #enqueue} when the size of {@link #mEvents} matches the threshold will 52 * cause the oldest event to be evicted. 53 */ 54 private final int mMemSize; 55 56 /** 57 * Constructor for logger. 58 * @param size the maximum number of events to keep in log 59 * @param tag the string displayed before the recorded log 60 */ EventLogger(int size, @Nullable String tag)61 public EventLogger(int size, @Nullable String tag) { 62 mEvents = new ArrayDeque<>(size); 63 mMemSize = size; 64 mTag = tag; 65 } 66 67 /** Enqueues {@code event} to be logged. */ enqueue(Event event)68 public synchronized void enqueue(Event event) { 69 if (mEvents.size() >= mMemSize) { 70 mEvents.removeFirst(); 71 } 72 73 mEvents.addLast(event); 74 } 75 76 /** 77 * Add a string-based event to the log, and print it to logcat with a specific severity. 78 * @param msg the message for the logs 79 * @param logType the type of logcat entry 80 * @param tag the logcat tag to use 81 */ enqueueAndLog(String msg, @Event.LogType int logType, String tag)82 public synchronized void enqueueAndLog(String msg, @Event.LogType int logType, String tag) { 83 final Event event = new StringEvent(msg); 84 enqueue(event.printLog(logType, tag)); 85 } 86 87 /** Dumps events into the given {@link DumpSink}. */ dump(DumpSink dumpSink)88 public synchronized void dump(DumpSink dumpSink) { 89 dumpSink.sink(mTag, new ArrayList<>(mEvents)); 90 } 91 92 /** Dumps events using {@link PrintWriter}. */ dump(PrintWriter pw)93 public synchronized void dump(PrintWriter pw) { 94 dump(pw, "" /* prefix */); 95 } 96 getDumpTitle()97 protected String getDumpTitle() { 98 if (mTag == null) { 99 return DUMP_TITLE_PREFIX; 100 } 101 return DUMP_TITLE_PREFIX + mTag; 102 } 103 104 /** Dumps events using {@link PrintWriter} with a certain indent. */ dump(PrintWriter pw, String indent)105 public synchronized void dump(PrintWriter pw, String indent) { 106 pw.println(getDumpTitle()); 107 108 for (Event evt : mEvents) { 109 pw.println(indent + evt.toString()); 110 } 111 } 112 113 /** Receives events from {@link EventLogger} upon a {@link #dump(DumpSink)} call. **/ 114 public interface DumpSink { 115 116 /** Processes given events into some pipeline with a given tag. **/ sink(String tag, List<Event> events)117 void sink(String tag, List<Event> events); 118 119 } 120 121 public abstract static class Event { 122 123 /** Timestamps formatter. */ 124 private static final SimpleDateFormat sFormat = 125 new SimpleDateFormat("MM-dd HH:mm:ss:SSS", Locale.US); 126 127 private final long mTimestamp; 128 Event()129 public Event() { 130 mTimestamp = System.currentTimeMillis(); 131 } 132 toString()133 public String toString() { 134 return (new StringBuilder(sFormat.format(new Date(mTimestamp)))) 135 .append(" ").append(eventToString()).toString(); 136 } 137 138 /** 139 * Causes the string message for the event to appear in the logcat. 140 * Here is an example of how to create a new event (a StringEvent), adding it to the logger 141 * (an instance of AudioEventLogger) while also making it show in the logcat: 142 * <pre> 143 * myLogger.log( 144 * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) ); 145 * </pre> 146 * @param tag the tag for the android.util.Log.v 147 * @return the same instance of the event 148 */ printLog(String tag)149 public Event printLog(String tag) { 150 return printLog(ALOGI, tag); 151 } 152 153 /** @hide */ 154 @IntDef(flag = false, value = { 155 ALOGI, 156 ALOGE, 157 ALOGW, 158 ALOGV } 159 ) 160 @Retention(RetentionPolicy.SOURCE) 161 public @interface LogType {} 162 163 public static final int ALOGI = 0; 164 public static final int ALOGE = 1; 165 public static final int ALOGW = 2; 166 public static final int ALOGV = 3; 167 168 /** 169 * Same as {@link #printLog(String)} with a log type 170 * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV} 171 * @param tag 172 * @return 173 */ printLog(@ogType int type, String tag)174 public Event printLog(@LogType int type, String tag) { 175 switch (type) { 176 case ALOGI: 177 Log.i(tag, eventToString()); 178 break; 179 case ALOGE: 180 Log.e(tag, eventToString()); 181 break; 182 case ALOGW: 183 Log.w(tag, eventToString()); 184 break; 185 case ALOGV: 186 default: 187 Log.v(tag, eventToString()); 188 break; 189 } 190 return this; 191 } 192 193 /** 194 * Convert event to String. 195 * This method is only called when the logger history is about to the dumped, 196 * so this method is where expensive String conversions should be made, not when the Event 197 * subclass is created. 198 * Timestamp information will be automatically added, do not include it. 199 * @return a string representation of the event that occurred. 200 */ eventToString()201 public abstract String eventToString(); 202 } 203 204 public static class StringEvent extends Event { 205 206 @Nullable 207 private final String mSource; 208 209 private final String mDescription; 210 211 /** Creates event from {@code source} and formatted {@code description} with {@code args} */ from(@onNull String source, @NonNull String description, Object... args)212 public static StringEvent from(@NonNull String source, 213 @NonNull String description, Object... args) { 214 return new StringEvent(source, String.format(Locale.US, description, args)); 215 } 216 StringEvent(String description)217 public StringEvent(String description) { 218 this(null /* source */, description); 219 } 220 StringEvent(String source, String description)221 public StringEvent(String source, String description) { 222 mSource = source; 223 mDescription = description; 224 } 225 226 @Override eventToString()227 public String eventToString() { 228 if (mSource == null) { 229 return mDescription; 230 } 231 232 // [source ] optional description 233 return String.format("[%-40s] %s", 234 mSource, 235 (mDescription == null ? "" : mDescription)); 236 } 237 } 238 } 239