1 /* <lambda>null2 * Copyright (C) 2020 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 com.android.systemui.dump 18 19 import android.content.Context 20 import android.icu.text.SimpleDateFormat 21 import android.util.Log 22 import com.android.systemui.dagger.SysUISingleton 23 import com.android.systemui.dump.DumpHandler.Companion.dumpEntries 24 import com.android.systemui.log.LogBuffer 25 import com.android.systemui.util.io.Files 26 import com.android.systemui.util.time.SystemClock 27 import java.io.IOException 28 import java.io.PrintWriter 29 import java.io.UncheckedIOException 30 import java.nio.file.Path 31 import java.nio.file.Paths 32 import java.nio.file.StandardOpenOption.CREATE 33 import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING 34 import java.nio.file.attribute.BasicFileAttributes 35 import java.util.Locale 36 import java.util.concurrent.TimeUnit 37 import javax.inject.Inject 38 39 /** 40 * Dumps all [LogBuffer]s to a file 41 * 42 * Intended for emergencies, i.e. we're about to crash. This file can then be read at a later date 43 * (usually in a bug report). 44 */ 45 @SysUISingleton 46 class LogBufferEulogizer( 47 private val dumpManager: DumpManager, 48 private val systemClock: SystemClock, 49 private val files: Files, 50 private val logPath: Path, 51 private val minWriteGap: Long, 52 private val maxLogAgeToDump: Long, 53 ) { 54 @Inject 55 constructor( 56 context: Context, 57 dumpManager: DumpManager, 58 systemClock: SystemClock, 59 files: Files, 60 ) : this( 61 dumpManager, 62 systemClock, 63 files, 64 Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"), 65 MIN_WRITE_GAP, 66 MAX_AGE_TO_DUMP, 67 ) 68 69 /** 70 * Dumps all active log buffers to a file 71 * 72 * The file will be prefaced by the [reason], which will then be returned (presumably so it can 73 * be thrown). 74 */ 75 fun <T : Throwable> record(reason: T): T { 76 val start = systemClock.uptimeMillis() 77 var duration = 0L 78 79 Log.i(TAG, "Performing emergency dump of log buffers") 80 81 val millisSinceLastWrite = getMillisSinceLastWrite(logPath) 82 if (millisSinceLastWrite < minWriteGap) { 83 Log.w(TAG, "Cannot dump logs, last write was only $millisSinceLastWrite ms ago") 84 return reason 85 } 86 87 try { 88 val writer = files.newBufferedWriter(logPath, CREATE, TRUNCATE_EXISTING) 89 writer.use { out -> 90 val pw = PrintWriter(out) 91 92 pw.println(DATE_FORMAT.format(systemClock.currentTimeMillis())) 93 pw.println() 94 pw.println("Dump triggered by exception:") 95 reason.printStackTrace(pw) 96 val buffers = dumpManager.getLogBuffers() 97 dumpEntries(buffers, pw) 98 duration = systemClock.uptimeMillis() - start 99 pw.println() 100 pw.println("Buffer eulogy took ${duration}ms") 101 } 102 } catch (e: Exception) { 103 Log.e(TAG, "Exception while attempting to dump buffers, bailing", e) 104 } 105 106 Log.i(TAG, "Buffer eulogy took ${duration}ms") 107 108 return reason 109 } 110 111 /** If a eulogy file is present, writes its contents to [pw]. */ 112 fun readEulogyIfPresent(pw: PrintWriter) { 113 try { 114 val millisSinceLastWrite = getMillisSinceLastWrite(logPath) 115 if (millisSinceLastWrite > maxLogAgeToDump) { 116 Log.i( 117 TAG, 118 "Not eulogizing buffers; they are " + 119 TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) + 120 " hours old" 121 ) 122 return 123 } 124 125 files.lines(logPath).use { s -> 126 pw.println() 127 pw.println() 128 pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============") 129 s.forEach { line -> pw.println(line) } 130 } 131 } catch (e: IOException) { 132 // File doesn't exist, okay 133 } catch (e: UncheckedIOException) { 134 Log.e(TAG, "UncheckedIOException while dumping the core", e) 135 } 136 } 137 138 private fun getMillisSinceLastWrite(path: Path): Long { 139 val stats = 140 try { 141 files.readAttributes(path, BasicFileAttributes::class.java) 142 } catch (e: IOException) { 143 // File doesn't exist 144 null 145 } 146 return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0) 147 } 148 } 149 150 private const val TAG = "BufferEulogizer" 151 private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5) 152 private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48) 153 private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) 154