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.util.Log; 23 24 import androidx.annotation.GuardedBy; 25 import androidx.annotation.NonNull; 26 import androidx.annotation.Nullable; 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.Date; 36 import java.util.Locale; 37 import java.util.stream.Stream; 38 39 public class LegacyLogging { 40 public static final String TAG = "LegacyMediaProvider"; 41 42 /** Size limit of each persistent log file, in bytes */ 43 private static final int PERSISTENT_SIZE = 32 * 1024; 44 private static final SimpleDateFormat DATE_FORMAT = 45 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ROOT); 46 private static final Object LOCK = new Object(); 47 48 @GuardedBy("LOCK") 49 private static Path sPersistentDir; 50 @GuardedBy("LOCK") 51 private static Path sPersistentFile; 52 @GuardedBy("LOCK") 53 private static Writer sWriter; 54 55 /** 56 * Initialize persistent logging which is then available through 57 * {@link #logPersistent(String)} and {@link #dumpPersistent(PrintWriter)}. 58 */ initPersistent(@onNull File persistentDir)59 public static void initPersistent(@NonNull File persistentDir) { 60 synchronized (LOCK) { 61 sPersistentDir = persistentDir.toPath(); 62 closeWriterAndUpdatePathLocked(null); 63 } 64 } 65 66 /** 67 * Write the given message to persistent logs. 68 */ logPersistent(@onNull String format, @Nullable Object ... args)69 public static void logPersistent(@NonNull String format, @Nullable Object ... args) { 70 final String msg = (args == null || args.length == 0) 71 ? format : String.format(Locale.ROOT, format, args); 72 73 Log.i(TAG, msg); 74 75 synchronized (LOCK) { 76 if (sPersistentDir == null) return; 77 78 try { 79 Path path = resolveCurrentPersistentFileLocked(); 80 if (!path.equals(sPersistentFile)) { 81 closeWriterAndUpdatePathLocked(path); 82 } 83 84 if (sWriter == null) { 85 sWriter = Files.newBufferedWriter(path, CREATE, APPEND); 86 } 87 88 sWriter.write(DATE_FORMAT.format(new Date()) + " " + msg + "\n"); 89 // Flush to guarantee that all our writes have been sent to the filesystem 90 sWriter.flush(); 91 } catch (IOException e) { 92 closeWriterAndUpdatePathLocked(null); 93 Log.w(TAG, "Failed to write: " + sPersistentFile, e); 94 } 95 } 96 } 97 98 @GuardedBy("LOCK") closeWriterAndUpdatePathLocked(@ullable Path newPath)99 private static void closeWriterAndUpdatePathLocked(@Nullable Path newPath) { 100 if (sWriter != null) { 101 try { 102 sWriter.close(); 103 } catch (IOException ignored) { 104 Log.w(TAG, "Failed to close: " + sPersistentFile, ignored); 105 } 106 sWriter = null; 107 } 108 sPersistentFile = newPath; 109 } 110 111 /** 112 * Dump any persistent logs. 113 */ dumpPersistent(@onNull PrintWriter pw)114 public static void dumpPersistent(@NonNull PrintWriter pw) { 115 Path persistentDir = null; 116 synchronized (LOCK) { 117 if (sPersistentDir == null) return; 118 persistentDir = sPersistentDir; 119 } 120 121 try (Stream<Path> stream = Files.list(persistentDir)) { 122 stream.sorted().forEach((path) -> { 123 dumpPersistentFile(path, pw); 124 }); 125 } catch (IOException e) { 126 pw.println(e.getMessage()); 127 pw.println(); 128 } 129 } 130 dumpPersistentFile(@onNull Path path, @NonNull PrintWriter pw)131 private static void dumpPersistentFile(@NonNull Path path, @NonNull PrintWriter pw) { 132 pw.println("Persistent logs in " + path + ":"); 133 try (Stream<String> stream = Files.lines(path)) { 134 stream.forEach((line) -> { 135 pw.println(" " + line); 136 }); 137 pw.println(); 138 } catch (IOException e) { 139 pw.println(" " + e.getMessage()); 140 pw.println(); 141 } 142 } 143 144 /** 145 * Resolve the current log file to write new entries into. Automatically 146 * starts new files when the current file is larger than 147 * {@link #PERSISTENT_SIZE}. 148 */ 149 @GuardedBy("LOCK") resolveCurrentPersistentFileLocked()150 private static @NonNull Path resolveCurrentPersistentFileLocked() throws IOException { 151 if (sPersistentFile != null && sPersistentFile.toFile().length() < PERSISTENT_SIZE) { 152 return sPersistentFile; 153 } 154 155 return sPersistentDir.resolve(String.valueOf(System.currentTimeMillis())); 156 } 157 } 158