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