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)