1 /* 2 * Copyright (C) 2017 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.net.module.util; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.text.TextUtils; 22 import android.util.Log; 23 24 import java.io.FileDescriptor; 25 import java.io.PrintWriter; 26 import java.time.LocalDateTime; 27 import java.util.ArrayDeque; 28 import java.util.Deque; 29 import java.util.Iterator; 30 import java.util.StringJoiner; 31 32 33 /** 34 * Class to centralize logging functionality for tethering. 35 * 36 * All access to class methods other than dump() must be on the same thread. 37 * 38 * @hide 39 */ 40 public class SharedLog { 41 private static final int DEFAULT_MAX_RECORDS = 500; 42 private static final String COMPONENT_DELIMITER = "."; 43 44 private enum Category { 45 NONE, 46 ERROR, 47 MARK, 48 WARN, 49 VERBOSE, 50 TERRIBLE, 51 } 52 53 private final LocalLog mLocalLog; 54 // The tag to use for output to the system log. This is not output to the 55 // LocalLog because that would be redundant. 56 private final String mTag; 57 // The component (or subcomponent) of a system that is sharing this log. 58 // This can grow in depth if components call forSubComponent() to obtain 59 // their SharedLog instance. The tag is not included in the component for 60 // brevity. 61 private final String mComponent; 62 SharedLog(String tag)63 public SharedLog(String tag) { 64 this(DEFAULT_MAX_RECORDS, tag); 65 } 66 SharedLog(int maxRecords, String tag)67 public SharedLog(int maxRecords, String tag) { 68 this(new LocalLog(maxRecords), tag, tag); 69 } 70 SharedLog(LocalLog localLog, String tag, String component)71 private SharedLog(LocalLog localLog, String tag, String component) { 72 mLocalLog = localLog; 73 mTag = tag; 74 mComponent = component; 75 } 76 getTag()77 public String getTag() { 78 return mTag; 79 } 80 81 /** 82 * Create a SharedLog based on this log with an additional component prefix on each logged line. 83 */ forSubComponent(String component)84 public SharedLog forSubComponent(String component) { 85 if (!isRootLogInstance()) { 86 component = mComponent + COMPONENT_DELIMITER + component; 87 } 88 return new SharedLog(mLocalLog, mTag, component); 89 } 90 91 /** 92 * Dump the contents of this log. 93 * 94 * <p>This method may be called on any thread. 95 */ dump(FileDescriptor fd, PrintWriter writer, String[] args)96 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 97 mLocalLog.dump(writer); 98 } 99 100 /** 101 * Reverse dump the contents of this log. 102 * 103 * <p>This method may be called on any thread. 104 */ reverseDump(PrintWriter writer)105 public void reverseDump(PrintWriter writer) { 106 mLocalLog.reverseDump(writer); 107 } 108 109 ////// 110 // Methods that both log an entry and emit it to the system log. 111 ////// 112 113 /** 114 * Log an error due to an exception. This does not include the exception stacktrace. 115 * 116 * <p>The log entry will be also added to the system log. 117 * @see #e(String, Throwable) 118 */ e(Exception e)119 public void e(Exception e) { 120 Log.e(mTag, record(Category.ERROR, e.toString())); 121 } 122 123 /** 124 * Log an error message. 125 * 126 * <p>The log entry will be also added to the system log. 127 */ e(String msg)128 public void e(String msg) { 129 Log.e(mTag, record(Category.ERROR, msg)); 130 } 131 132 /** 133 * Log an error due to an exception, with the exception stacktrace if provided. 134 * 135 * <p>The error and exception message appear in the shared log, but the stacktrace is only 136 * logged in general log output (logcat). The log entry will be also added to the system log. 137 */ e(@onNull String msg, @Nullable Throwable exception)138 public void e(@NonNull String msg, @Nullable Throwable exception) { 139 if (exception == null) { 140 e(msg); 141 return; 142 } 143 Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception); 144 } 145 146 /** 147 * Log an informational message. 148 * 149 * <p>The log entry will be also added to the system log. 150 */ i(String msg)151 public void i(String msg) { 152 Log.i(mTag, record(Category.NONE, msg)); 153 } 154 155 /** 156 * Log a warning message. 157 * 158 * <p>The log entry will be also added to the system log. 159 */ w(String msg)160 public void w(String msg) { 161 Log.w(mTag, record(Category.WARN, msg)); 162 } 163 164 /** 165 * Log a verbose message. 166 * 167 * <p>The log entry will be also added to the system log. 168 */ v(String msg)169 public void v(String msg) { 170 Log.v(mTag, record(Category.VERBOSE, msg)); 171 } 172 173 /** 174 * Log a terrible failure message. 175 * 176 * <p>The log entry will be also added to the system log and will trigger system reporting 177 * for terrible failures. 178 */ wtf(String msg)179 public void wtf(String msg) { 180 Log.wtf(mTag, record(Category.TERRIBLE, msg)); 181 } 182 183 /** 184 * Log a terrible failure due to an exception, with the exception stacktrace if provided. 185 * 186 * <p>The error and exception message appear in the shared log, but the stacktrace is only 187 * logged in general log output (logcat). The log entry will be also added to the system log 188 * and will trigger system reporting for terrible failures. 189 */ wtf(@onNull String msg, @Nullable Throwable exception)190 public void wtf(@NonNull String msg, @Nullable Throwable exception) { 191 if (exception == null) { 192 e(msg); 193 return; 194 } 195 Log.wtf(mTag, record(Category.TERRIBLE, msg + ": " + exception.getMessage()), exception); 196 } 197 198 199 ////// 200 // Methods that only log an entry (and do NOT emit to the system log). 201 ////// 202 203 /** 204 * Log a general message to be only included in the in-memory log. 205 * 206 * <p>The log entry will *not* be added to the system log. 207 */ log(String msg)208 public void log(String msg) { 209 record(Category.NONE, msg); 210 } 211 212 /** 213 * Log a general, formatted message to be only included in the in-memory log. 214 * 215 * <p>The log entry will *not* be added to the system log. 216 * @see String#format(String, Object...) 217 */ logf(String fmt, Object... args)218 public void logf(String fmt, Object... args) { 219 log(String.format(fmt, args)); 220 } 221 222 /** 223 * Log a message with MARK level. 224 * 225 * <p>The log entry will *not* be added to the system log. 226 */ mark(String msg)227 public void mark(String msg) { 228 record(Category.MARK, msg); 229 } 230 record(Category category, String msg)231 private String record(Category category, String msg) { 232 final String entry = logLine(category, msg); 233 mLocalLog.append(entry); 234 return entry; 235 } 236 logLine(Category category, String msg)237 private String logLine(Category category, String msg) { 238 final StringJoiner sj = new StringJoiner(" "); 239 if (!isRootLogInstance()) sj.add("[" + mComponent + "]"); 240 if (category != Category.NONE) sj.add(category.toString()); 241 return sj.add(msg).toString(); 242 } 243 244 // Check whether this SharedLog instance is nominally the top level in 245 // a potential hierarchy of shared logs (the root of a tree), 246 // or is a subcomponent within the hierarchy. isRootLogInstance()247 private boolean isRootLogInstance() { 248 return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag); 249 } 250 251 private static final class LocalLog { 252 private final Deque<String> mLog; 253 private final int mMaxLines; 254 LocalLog(int maxLines)255 LocalLog(int maxLines) { 256 mMaxLines = Math.max(0, maxLines); 257 mLog = new ArrayDeque<>(mMaxLines); 258 } 259 append(String logLine)260 synchronized void append(String logLine) { 261 if (mMaxLines <= 0) return; 262 while (mLog.size() >= mMaxLines) { 263 mLog.remove(); 264 } 265 mLog.add(LocalDateTime.now() + " - " + logLine); 266 } 267 268 /** 269 * Dumps the content of local log to print writer with each log entry 270 * 271 * @param pw printer writer to write into 272 */ dump(PrintWriter pw)273 synchronized void dump(PrintWriter pw) { 274 for (final String s : mLog) { 275 pw.println(s); 276 } 277 } 278 reverseDump(PrintWriter pw)279 synchronized void reverseDump(PrintWriter pw) { 280 final Iterator<String> itr = mLog.descendingIterator(); 281 while (itr.hasNext()) { 282 pw.println(itr.next()); 283 } 284 } 285 } 286 } 287