diff options
author | Eric Erfanian <erfanian@google.com> | 2017-04-06 23:16:42 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-04-06 23:16:42 +0000 |
commit | 28188020060d1ad3304dfa0367486ff5bb35da62 (patch) | |
tree | 8d08d4f2292aa84dc435d455891e7ab633d95a25 /java/com/android/dialer/persistentlog/PersistentLogger.java | |
parent | 34e183cfd77f68e0ade42d2080c76f54a319430d (diff) | |
parent | 01a86435499d66b267765f89b7dd9c83c158ddfe (diff) |
Update AOSP Dialer source from internal google3 repository at cl/152373142. am: d8046e520a
am: 01a8643549
Change-Id: Ib92542e6b88bc8d8906982280244661ed7dbca8f
Diffstat (limited to 'java/com/android/dialer/persistentlog/PersistentLogger.java')
-rw-r--r-- | java/com/android/dialer/persistentlog/PersistentLogger.java | 170 |
1 files changed, 170 insertions, 0 deletions
diff --git a/java/com/android/dialer/persistentlog/PersistentLogger.java b/java/com/android/dialer/persistentlog/PersistentLogger.java new file mode 100644 index 000000000..049eb9687 --- /dev/null +++ b/java/com/android/dialer/persistentlog/PersistentLogger.java @@ -0,0 +1,170 @@ +/* + * 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 com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +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: 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); + 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); + } + + /** 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 = 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); + } +} |