• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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.providers.media.util;
18 
19 import static java.nio.file.StandardOpenOption.APPEND;
20 import static java.nio.file.StandardOpenOption.CREATE;
21 
22 import android.os.SystemProperties;
23 import android.text.format.DateUtils;
24 import android.util.Log;
25 
26 import androidx.annotation.GuardedBy;
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 
30 import java.io.File;
31 import java.io.IOException;
32 import java.io.PrintWriter;
33 import java.io.Writer;
34 import java.nio.file.Files;
35 import java.nio.file.Path;
36 import java.text.SimpleDateFormat;
37 import java.util.Comparator;
38 import java.util.Date;
39 import java.util.Optional;
40 import java.util.stream.Stream;
41 
42 public class Logging {
43     public static final String TAG = "MediaProvider";
44     public static final boolean LOGW = Log.isLoggable(TAG, Log.WARN);
45     public static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
46     public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
47 
48     public static final boolean IS_DEBUGGABLE =
49             SystemProperties.getInt("ro.debuggable", 0) == 1;
50 
51     /** Size limit of each persistent log file, in bytes */
52     private static final int PERSISTENT_SIZE = 32 * 1024;
53     private static final int PERSISTENT_COUNT = 4;
54     private static final long PERSISTENT_AGE = DateUtils.WEEK_IN_MILLIS;
55     private static final SimpleDateFormat DATE_FORMAT =
56             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
57     private static final Object LOCK = new Object();
58 
59     @GuardedBy("LOCK")
60     private static Path sPersistentDir;
61     @GuardedBy("LOCK")
62     private static Path sPersistentFile;
63     @GuardedBy("LOCK")
64     private static Writer sWriter;
65 
66     /**
67      * Initialize persistent logging which is then available through
68      * {@link #logPersistent(String)} and {@link #dumpPersistent(PrintWriter)}.
69      */
initPersistent(@onNull File persistentDir)70     public static void initPersistent(@NonNull File persistentDir) {
71         synchronized (LOCK) {
72             sPersistentDir = persistentDir.toPath();
73             closeWriterAndUpdatePathLocked(null);
74         }
75     }
76 
77     /**
78      * Write the given message to persistent logs.
79      */
logPersistent(@onNull String msg)80     public static void logPersistent(@NonNull String msg) {
81         Log.i(TAG, msg);
82 
83         synchronized (LOCK) {
84             if (sPersistentDir == null) return;
85 
86             try {
87                 Path path = resolveCurrentPersistentFileLocked();
88                 if (!path.equals(sPersistentFile)) {
89                     closeWriterAndUpdatePathLocked(path);
90                 }
91 
92                 if (sWriter == null) {
93                     sWriter = Files.newBufferedWriter(path, CREATE, APPEND);
94                 }
95 
96                 sWriter.write(DATE_FORMAT.format(new Date()) + " " + msg + "\n");
97                 // Flush to guarantee that all our writes have been sent to the filesystem
98                 sWriter.flush();
99             } catch (IOException e) {
100                 closeWriterAndUpdatePathLocked(null);
101                 Log.w(TAG, "Failed to write: " + sPersistentFile, e);
102             }
103         }
104     }
105 
106     @GuardedBy("LOCK")
closeWriterAndUpdatePathLocked(@ullable Path newPath)107     private static void closeWriterAndUpdatePathLocked(@Nullable Path newPath) {
108         if (sWriter != null) {
109             try {
110                 sWriter.close();
111             } catch (IOException ignored) {
112                 Log.w(TAG, "Failed to close: " + sPersistentFile, ignored);
113             }
114             sWriter = null;
115         }
116         sPersistentFile = newPath;
117     }
118 
119     /**
120      * Trim any persistent logs, typically called during idle maintenance.
121      */
trimPersistent()122     public static void trimPersistent() {
123         File persistentDir = null;
124         synchronized (LOCK) {
125             if (sPersistentDir == null) return;
126             persistentDir = sPersistentDir.toFile();
127 
128             closeWriterAndUpdatePathLocked(sPersistentFile);
129         }
130 
131         FileUtils.deleteOlderFiles(persistentDir, PERSISTENT_COUNT, PERSISTENT_AGE);
132     }
133 
134     /**
135      * Dump any persistent logs.
136      */
dumpPersistent(@onNull PrintWriter pw)137     public static void dumpPersistent(@NonNull PrintWriter pw) {
138         Path persistentDir = null;
139         synchronized (LOCK) {
140             if (sPersistentDir == null) return;
141             persistentDir = sPersistentDir;
142         }
143 
144         try (Stream<Path> stream = Files.list(persistentDir)) {
145             stream.sorted().forEach((path) -> {
146                 dumpPersistentFile(path, pw);
147             });
148         } catch (IOException e) {
149             pw.println(e.getMessage());
150             pw.println();
151         }
152     }
153 
dumpPersistentFile(@onNull Path path, @NonNull PrintWriter pw)154     private static void dumpPersistentFile(@NonNull Path path, @NonNull PrintWriter pw) {
155         pw.println("Persistent logs in " + path + ":");
156         try (Stream<String> stream = Files.lines(path)) {
157             stream.forEach((line) -> {
158                 pw.println("  " + line);
159             });
160             pw.println();
161         } catch (IOException e) {
162             pw.println("  " + e.getMessage());
163             pw.println();
164         }
165     }
166 
167     /**
168      * Resolve the current log file to write new entries into. Automatically
169      * starts new files when the current file is larger than
170      * {@link #PERSISTENT_SIZE}.
171      */
172     @GuardedBy("LOCK")
resolveCurrentPersistentFileLocked()173     private static @NonNull Path resolveCurrentPersistentFileLocked() throws IOException {
174         if (sPersistentFile != null && sPersistentFile.toFile().length() < PERSISTENT_SIZE) {
175             return sPersistentFile;
176         }
177 
178         return sPersistentDir.resolve(String.valueOf(System.currentTimeMillis()));
179     }
180 }
181