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