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