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