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