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 android.net.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 } 50 51 private final LocalLog mLocalLog; 52 // The tag to use for output to the system log. This is not output to the 53 // LocalLog because that would be redundant. 54 private final String mTag; 55 // The component (or subcomponent) of a system that is sharing this log. 56 // This can grow in depth if components call forSubComponent() to obtain 57 // their SharedLog instance. The tag is not included in the component for 58 // brevity. 59 private final String mComponent; 60 SharedLog(String tag)61 public SharedLog(String tag) { 62 this(DEFAULT_MAX_RECORDS, tag); 63 } 64 SharedLog(int maxRecords, String tag)65 public SharedLog(int maxRecords, String tag) { 66 this(new LocalLog(maxRecords), tag, tag); 67 } 68 SharedLog(LocalLog localLog, String tag, String component)69 private SharedLog(LocalLog localLog, String tag, String component) { 70 mLocalLog = localLog; 71 mTag = tag; 72 mComponent = component; 73 } 74 getTag()75 public String getTag() { 76 return mTag; 77 } 78 79 /** 80 * Create a SharedLog based on this log with an additional component prefix on each logged line. 81 */ forSubComponent(String component)82 public SharedLog forSubComponent(String component) { 83 if (!isRootLogInstance()) { 84 component = mComponent + COMPONENT_DELIMITER + component; 85 } 86 return new SharedLog(mLocalLog, mTag, component); 87 } 88 89 /** 90 * Dump the contents of this log. 91 * 92 * <p>This method may be called on any thread. 93 */ dump(FileDescriptor fd, PrintWriter writer, String[] args)94 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 95 mLocalLog.dump(writer); 96 } 97 98 /** 99 * Reverse dump the contents of this log. 100 * 101 * <p>This method may be called on any thread. 102 */ reverseDump(PrintWriter writer)103 public void reverseDump(PrintWriter writer) { 104 mLocalLog.reverseDump(writer); 105 } 106 107 ////// 108 // Methods that both log an entry and emit it to the system log. 109 ////// 110 111 /** 112 * Log an error due to an exception. This does not include the exception stacktrace. 113 * 114 * <p>The log entry will be also added to the system log. 115 * @see #e(String, Throwable) 116 */ e(Exception e)117 public void e(Exception e) { 118 Log.e(mTag, record(Category.ERROR, e.toString())); 119 } 120 121 /** 122 * Log an error message. 123 * 124 * <p>The log entry will be also added to the system log. 125 */ e(String msg)126 public void e(String msg) { 127 Log.e(mTag, record(Category.ERROR, msg)); 128 } 129 130 /** 131 * Log an error due to an exception, with the exception stacktrace if provided. 132 * 133 * <p>The error and exception message appear in the shared log, but the stacktrace is only 134 * logged in general log output (logcat). The log entry will be also added to the system log. 135 */ e(@onNull String msg, @Nullable Throwable exception)136 public void e(@NonNull String msg, @Nullable Throwable exception) { 137 if (exception == null) { 138 e(msg); 139 return; 140 } 141 Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception); 142 } 143 144 /** 145 * Log an informational message. 146 * 147 * <p>The log entry will be also added to the system log. 148 */ i(String msg)149 public void i(String msg) { 150 Log.i(mTag, record(Category.NONE, msg)); 151 } 152 153 /** 154 * Log a warning message. 155 * 156 * <p>The log entry will be also added to the system log. 157 */ w(String msg)158 public void w(String msg) { 159 Log.w(mTag, record(Category.WARN, msg)); 160 } 161 162 ////// 163 // Methods that only log an entry (and do NOT emit to the system log). 164 ////// 165 166 /** 167 * Log a general message to be only included in the in-memory log. 168 * 169 * <p>The log entry will *not* be added to the system log. 170 */ log(String msg)171 public void log(String msg) { 172 record(Category.NONE, msg); 173 } 174 175 /** 176 * Log a general, formatted message to be only included in the in-memory log. 177 * 178 * <p>The log entry will *not* be added to the system log. 179 * @see String#format(String, Object...) 180 */ logf(String fmt, Object... args)181 public void logf(String fmt, Object... args) { 182 log(String.format(fmt, args)); 183 } 184 185 /** 186 * Log a message with MARK level. 187 * 188 * <p>The log entry will *not* be added to the system log. 189 */ mark(String msg)190 public void mark(String msg) { 191 record(Category.MARK, msg); 192 } 193 record(Category category, String msg)194 private String record(Category category, String msg) { 195 final String entry = logLine(category, msg); 196 mLocalLog.append(entry); 197 return entry; 198 } 199 logLine(Category category, String msg)200 private String logLine(Category category, String msg) { 201 final StringJoiner sj = new StringJoiner(" "); 202 if (!isRootLogInstance()) sj.add("[" + mComponent + "]"); 203 if (category != Category.NONE) sj.add(category.toString()); 204 return sj.add(msg).toString(); 205 } 206 207 // Check whether this SharedLog instance is nominally the top level in 208 // a potential hierarchy of shared logs (the root of a tree), 209 // or is a subcomponent within the hierarchy. isRootLogInstance()210 private boolean isRootLogInstance() { 211 return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag); 212 } 213 214 private static final class LocalLog { 215 private final Deque<String> mLog; 216 private final int mMaxLines; 217 LocalLog(int maxLines)218 LocalLog(int maxLines) { 219 mMaxLines = Math.max(0, maxLines); 220 mLog = new ArrayDeque<>(mMaxLines); 221 } 222 append(String logLine)223 synchronized void append(String logLine) { 224 if (mMaxLines <= 0) return; 225 while (mLog.size() >= mMaxLines) { 226 mLog.remove(); 227 } 228 mLog.add(LocalDateTime.now() + " - " + logLine); 229 } 230 231 /** 232 * Dumps the content of local log to print writer with each log entry 233 * 234 * @param pw printer writer to write into 235 */ dump(PrintWriter pw)236 synchronized void dump(PrintWriter pw) { 237 for (final String s : mLog) { 238 pw.println(s); 239 } 240 } 241 reverseDump(PrintWriter pw)242 synchronized void reverseDump(PrintWriter pw) { 243 final Iterator<String> itr = mLog.descendingIterator(); 244 while (itr.hasNext()) { 245 pw.println(itr.next()); 246 } 247 } 248 } 249 } 250