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