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.LocalLog;
23 import android.util.Log;
24 
25 import java.io.FileDescriptor;
26 import java.io.PrintWriter;
27 import java.util.StringJoiner;
28 
29 
30 /**
31  * Class to centralize logging functionality for tethering.
32  *
33  * All access to class methods other than dump() must be on the same thread.
34  *
35  * @hide
36  */
37 public class SharedLog {
38     private static final int DEFAULT_MAX_RECORDS = 500;
39     private static final String COMPONENT_DELIMITER = ".";
40 
41     private enum Category {
42         NONE,
43         ERROR,
44         MARK,
45         WARN,
46     };
47 
48     private final LocalLog mLocalLog;
49     // The tag to use for output to the system log. This is not output to the
50     // LocalLog because that would be redundant.
51     private final String mTag;
52     // The component (or subcomponent) of a system that is sharing this log.
53     // This can grow in depth if components call forSubComponent() to obtain
54     // their SharedLog instance. The tag is not included in the component for
55     // brevity.
56     private final String mComponent;
57 
SharedLog(String tag)58     public SharedLog(String tag) {
59         this(DEFAULT_MAX_RECORDS, tag);
60     }
61 
SharedLog(int maxRecords, String tag)62     public SharedLog(int maxRecords, String tag) {
63         this(new LocalLog(maxRecords), tag, tag);
64     }
65 
SharedLog(LocalLog localLog, String tag, String component)66     private SharedLog(LocalLog localLog, String tag, String component) {
67         mLocalLog = localLog;
68         mTag = tag;
69         mComponent = component;
70     }
71 
getTag()72     public String getTag() {
73         return mTag;
74     }
75 
76     /**
77      * Create a SharedLog based on this log with an additional component prefix on each logged line.
78      */
forSubComponent(String component)79     public SharedLog forSubComponent(String component) {
80         if (!isRootLogInstance()) {
81             component = mComponent + COMPONENT_DELIMITER + component;
82         }
83         return new SharedLog(mLocalLog, mTag, component);
84     }
85 
86     /**
87      * Dump the contents of this log.
88      *
89      * <p>This method may be called on any thread.
90      */
dump(FileDescriptor fd, PrintWriter writer, String[] args)91     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
92         mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
93     }
94 
95     //////
96     // Methods that both log an entry and emit it to the system log.
97     //////
98 
99     /**
100      * Log an error due to an exception. This does not include the exception stacktrace.
101      *
102      * <p>The log entry will be also added to the system log.
103      * @see #e(String, Throwable)
104      */
e(Exception e)105     public void e(Exception e) {
106         Log.e(mTag, record(Category.ERROR, e.toString()));
107     }
108 
109     /**
110      * Log an error message.
111      *
112      * <p>The log entry will be also added to the system log.
113      */
e(String msg)114     public void e(String msg) {
115         Log.e(mTag, record(Category.ERROR, msg));
116     }
117 
118     /**
119      * Log an error due to an exception, with the exception stacktrace if provided.
120      *
121      * <p>The error and exception message appear in the shared log, but the stacktrace is only
122      * logged in general log output (logcat). The log entry will be also added to the system log.
123      */
e(@onNull String msg, @Nullable Throwable exception)124     public void e(@NonNull String msg, @Nullable Throwable exception) {
125         if (exception == null) {
126             e(msg);
127             return;
128         }
129         Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
130     }
131 
132     /**
133      * Log an informational message.
134      *
135      * <p>The log entry will be also added to the system log.
136      */
i(String msg)137     public void i(String msg) {
138         Log.i(mTag, record(Category.NONE, msg));
139     }
140 
141     /**
142      * Log a warning message.
143      *
144      * <p>The log entry will be also added to the system log.
145      */
w(String msg)146     public void w(String msg) {
147         Log.w(mTag, record(Category.WARN, msg));
148     }
149 
150     //////
151     // Methods that only log an entry (and do NOT emit to the system log).
152     //////
153 
154     /**
155      * Log a general message to be only included in the in-memory log.
156      *
157      * <p>The log entry will *not* be added to the system log.
158      */
log(String msg)159     public void log(String msg) {
160         record(Category.NONE, msg);
161     }
162 
163     /**
164      * Log a general, formatted message to be only included in the in-memory log.
165      *
166      * <p>The log entry will *not* be added to the system log.
167      * @see String#format(String, Object...)
168      */
logf(String fmt, Object... args)169     public void logf(String fmt, Object... args) {
170         log(String.format(fmt, args));
171     }
172 
173     /**
174      * Log a message with MARK level.
175      *
176      * <p>The log entry will *not* be added to the system log.
177      */
mark(String msg)178     public void mark(String msg) {
179         record(Category.MARK, msg);
180     }
181 
record(Category category, String msg)182     private String record(Category category, String msg) {
183         final String entry = logLine(category, msg);
184         mLocalLog.log(entry);
185         return entry;
186     }
187 
logLine(Category category, String msg)188     private String logLine(Category category, String msg) {
189         final StringJoiner sj = new StringJoiner(" ");
190         if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
191         if (category != Category.NONE) sj.add(category.toString());
192         return sj.add(msg).toString();
193     }
194 
195     // Check whether this SharedLog instance is nominally the top level in
196     // a potential hierarchy of shared logs (the root of a tree),
197     // or is a subcomponent within the hierarchy.
isRootLogInstance()198     private boolean isRootLogInstance() {
199         return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
200     }
201 }
202