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