/*
* Copyright (C) 2014 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.server.hdmi;
import android.annotation.Nullable;
import android.os.Build;
import android.os.SystemClock;
import android.util.Pair;
import android.util.Slog;
import java.util.HashMap;
/**
* A logger that prevents spammy log. For the same log message, it logs once every 20seconds.
* This class is not thread-safe.
* <p>
* For convenience, use single character prefix for all messages.
* Here are common acronyms
* <ul>
* <li>[T]: Timout
* <li>[R]: Received message
* <li>[S]: Sent message
* <li>[P]: Device polling result
* </ul>
*/
final class HdmiLogger {
private static final String TAG = "HDMI";
// Logging duration for same error message.
private static final long ERROR_LOG_DURATTION_MILLIS = 20 * 1000; // 20s
private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE);
private static final ThreadLocal<HdmiLogger> sLogger = new ThreadLocal<>();
// Key (String): log message.
// Value (Pair(Long, Integer)): a pair of last log time millis and the number of logMessage.
// Cache for warning.
private final HashMap<String, Pair<Long, Integer>> mWarningTimingCache = new HashMap<>();
// Cache for error.
private final HashMap<String, Pair<Long, Integer>> mErrorTimingCache = new HashMap<>();
private HdmiLogger() {
}
static final void warning(String logMessage, Object... objs) {
getLogger().warningInternal(toLogString(logMessage, objs));
}
private void warningInternal(String logMessage) {
String log = updateLog(mWarningTimingCache, logMessage);
if (!log.isEmpty()) {
Slog.w(TAG, log);
}
}
static final void error(String logMessage, Object... objs) {
getLogger().errorInternal(toLogString(logMessage, objs));
}
private void errorInternal(String logMessage) {
String log = updateLog(mErrorTimingCache, logMessage);
if (!log.isEmpty()) {
Slog.e(TAG, log);
}
}
static final void debug(String logMessage, Object... objs) {
getLogger().debugInternal(toLogString(logMessage, objs));
}
private void debugInternal(String logMessage) {
if (true || IS_USER_BUILD) {
return;
}
Slog.d(TAG, logMessage);
}
private static final String toLogString(String logMessage, Object[] objs) {
if (objs.length > 0) {
return String.format(logMessage, objs);
} else {
return logMessage;
}
}
private static HdmiLogger getLogger() {
HdmiLogger logger = sLogger.get();
if (logger == null) {
logger = new HdmiLogger();
sLogger.set(logger);
}
return logger;
}
private static String updateLog(HashMap<String, Pair<Long, Integer>> cache, String logMessage) {
long curTime = SystemClock.uptimeMillis();
Pair<Long, Integer> timing = cache.get(logMessage);
if (shouldLogNow(timing, curTime)) {
String log = buildMessage(logMessage, timing);
cache.put(logMessage, new Pair<>(curTime, 1));
return log;
} else {
increaseLogCount(cache, logMessage);
}
return "";
}
private static String buildMessage(String message, @Nullable Pair<Long, Integer> timing) {
return new StringBuilder()
.append("[").append(timing == null ? 1 : timing.second).append("]:")
.append(message).toString();
}
private static void increaseLogCount(HashMap<String, Pair<Long, Integer>> cache,
String message) {
Pair<Long, Integer> timing = cache.get(message);
if (timing != null) {
cache.put(message, new Pair<>(timing.first, timing.second + 1));
}
}
private static boolean shouldLogNow(@Nullable Pair<Long, Integer> timing, long curTime) {
return timing == null || curTime - timing.first > ERROR_LOG_DURATTION_MILLIS;
}
}