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