186 lines
6.0 KiB
Java
186 lines
6.0 KiB
Java
/*
|
|
* Copyright (C) 2017 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.dialer.persistentlog;
|
|
|
|
import android.content.Context;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.support.annotation.AnyThread;
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.VisibleForTesting;
|
|
import android.support.annotation.WorkerThread;
|
|
import android.support.v4.os.UserManagerCompat;
|
|
import com.android.dialer.common.Assert;
|
|
import com.android.dialer.common.LogUtil;
|
|
import com.android.dialer.strictmode.StrictModeUtils;
|
|
import java.io.IOException;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.List;
|
|
import java.util.concurrent.CountDownLatch;
|
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
|
|
/**
|
|
* Logs data that is persisted across app termination and device reboot. The logs are stored as
|
|
* rolling files in cache with a limit of {@link #LOG_FILE_SIZE_LIMIT} * {@link
|
|
* #LOG_FILE_COUNT_LIMIT}. The log writing is batched and there is a {@link #FLUSH_DELAY_MILLIS}
|
|
* delay before the logs are committed to disk to avoid excessive IO. If the app is terminated
|
|
* before the logs are committed it will be lost. {@link
|
|
* com.google.android.apps.dialer.crashreporter.SilentCrashReporter} is expected to handle such
|
|
* cases.
|
|
*
|
|
* <p>{@link #logText(String, String)} should be used to log ad-hoc text logs. TODO(twyen): switch
|
|
* to structured logging
|
|
*/
|
|
public final class PersistentLogger {
|
|
|
|
private static final int FLUSH_DELAY_MILLIS = 200;
|
|
private static final String LOG_FOLDER = "plain_text";
|
|
private static final int MESSAGE_FLUSH = 1;
|
|
|
|
@VisibleForTesting static final int LOG_FILE_SIZE_LIMIT = 64 * 1024;
|
|
@VisibleForTesting static final int LOG_FILE_COUNT_LIMIT = 8;
|
|
|
|
private static PersistentLogFileHandler fileHandler;
|
|
|
|
private static HandlerThread loggerThread;
|
|
private static Handler loggerThreadHandler;
|
|
|
|
private static final LinkedBlockingQueue<byte[]> messageQueue = new LinkedBlockingQueue<>();
|
|
|
|
private PersistentLogger() {}
|
|
|
|
public static void initialize(Context context) {
|
|
fileHandler =
|
|
new PersistentLogFileHandler(LOG_FOLDER, LOG_FILE_SIZE_LIMIT, LOG_FILE_COUNT_LIMIT);
|
|
loggerThread = new HandlerThread("PersistentLogger");
|
|
loggerThread.start();
|
|
loggerThreadHandler =
|
|
new Handler(
|
|
loggerThread.getLooper(),
|
|
(message) -> {
|
|
if (message.what == MESSAGE_FLUSH) {
|
|
if (messageQueue.isEmpty()) {
|
|
return true;
|
|
}
|
|
loggerThreadHandler.removeMessages(MESSAGE_FLUSH);
|
|
List<byte[]> messages = new ArrayList<>();
|
|
messageQueue.drainTo(messages);
|
|
if (!UserManagerCompat.isUserUnlocked(context)) {
|
|
return true;
|
|
}
|
|
try {
|
|
fileHandler.writeLogs(messages);
|
|
} catch (IOException e) {
|
|
LogUtil.e("PersistentLogger.MESSAGE_FLUSH", "error writing message", e);
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
loggerThreadHandler.post(() -> fileHandler.initialize(context));
|
|
}
|
|
|
|
static HandlerThread getLoggerThread() {
|
|
return loggerThread;
|
|
}
|
|
|
|
@AnyThread
|
|
public static void logText(String tag, String string) {
|
|
log(buildTextLog(tag, string));
|
|
}
|
|
|
|
@VisibleForTesting
|
|
@AnyThread
|
|
static void log(byte[] data) {
|
|
messageQueue.add(data);
|
|
loggerThreadHandler.sendEmptyMessageDelayed(MESSAGE_FLUSH, FLUSH_DELAY_MILLIS);
|
|
}
|
|
|
|
@VisibleForTesting
|
|
/** write raw bytes directly to the log file, likely corrupting it. */
|
|
static void rawLogForTest(byte[] data) {
|
|
try {
|
|
fileHandler.writeRawLogsForTest(data);
|
|
} catch (IOException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
/** Dump the log as human readable string. Blocks until the dump is finished. */
|
|
@NonNull
|
|
@WorkerThread
|
|
public static String dumpLogToString() {
|
|
Assert.isWorkerThread();
|
|
DumpStringRunnable dumpStringRunnable = new DumpStringRunnable();
|
|
loggerThreadHandler.post(dumpStringRunnable);
|
|
try {
|
|
return dumpStringRunnable.get();
|
|
} catch (InterruptedException e) {
|
|
Thread.currentThread().interrupt();
|
|
return "Cannot dump logText: " + e;
|
|
}
|
|
}
|
|
|
|
private static class DumpStringRunnable implements Runnable {
|
|
private String result;
|
|
private final CountDownLatch latch = new CountDownLatch(1);
|
|
|
|
@Override
|
|
public void run() {
|
|
result = dumpLogToStringInternal();
|
|
latch.countDown();
|
|
}
|
|
|
|
public String get() throws InterruptedException {
|
|
latch.await();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
@NonNull
|
|
@WorkerThread
|
|
private static String dumpLogToStringInternal() {
|
|
StringBuilder result = new StringBuilder();
|
|
List<byte[]> logs;
|
|
try {
|
|
logs = readLogs();
|
|
} catch (IOException e) {
|
|
return "Cannot dump logText: " + e;
|
|
}
|
|
|
|
for (byte[] log : logs) {
|
|
result.append(new String(log, StandardCharsets.UTF_8)).append("\n");
|
|
}
|
|
return result.toString();
|
|
}
|
|
|
|
@NonNull
|
|
@WorkerThread
|
|
@VisibleForTesting
|
|
static List<byte[]> readLogs() throws IOException {
|
|
Assert.isWorkerThread();
|
|
return fileHandler.getLogs();
|
|
}
|
|
|
|
private static byte[] buildTextLog(String tag, String string) {
|
|
Calendar c = StrictModeUtils.bypass(() -> Calendar.getInstance());
|
|
return String.format("%tm-%td %tH:%tM:%tS.%tL - %s - %s", c, c, c, c, c, c, tag, string)
|
|
.getBytes(StandardCharsets.UTF_8);
|
|
}
|
|
}
|