1 /*
2  * Copyright (C) 2007 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 android.util;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.SystemApi;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.os.Build;
24 
25 import java.io.BufferedReader;
26 import java.io.FileReader;
27 import java.io.IOException;
28 import java.io.UnsupportedEncodingException;
29 import java.nio.BufferUnderflowException;
30 import java.nio.ByteBuffer;
31 import java.nio.ByteOrder;
32 import java.util.Arrays;
33 import java.util.Collection;
34 import java.util.HashMap;
35 import java.util.regex.Matcher;
36 import java.util.regex.Pattern;
37 
38 /**
39  * Access to the system diagnostic event record.  System diagnostic events are
40  * used to record certain system-level events (such as garbage collection,
41  * activity manager state, system watchdogs, and other low level activity),
42  * which may be automatically collected and analyzed during system development.
43  *
44  * <p>This is <b>not</b> the main "logcat" debugging log ({@link android.util.Log})!
45  * These diagnostic events are for system integrators, not application authors.
46  *
47  * <p>Events use integer tag codes corresponding to /system/etc/event-log-tags.
48  * They carry a payload of one or more int, long, or String values.  The
49  * event-log-tags file defines the payload contents for each type code.
50  */
51 public class EventLog {
EventLog()52     /** @hide */ public EventLog() {}
53 
54     private static final String TAG = "EventLog";
55 
56     private static final String TAGS_FILE = "/system/etc/event-log-tags";
57     private static final String COMMENT_PATTERN = "^\\s*(#.*)?$";
58     private static final String TAG_PATTERN = "^\\s*(\\d+)\\s+(\\w+)\\s*(\\(.*\\))?\\s*$";
59     private static HashMap<String, Integer> sTagCodes = null;
60     private static HashMap<Integer, String> sTagNames = null;
61 
62     /** A previously logged event read from the logs. Instances are thread safe. */
63     public static final class Event {
64         private final ByteBuffer mBuffer;
65         private Exception mLastWtf;
66 
67         // Layout of event log entry received from Android logger.
68         //  see system/logging/liblog/include/log/log_read.h
69         private static final int LENGTH_OFFSET = 0;
70         private static final int HEADER_SIZE_OFFSET = 2;
71         private static final int PROCESS_OFFSET = 4;
72         private static final int THREAD_OFFSET = 8;
73         private static final int SECONDS_OFFSET = 12;
74         private static final int NANOSECONDS_OFFSET = 16;
75         private static final int UID_OFFSET = 24;
76 
77         // Layout for event log v1 format, v2 and v3 use HEADER_SIZE_OFFSET
78         private static final int V1_PAYLOAD_START = 20;
79         private static final int TAG_LENGTH = 4;
80 
81         // Value types
82         private static final byte INT_TYPE    = 0;
83         private static final byte LONG_TYPE   = 1;
84         private static final byte STRING_TYPE = 2;
85         private static final byte LIST_TYPE   = 3;
86         private static final byte FLOAT_TYPE = 4;
87 
88         /** @param data containing event, read from the system */
89         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
Event(byte[] data)90         /*package*/ Event(byte[] data) {
91             mBuffer = ByteBuffer.wrap(data);
92             mBuffer.order(ByteOrder.nativeOrder());
93         }
94 
95         /** @return the process ID which wrote the log entry */
getProcessId()96         public int getProcessId() {
97             return mBuffer.getInt(PROCESS_OFFSET);
98         }
99 
100         /**
101          * @return the UID which wrote the log entry
102          * @hide
103          */
104         @SystemApi
getUid()105         public int getUid() {
106             try {
107                 return mBuffer.getInt(UID_OFFSET);
108             } catch (IndexOutOfBoundsException e) {
109                 // buffer won't contain the UID if the caller doesn't have permission.
110                 return -1;
111             }
112         }
113 
114         /** @return the thread ID which wrote the log entry */
getThreadId()115         public int getThreadId() {
116             return mBuffer.getInt(THREAD_OFFSET);
117         }
118 
119         /** @return the wall clock time when the entry was written */
getTimeNanos()120         public long getTimeNanos() {
121             return mBuffer.getInt(SECONDS_OFFSET) * 1000000000l
122                     + mBuffer.getInt(NANOSECONDS_OFFSET);
123         }
124 
125         /** @return the type tag code of the entry */
getTag()126         public int getTag() {
127             return mBuffer.getInt(getHeaderSize());
128         }
129 
getHeaderSize()130         private int getHeaderSize() {
131             int length = mBuffer.getShort(HEADER_SIZE_OFFSET);
132             if (length != 0) {
133                 return length;
134             }
135             return V1_PAYLOAD_START;
136         }
137         /** @return one of Integer, Long, Float, String, null, or Object[] of same. */
getData()138         public synchronized Object getData() {
139             try {
140                 int offset = getHeaderSize();
141                 mBuffer.limit(offset + mBuffer.getShort(LENGTH_OFFSET));
142                 if ((offset + TAG_LENGTH) >= mBuffer.limit()) {
143                     // no payload
144                     return null;
145                 }
146                 mBuffer.position(offset + TAG_LENGTH); // Just after the tag.
147                 return decodeObject();
148             } catch (IllegalArgumentException e) {
149                 Log.wtf(TAG, "Illegal entry payload: tag=" + getTag(), e);
150                 mLastWtf = e;
151                 return null;
152             } catch (BufferUnderflowException e) {
153                 Log.wtf(TAG, "Truncated entry payload: tag=" + getTag(), e);
154                 mLastWtf = e;
155                 return null;
156             }
157         }
158 
159         /**
160          * Construct a new EventLog object from the current object, copying all log metadata
161          * but replacing the actual payload with the content provided.
162          * @hide
163          */
withNewData(@ullable Object object)164         public Event withNewData(@Nullable Object object) {
165             byte[] payload = encodeObject(object);
166             if (payload.length > 65535 - TAG_LENGTH) {
167                 throw new IllegalArgumentException("Payload too long");
168             }
169             int headerLength = getHeaderSize();
170             byte[] newBytes = new byte[headerLength + TAG_LENGTH + payload.length];
171             // Copy header (including the 4 bytes of tag integer at the beginning of payload)
172             System.arraycopy(mBuffer.array(), 0, newBytes, 0, headerLength + TAG_LENGTH);
173             // Fill in encoded objects
174             System.arraycopy(payload, 0, newBytes, headerLength + TAG_LENGTH, payload.length);
175             Event result = new Event(newBytes);
176             // Patch payload length in header
177             result.mBuffer.putShort(LENGTH_OFFSET, (short) (payload.length + TAG_LENGTH));
178             return result;
179         }
180 
181         /** @return the loggable item at the current position in mBuffer. */
decodeObject()182         private Object decodeObject() {
183             byte type = mBuffer.get();
184             switch (type) {
185             case INT_TYPE:
186                 return mBuffer.getInt();
187 
188             case LONG_TYPE:
189                 return mBuffer.getLong();
190 
191             case FLOAT_TYPE:
192                 return mBuffer.getFloat();
193 
194             case STRING_TYPE:
195                 try {
196                     int length = mBuffer.getInt();
197                     int start = mBuffer.position();
198                     mBuffer.position(start + length);
199                     return new String(mBuffer.array(), start, length, "UTF-8");
200                 } catch (UnsupportedEncodingException e) {
201                     Log.wtf(TAG, "UTF-8 is not supported", e);
202                     mLastWtf = e;
203                     return null;
204                 }
205 
206             case LIST_TYPE:
207                 int length = mBuffer.get();
208                 if (length < 0) length += 256;  // treat as signed byte
209                 Object[] array = new Object[length];
210                 for (int i = 0; i < length; ++i) array[i] = decodeObject();
211                 return array;
212 
213             default:
214                 throw new IllegalArgumentException("Unknown entry type: " + type);
215             }
216         }
217 
encodeObject(@ullable Object object)218         private static @NonNull byte[] encodeObject(@Nullable Object object) {
219             if (object == null) {
220                 return new byte[0];
221             }
222             if (object instanceof Integer) {
223                 return ByteBuffer.allocate(1 + 4)
224                         .order(ByteOrder.nativeOrder())
225                         .put(INT_TYPE)
226                         .putInt((Integer) object)
227                         .array();
228             } else if (object instanceof Long) {
229                 return ByteBuffer.allocate(1 + 8)
230                         .order(ByteOrder.nativeOrder())
231                         .put(LONG_TYPE)
232                         .putLong((Long) object)
233                         .array();
234             } else if (object instanceof Float) {
235                 return ByteBuffer.allocate(1 + 4)
236                         .order(ByteOrder.nativeOrder())
237                         .put(FLOAT_TYPE)
238                         .putFloat((Float) object)
239                         .array();
240             } else if (object instanceof String) {
241                 String string = (String) object;
242                 byte[] bytes;
243                 try {
244                     bytes = string.getBytes("UTF-8");
245                 } catch (UnsupportedEncodingException e) {
246                     bytes = new byte[0];
247                 }
248                 return ByteBuffer.allocate(1 + 4 + bytes.length)
249                          .order(ByteOrder.nativeOrder())
250                          .put(STRING_TYPE)
251                          .putInt(bytes.length)
252                          .put(bytes)
253                          .array();
254             } else if (object instanceof Object[]) {
255                 Object[] objects = (Object[]) object;
256                 if (objects.length > 255) {
257                     throw new IllegalArgumentException("Object array too long");
258                 }
259                 byte[][] bytes = new byte[objects.length][];
260                 int totalLength = 0;
261                 for (int i = 0; i < objects.length; i++) {
262                     bytes[i] = encodeObject(objects[i]);
263                     totalLength += bytes[i].length;
264                 }
265                 ByteBuffer buffer = ByteBuffer.allocate(1 + 1 + totalLength)
266                         .order(ByteOrder.nativeOrder())
267                         .put(LIST_TYPE)
268                         .put((byte) objects.length);
269                 for (int i = 0; i < objects.length; i++) {
270                     buffer.put(bytes[i]);
271                 }
272                 return buffer.array();
273             } else {
274                 throw new IllegalArgumentException("Unknown object type " + object);
275             }
276         }
277 
278         /** @hide */
fromBytes(byte[] data)279         public static Event fromBytes(byte[] data) {
280             return new Event(data);
281         }
282 
283         /** @hide */
getBytes()284         public byte[] getBytes() {
285             byte[] bytes = mBuffer.array();
286             return Arrays.copyOf(bytes, bytes.length);
287         }
288 
289         /**
290          * Retreive the last WTF error generated by this object.
291          * @hide
292          */
293         //VisibleForTesting
getLastError()294         public Exception getLastError() {
295             return mLastWtf;
296         }
297 
298         /**
299          * Clear the error state for this object.
300          * @hide
301          */
302         //VisibleForTesting
clearError()303         public void clearError() {
304             mLastWtf = null;
305         }
306 
307         /**
308          * @hide
309          */
310         @Override
equals(@ullable Object o)311         public boolean equals(@Nullable Object o) {
312             // Not using ByteBuffer.equals since it takes buffer position into account and we
313             // always use absolute positions here.
314             if (this == o) return true;
315             if (o == null || getClass() != o.getClass()) return false;
316             Event other = (Event) o;
317             return Arrays.equals(mBuffer.array(), other.mBuffer.array());
318         }
319 
320         /**
321          * @hide
322          */
323         @Override
hashCode()324         public int hashCode() {
325             // Not using ByteBuffer.hashCode since it takes buffer position into account and we
326             // always use absolute positions here.
327             return Arrays.hashCode(mBuffer.array());
328         }
329     }
330 
331     // We assume that the native methods deal with any concurrency issues.
332 
333     /**
334      * Record an event log message.
335      * @param tag The event type tag code
336      * @param value A value to log
337      * @return The number of bytes written
338      */
writeEvent(int tag, int value)339     public static native int writeEvent(int tag, int value);
340 
341     /**
342      * Record an event log message.
343      * @param tag The event type tag code
344      * @param value A value to log
345      * @return The number of bytes written
346      */
writeEvent(int tag, long value)347     public static native int writeEvent(int tag, long value);
348 
349     /**
350      * Record an event log message.
351      * @param tag The event type tag code
352      * @param value A value to log
353      * @return The number of bytes written
354      */
writeEvent(int tag, float value)355     public static native int writeEvent(int tag, float value);
356 
357     /**
358      * Record an event log message.
359      * @param tag The event type tag code
360      * @param str A value to log
361      * @return The number of bytes written
362      */
writeEvent(int tag, String str)363     public static native int writeEvent(int tag, String str);
364 
365     /**
366      * Record an event log message.
367      * @param tag The event type tag code
368      * @param list A list of values to log
369      * @return The number of bytes written
370      */
writeEvent(int tag, Object... list)371     public static native int writeEvent(int tag, Object... list);
372 
373     /**
374      * Read events from the log, filtered by type.
375      * @param tags to search for
376      * @param output container to add events into
377      * @throws IOException if something goes wrong reading events
378      */
readEvents(int[] tags, Collection<Event> output)379     public static native void readEvents(int[] tags, Collection<Event> output)
380             throws IOException;
381 
382     /**
383      * Read events from the log, filtered by type, blocking until logs are about to be overwritten.
384      * @param tags to search for
385      * @param timestamp timestamp allow logs before this time to be overwritten.
386      * @param output container to add events into
387      * @throws IOException if something goes wrong reading events
388      * @hide
389      */
390     @SystemApi
readEventsOnWrapping(int[] tags, long timestamp, Collection<Event> output)391     public static native void readEventsOnWrapping(int[] tags, long timestamp,
392             Collection<Event> output)
393             throws IOException;
394 
395     /**
396      * Get the name associated with an event type tag code.
397      * @param tag code to look up
398      * @return the name of the tag, or null if no tag has that number
399      */
getTagName(int tag)400     public static String getTagName(int tag) {
401         readTagsFile();
402         return sTagNames.get(tag);
403     }
404 
405     /**
406      * Get the event type tag code associated with an event name.
407      * @param name of event to look up
408      * @return the tag code, or -1 if no tag has that name
409      */
getTagCode(String name)410     public static int getTagCode(String name) {
411         readTagsFile();
412         Integer code = sTagCodes.get(name);
413         return code != null ? code : -1;
414     }
415 
416     /**
417      * Read TAGS_FILE, populating sTagCodes and sTagNames, if not already done.
418      */
readTagsFile()419     private static synchronized void readTagsFile() {
420         if (sTagCodes != null && sTagNames != null) return;
421 
422         sTagCodes = new HashMap<String, Integer>();
423         sTagNames = new HashMap<Integer, String>();
424 
425         Pattern comment = Pattern.compile(COMMENT_PATTERN);
426         Pattern tag = Pattern.compile(TAG_PATTERN);
427         BufferedReader reader = null;
428         String line;
429 
430         try {
431             reader = new BufferedReader(new FileReader(TAGS_FILE), 256);
432             while ((line = reader.readLine()) != null) {
433                 if (comment.matcher(line).matches()) continue;
434 
435                 Matcher m = tag.matcher(line);
436                 if (!m.matches()) {
437                     Log.wtf(TAG, "Bad entry in " + TAGS_FILE + ": " + line);
438                     continue;
439                 }
440 
441                 try {
442                     int num = Integer.parseInt(m.group(1));
443                     String name = m.group(2);
444                     sTagCodes.put(name, num);
445                     sTagNames.put(num, name);
446                 } catch (NumberFormatException e) {
447                     Log.wtf(TAG, "Error in " + TAGS_FILE + ": " + line, e);
448                 }
449             }
450         } catch (IOException e) {
451             Log.wtf(TAG, "Error reading " + TAGS_FILE, e);
452             // Leave the maps existing but unpopulated
453         } finally {
454             try { if (reader != null) reader.close(); } catch (IOException e) {}
455         }
456     }
457 }
458