blob: ff69e5c4697039c8651e2c7e18d8f5120de744e7 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.providers.media.util;
import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import android.os.SystemProperties;
import android.text.format.DateUtils;
import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Comparator;
import java.util.Date;
import java.util.Optional;
import java.util.stream.Stream;
public class Logging {
public static final String TAG = "MediaProvider";
public static final boolean LOGW = Log.isLoggable(TAG, Log.WARN);
public static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
public static final boolean IS_DEBUGGABLE =
SystemProperties.getInt("ro.debuggable", 0) == 1;
/** Size limit of each persistent log file, in bytes */
private static final int PERSISTENT_SIZE = 32 * 1024;
private static final int PERSISTENT_COUNT = 4;
private static final long PERSISTENT_AGE = DateUtils.WEEK_IN_MILLIS;
private static final SimpleDateFormat DATE_FORMAT =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static final Object LOCK = new Object();
@GuardedBy("LOCK")
private static Path sPersistentDir;
@GuardedBy("LOCK")
private static Path sPersistentFile;
@GuardedBy("LOCK")
private static Writer sWriter;
/**
* Initialize persistent logging which is then available through
* {@link #logPersistent(String)} and {@link #dumpPersistent(PrintWriter)}.
*/
public static void initPersistent(@NonNull File persistentDir) {
synchronized (LOCK) {
sPersistentDir = persistentDir.toPath();
closeWriterAndUpdatePathLocked(null);
}
}
/**
* Write the given message to persistent logs.
*/
public static void logPersistent(@NonNull String msg) {
Log.i(TAG, msg);
synchronized (LOCK) {
if (sPersistentDir == null) return;
try {
Path path = resolveCurrentPersistentFileLocked();
if (!path.equals(sPersistentFile)) {
closeWriterAndUpdatePathLocked(path);
}
if (sWriter == null) {
sWriter = Files.newBufferedWriter(path, CREATE, APPEND);
}
sWriter.write(DATE_FORMAT.format(new Date()) + " " + msg + "\n");
// Flush to guarantee that all our writes have been sent to the filesystem
sWriter.flush();
} catch (IOException e) {
closeWriterAndUpdatePathLocked(null);
Log.w(TAG, "Failed to write: " + sPersistentFile, e);
}
}
}
@GuardedBy("LOCK")
private static void closeWriterAndUpdatePathLocked(@Nullable Path newPath) {
if (sWriter != null) {
try {
sWriter.close();
} catch (IOException ignored) {
Log.w(TAG, "Failed to close: " + sPersistentFile, ignored);
}
sWriter = null;
}
sPersistentFile = newPath;
}
/**
* Trim any persistent logs, typically called during idle maintenance.
*/
public static void trimPersistent() {
File persistentDir = null;
synchronized (LOCK) {
if (sPersistentDir == null) return;
persistentDir = sPersistentDir.toFile();
closeWriterAndUpdatePathLocked(sPersistentFile);
}
FileUtils.deleteOlderFiles(persistentDir, PERSISTENT_COUNT, PERSISTENT_AGE);
}
/**
* Dump any persistent logs.
*/
public static void dumpPersistent(@NonNull PrintWriter pw) {
Path persistentDir = null;
synchronized (LOCK) {
if (sPersistentDir == null) return;
persistentDir = sPersistentDir;
}
try (Stream<Path> stream = Files.list(persistentDir)) {
stream.sorted().forEach((path) -> {
dumpPersistentFile(path, pw);
});
} catch (IOException e) {
pw.println(e.getMessage());
pw.println();
}
}
private static void dumpPersistentFile(@NonNull Path path, @NonNull PrintWriter pw) {
pw.println("Persistent logs in " + path + ":");
try (Stream<String> stream = Files.lines(path)) {
stream.forEach((line) -> {
pw.println(" " + line);
});
pw.println();
} catch (IOException e) {
pw.println(" " + e.getMessage());
pw.println();
}
}
/**
* Resolve the current log file to write new entries into. Automatically
* starts new files when the current file is larger than
* {@link #PERSISTENT_SIZE}.
*/
@GuardedBy("LOCK")
private static @NonNull Path resolveCurrentPersistentFileLocked() throws IOException {
if (sPersistentFile != null && sPersistentFile.toFile().length() < PERSISTENT_SIZE) {
return sPersistentFile;
}
return sPersistentDir.resolve(String.valueOf(System.currentTimeMillis()));
}
}