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.NonNull;
27 
28 import java.io.File;
29 import java.io.IOException;
30 import java.io.PrintWriter;
31 import java.io.Writer;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.text.SimpleDateFormat;
35 import java.util.Comparator;
36 import java.util.Date;
37 import java.util.Optional;
38 import java.util.stream.Stream;
39 
40 public class Logging {
41     public static final String TAG = "MediaProvider";
42     public static final boolean LOGW = Log.isLoggable(TAG, Log.WARN);
43     public static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
44     public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
45 
46     public static final boolean IS_DEBUGGABLE =
47             SystemProperties.getInt("ro.debuggable", 0) == 1;
48 
49     /** Size limit of each persistent log file, in bytes */
50     private static final int PERSISTENT_SIZE = 32 * 1024;
51     private static final int PERSISTENT_COUNT = 4;
52     private static final long PERSISTENT_AGE = DateUtils.WEEK_IN_MILLIS;
53 
54     private static Path sPersistentDir;
55     private static SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
56 
57     /**
58      * Initialize persistent logging which is then available through
59      * {@link #logPersistent(String)} and {@link #dumpPersistent(PrintWriter)}.
60      */
initPersistent(@onNull File persistentDir)61     public static void initPersistent(@NonNull File persistentDir) {
62         sPersistentDir = persistentDir.toPath();
63     }
64 
65     /**
66      * Write the given message to persistent logs.
67      */
logPersistent(@onNull String msg)68     public static void logPersistent(@NonNull String msg) {
69         Log.i(TAG, msg);
70 
71         if (sPersistentDir == null) return;
72         try (Writer w = Files.newBufferedWriter(resolveCurrentPersistentFile(), CREATE, APPEND)) {
73             w.write(sDateFormat.format(new Date()) + " " + msg + "\n");
74         } catch (IOException e) {
75             Log.w(TAG, "Failed to persist: " + e);
76         }
77     }
78 
79     /**
80      * Trim any persistent logs, typically called during idle maintenance.
81      */
trimPersistent()82     public static void trimPersistent() {
83         if (sPersistentDir == null) return;
84         FileUtils.deleteOlderFiles(sPersistentDir.toFile(), PERSISTENT_COUNT, PERSISTENT_AGE);
85     }
86 
87     /**
88      * Dump any persistent logs.
89      */
dumpPersistent(@onNull PrintWriter pw)90     public static void dumpPersistent(@NonNull PrintWriter pw) {
91         if (sPersistentDir == null) return;
92         try (Stream<Path> stream = Files.list(sPersistentDir)) {
93             stream.sorted().forEach((path) -> {
94                 dumpPersistentFile(path, pw);
95             });
96         } catch (IOException e) {
97             pw.println(e.getMessage());
98             pw.println();
99         }
100     }
101 
dumpPersistentFile(@onNull Path path, @NonNull PrintWriter pw)102     private static void dumpPersistentFile(@NonNull Path path, @NonNull PrintWriter pw) {
103         pw.println("Persistent logs in " + path + ":");
104         try (Stream<String> stream = Files.lines(path)) {
105             stream.forEach((line) -> {
106                 pw.println("  " + line);
107             });
108             pw.println();
109         } catch (IOException e) {
110             pw.println("  " + e.getMessage());
111             pw.println();
112         }
113     }
114 
115     /**
116      * Resolve the current log file to write new entries into. Automatically
117      * starts new files when the current file is larger than
118      * {@link #PERSISTENT_SIZE}.
119      */
resolveCurrentPersistentFile()120     private static @NonNull Path resolveCurrentPersistentFile() throws IOException {
121         try (Stream<Path> stream = Files.list(sPersistentDir)) {
122             Optional<Path> latest = stream.max(Comparator.naturalOrder());
123             if (latest.isPresent() && latest.get().toFile().length() < PERSISTENT_SIZE) {
124                 return latest.get();
125             } else {
126                 return sPersistentDir.resolve(String.valueOf(System.currentTimeMillis()));
127             }
128         }
129     }
130 }
131