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