/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.dump import android.content.Context import android.util.Log import com.android.systemui.log.LogBuffer import com.android.systemui.util.io.Files import com.android.systemui.util.time.SystemClock import java.io.IOException import java.io.PrintWriter import java.io.UncheckedIOException import java.nio.file.Path import java.nio.file.Paths import java.nio.file.StandardOpenOption.CREATE import java.nio.file.StandardOpenOption.TRUNCATE_EXISTING import java.nio.file.attribute.BasicFileAttributes import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton /** * Dumps all [LogBuffer]s to a file * * Intended for emergencies, i.e. we're about to crash. This file can then be read at a later date * (usually in a bug report). */ @Singleton class LogBufferEulogizer( private val dumpManager: DumpManager, private val systemClock: SystemClock, private val files: Files, private val logPath: Path, private val minWriteGap: Long, private val maxLogAgeToDump: Long ) { @Inject constructor( context: Context, dumpManager: DumpManager, systemClock: SystemClock, files: Files ) : this( dumpManager, systemClock, files, Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"), MIN_WRITE_GAP, MAX_AGE_TO_DUMP ) /** * Dumps all active log buffers to a file * * The file will be prefaced by the [reason], which will then be returned (presumably so it can * be thrown). */ fun record(reason: T): T { val start = systemClock.uptimeMillis() var duration = 0L Log.i(TAG, "Performing emergency dump of log buffers") val millisSinceLastWrite = getMillisSinceLastWrite(logPath) if (millisSinceLastWrite < minWriteGap) { Log.w(TAG, "Cannot dump logs, last write was only $millisSinceLastWrite ms ago") return reason } try { val writer = files.newBufferedWriter(logPath, CREATE, TRUNCATE_EXISTING) writer.use { out -> val pw = PrintWriter(out) pw.println(DATE_FORMAT.format(systemClock.currentTimeMillis())) pw.println() pw.println("Dump triggered by exception:") reason.printStackTrace(pw) dumpManager.dumpBuffers(pw, 0) duration = systemClock.uptimeMillis() - start pw.println() pw.println("Buffer eulogy took ${duration}ms") } } catch (e: Exception) { Log.e(TAG, "Exception while attempting to dump buffers, bailing", e) } Log.i(TAG, "Buffer eulogy took ${duration}ms") return reason } /** * If a eulogy file is present, writes its contents to [pw]. */ fun readEulogyIfPresent(pw: PrintWriter) { try { val millisSinceLastWrite = getMillisSinceLastWrite(logPath) if (millisSinceLastWrite > maxLogAgeToDump) { Log.i(TAG, "Not eulogizing buffers; they are " + TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) + " hours old") return } files.lines(logPath).use { s -> pw.println() pw.println() pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============") s.forEach { line -> pw.println(line) } } } catch (e: IOException) { // File doesn't exist, okay } catch (e: UncheckedIOException) { Log.e(TAG, "UncheckedIOException while dumping the core", e) } } private fun getMillisSinceLastWrite(path: Path): Long { val stats = try { files.readAttributes(path, BasicFileAttributes::class.java) } catch (e: IOException) { // File doesn't exist null } return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0) } } private const val TAG = "BufferEulogizer" private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5) private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48) private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)