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 import android.util.Slog; 24 25 import java.io.PrintWriter; 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.text.SimpleDateFormat; 29 import java.util.ArrayDeque; 30 import java.util.ArrayList; 31 import java.util.Date; 32 import java.util.List; 33 import java.util.Locale; 34 35 /** 36 * Logs human-readable events for debugging purposes. 37 */ 38 public class EventLogger { 39 40 /** Prefix for the title added at the beginning of a {@link #dump(PrintWriter)} operation */ 41 private static final String DUMP_TITLE_PREFIX = "Events log: "; 42 43 /** Identifies the source of events. */ 44 @Nullable private final String mTag; 45 46 /** Stores the events using a ring buffer. */ 47 private final ArrayDeque<Event> mEvents; 48 49 /** 50 * The maximum number of events to keep in {@code mEvents}. 51 * 52 * <p>Calling {@link #enqueue} when the size of {@link #mEvents} matches the threshold will 53 * cause the oldest event to be evicted. 54 */ 55 private final int mMemSize; 56 57 /** 58 * Constructor for logger. 59 * @param size the maximum number of events to keep in log 60 * @param tag the string displayed before the recorded log 61 */ EventLogger(int size, @Nullable String tag)62 public EventLogger(int size, @Nullable String tag) { 63 mEvents = new ArrayDeque<>(size); 64 mMemSize = size; 65 mTag = tag; 66 } 67 68 /** Enqueues {@code event} to be logged. */ enqueue(Event event)69 public synchronized void enqueue(Event event) { 70 if (mEvents.size() >= mMemSize) { 71 mEvents.removeFirst(); 72 } 73 74 mEvents.addLast(event); 75 } 76 77 /** 78 * Add a string-based event to the log, and print it to logcat with a specific severity. 79 * @param msg the message for the logs 80 * @param logType the type of logcat entry 81 * @param tag the logcat tag to use 82 */ enqueueAndLog(String msg, @Event.LogType int logType, String tag)83 public synchronized void enqueueAndLog(String msg, @Event.LogType int logType, String tag) { 84 final Event event = new StringEvent(msg); 85 enqueue(event.printLog(logType, tag)); 86 } 87 88 /** 89 * Add a string-based event to the system log, and print it to the log with a specific severity. 90 * @param msg the message to appear in the log 91 * @param logType the log severity (verbose/info/warning/error) 92 * @param tag the tag under which the log entry will appear 93 */ enqueueAndSlog(String msg, @Event.LogType int logType, String tag)94 public synchronized void enqueueAndSlog(String msg, @Event.LogType int logType, String tag) { 95 final Event event = new StringEvent(msg); 96 enqueue(event.printSlog(logType, tag)); 97 } 98 99 /** Dumps events into the given {@link DumpSink}. */ dump(DumpSink dumpSink)100 public synchronized void dump(DumpSink dumpSink) { 101 dumpSink.sink(mTag, new ArrayList<>(mEvents)); 102 } 103 104 /** Dumps events using {@link PrintWriter}. */ dump(PrintWriter pw)105 public synchronized void dump(PrintWriter pw) { 106 dump(pw, "" /* prefix */); 107 } 108 getDumpTitle()109 protected String getDumpTitle() { 110 if (mTag == null) { 111 return DUMP_TITLE_PREFIX; 112 } 113 return DUMP_TITLE_PREFIX + mTag; 114 } 115 116 /** Dumps events using {@link PrintWriter} with a certain indent. */ dump(PrintWriter pw, String indent)117 public synchronized void dump(PrintWriter pw, String indent) { 118 pw.println(getDumpTitle()); 119 120 for (Event evt : mEvents) { 121 pw.println(indent + evt.toString()); 122 } 123 } 124 125 /** Receives events from {@link EventLogger} upon a {@link #dump(DumpSink)} call. **/ 126 public interface DumpSink { 127 128 /** Processes given events into some pipeline with a given tag. **/ sink(String tag, List<Event> events)129 void sink(String tag, List<Event> events); 130 131 } 132 133 public abstract static class Event { 134 135 /** Timestamps formatter. */ 136 private static final SimpleDateFormat sFormat = 137 new SimpleDateFormat("MM-dd HH:mm:ss:SSS", Locale.US); 138 139 private final long mTimestamp; 140 Event()141 public Event() { 142 mTimestamp = System.currentTimeMillis(); 143 } 144 toString()145 public String toString() { 146 return (new StringBuilder(sFormat.format(new Date(mTimestamp)))) 147 .append(" ").append(eventToString()).toString(); 148 } 149 150 /** 151 * Causes the string message for the event to appear in the logcat. 152 * Here is an example of how to create a new event (a StringEvent), adding it to the logger 153 * (an instance of EventLogger) while also making it show in the logcat: 154 * <pre> 155 * myLogger.log( 156 * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) ); 157 * </pre> 158 * @param tag the tag for the android.util.Log.v 159 * @return the same instance of the event 160 */ printLog(String tag)161 public Event printLog(String tag) { 162 return printLog(ALOGI, tag); 163 } 164 165 /** @hide */ 166 @IntDef(flag = false, value = { 167 ALOGI, 168 ALOGE, 169 ALOGW, 170 ALOGV } 171 ) 172 @Retention(RetentionPolicy.SOURCE) 173 public @interface LogType {} 174 175 public static final int ALOGI = 0; 176 public static final int ALOGE = 1; 177 public static final int ALOGW = 2; 178 public static final int ALOGV = 3; 179 180 /** 181 * Same as {@link #printLog(String)} with a log type 182 * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV}, {@link #ALOGW} 183 * @param tag the tag the log entry will be printed under 184 * @return the event itself 185 */ printLog(@ogType int type, String tag)186 public Event printLog(@LogType int type, String tag) { 187 switch (type) { 188 case ALOGI: 189 Log.i(tag, eventToString()); 190 break; 191 case ALOGE: 192 Log.e(tag, eventToString()); 193 break; 194 case ALOGW: 195 Log.w(tag, eventToString()); 196 break; 197 case ALOGV: 198 default: 199 Log.v(tag, eventToString()); 200 break; 201 } 202 return this; 203 } 204 205 /** 206 * Causes the string message for the event to appear in the system log. 207 * @param type one of {@link #ALOGI}, {@link #ALOGE}, {@link #ALOGV}, {@link #ALOGW} 208 * @param tag the tag the log entry will be printed under 209 * @return the event itself 210 * @see #printLog(int, String) 211 */ printSlog(@ogType int type, String tag)212 public Event printSlog(@LogType int type, String tag) { 213 switch (type) { 214 case ALOGI: 215 Slog.i(tag, eventToString()); 216 break; 217 case ALOGE: 218 Slog.e(tag, eventToString()); 219 break; 220 case ALOGW: 221 Slog.w(tag, eventToString()); 222 break; 223 case ALOGV: 224 default: 225 Slog.v(tag, eventToString()); 226 break; 227 } 228 return this; 229 } 230 231 /** 232 * Convert event to String. 233 * This method is only called when the logger history is about to the dumped, 234 * so this method is where expensive String conversions should be made, not when the Event 235 * subclass is created. 236 * Timestamp information will be automatically added, do not include it. 237 * @return a string representation of the event that occurred. 238 */ eventToString()239 public abstract String eventToString(); 240 } 241 242 public static class StringEvent extends Event { 243 244 @Nullable 245 private final String mSource; 246 247 private final String mDescription; 248 249 /** Creates event from {@code source} and formatted {@code description} with {@code args} */ from(@onNull String source, @NonNull String description, Object... args)250 public static StringEvent from(@NonNull String source, 251 @NonNull String description, Object... args) { 252 return new StringEvent(source, String.format(Locale.US, description, args)); 253 } 254 StringEvent(String description)255 public StringEvent(String description) { 256 this(null /* source */, description); 257 } 258 StringEvent(String source, String description)259 public StringEvent(String source, String description) { 260 mSource = source; 261 mDescription = description; 262 } 263 264 @Override eventToString()265 public String eventToString() { 266 if (mSource == null) { 267 return mDescription; 268 } 269 270 // [source ] optional description 271 return String.format("[%-40s] %s", 272 mSource, 273 (mDescription == null ? "" : mDescription)); 274 } 275 } 276 } 277