1 /*
2  * Copyright (C) 2016 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.server.wifi;
18 
19 import android.util.Log;
20 
21 import com.android.internal.annotations.Immutable;
22 import com.android.internal.annotations.VisibleForTesting;
23 
24 import javax.annotation.concurrent.ThreadSafe;
25 
26 /**
27  * Provides a WifiLog implementation which uses logd as the
28  * logging backend.
29  *
30  * This class is trivially thread-safe, as instances are immutable.
31  * Note, however, that LogMessage instances are _not_ thread-safe.
32  */
33 @ThreadSafe
34 @Immutable
35 class LogcatLog implements WifiLog {
36     private final String mTag;
37     private static volatile boolean sVerboseLogging = false;
38     private static final NoLogMessage NO_LOG_MESSAGE = new NoLogMessage();
39 
LogcatLog(String tag)40     LogcatLog(String tag) {
41         mTag = tag;
42     }
43 
enableVerboseLogging(boolean verboseEnabled)44     public static void enableVerboseLogging(boolean verboseEnabled) {
45         sVerboseLogging = verboseEnabled;
46     }
47 
48     /* New-style methods */
49     @Override
err(String format)50     public LogMessage err(String format) {
51         return new RealLogMessage(Log.ERROR, mTag, format);
52     }
53 
54     @Override
warn(String format)55     public LogMessage warn(String format) {
56         return new RealLogMessage(Log.WARN, mTag, format);
57     }
58 
59     @Override
info(String format)60     public LogMessage info(String format) {
61         return new RealLogMessage(Log.INFO, mTag, format);
62     }
63 
64     @Override
trace(String format)65     public LogMessage trace(String format) {
66         if (sVerboseLogging) {
67             return new RealLogMessage(Log.DEBUG, mTag, format,
68                     getNameOfCallingMethod(0));
69         } else {
70             return NO_LOG_MESSAGE;
71         }
72     }
73 
74     @Override
trace(String format, int numFramesToIgnore)75     public LogMessage trace(String format, int numFramesToIgnore) {
76         if (sVerboseLogging) {
77             return new RealLogMessage(Log.DEBUG, mTag, format,
78                     getNameOfCallingMethod(numFramesToIgnore));
79         } else {
80             return NO_LOG_MESSAGE;
81         }
82     }
83 
84     @Override
dump(String format)85     public LogMessage dump(String format) {
86         if (sVerboseLogging) {
87             return new RealLogMessage(Log.VERBOSE, mTag, format);
88         } else {
89             return NO_LOG_MESSAGE;
90         }
91     }
92 
93     @Override
eC(String msg)94     public void eC(String msg) {
95         Log.e(mTag, msg, null);
96     }
97 
98     @Override
wC(String msg)99     public void wC(String msg) {
100         Log.w(mTag, msg, null);
101     }
102 
103     @Override
iC(String msg)104     public void iC(String msg) {
105         Log.i(mTag, msg, null);
106     }
107 
108     @Override
tC(String msg)109     public void tC(String msg) {
110         Log.d(mTag, msg, null);
111     }
112 
113     /* Legacy methods */
114     @Override
e(String msg)115     public void e(String msg) {
116         Log.e(mTag, msg, null);
117     }
118 
119     @Override
w(String msg)120     public void w(String msg) {
121         Log.w(mTag, msg, null);
122     }
123 
124     @Override
i(String msg)125     public void i(String msg) {
126         Log.i(mTag, msg, null);
127     }
128 
129     @Override
d(String msg)130     public void d(String msg) {
131         Log.d(mTag, msg, null);
132     }
133 
134     @Override
v(String msg)135     public void v(String msg) {
136         Log.v(mTag, msg, null);
137     }
138 
139     /* Internal details */
140     private static class RealLogMessage implements WifiLog.LogMessage {
141         private final int mLogLevel;
142         private final String mTag;
143         private final String mFormat;
144         private final StringBuilder mStringBuilder;
145         private int mNextFormatCharPos;
146 
RealLogMessage(int logLevel, String tag, String format)147         RealLogMessage(int logLevel, String tag, String format) {
148             this(logLevel, tag, format, null);
149         }
150 
RealLogMessage(int logLevel, String tag, String format, String prefix)151         RealLogMessage(int logLevel, String tag, String format, String prefix) {
152             mLogLevel = logLevel;
153             mTag = tag;
154             mFormat = format;
155             mStringBuilder = new StringBuilder();
156             mNextFormatCharPos = 0;
157             if (prefix != null) {
158                 mStringBuilder.append(prefix).append(" ");
159             }
160         }
161 
162         @Override
r(String value)163         public WifiLog.LogMessage r(String value) {
164             // Since the logcat back-end is just transitional, we don't attempt to tag sensitive
165             // information in it.
166             return c(value);
167         }
168 
169         @Override
c(String value)170         public WifiLog.LogMessage c(String value) {
171             copyUntilPlaceholder();
172             if (mNextFormatCharPos < mFormat.length()) {
173                 mStringBuilder.append(value);
174                 ++mNextFormatCharPos;
175             }
176             return this;
177         }
178 
179         @Override
c(long value)180         public WifiLog.LogMessage c(long value) {
181             copyUntilPlaceholder();
182             if (mNextFormatCharPos < mFormat.length()) {
183                 mStringBuilder.append(value);
184                 ++mNextFormatCharPos;
185             }
186             return this;
187         }
188 
189         @Override
c(char value)190         public WifiLog.LogMessage c(char value) {
191             copyUntilPlaceholder();
192             if (mNextFormatCharPos < mFormat.length()) {
193                 mStringBuilder.append(value);
194                 ++mNextFormatCharPos;
195             }
196             return this;
197         }
198 
199         @Override
c(boolean value)200         public WifiLog.LogMessage c(boolean value) {
201             copyUntilPlaceholder();
202             if (mNextFormatCharPos < mFormat.length()) {
203                 mStringBuilder.append(value);
204                 ++mNextFormatCharPos;
205             }
206             return this;
207         }
208 
209         @Override
flush()210         public void flush() {
211             if (mNextFormatCharPos < mFormat.length()) {
212                 mStringBuilder.append(mFormat, mNextFormatCharPos, mFormat.length());
213             }
214             Log.println(mLogLevel, mTag, mStringBuilder.toString());
215         }
216 
217         @VisibleForTesting
toString()218         public String toString() {
219             return mStringBuilder.toString();
220         }
221 
copyUntilPlaceholder()222         private void copyUntilPlaceholder() {
223             if (mNextFormatCharPos >= mFormat.length()) {
224                 return;
225             }
226 
227             int placeholderPos = mFormat.indexOf(WifiLog.PLACEHOLDER, mNextFormatCharPos);
228             if (placeholderPos == -1) {
229                 placeholderPos = mFormat.length();
230             }
231 
232             mStringBuilder.append(mFormat, mNextFormatCharPos, placeholderPos);
233             mNextFormatCharPos = placeholderPos;
234         }
235     }
236 
237     private static final String[] TRACE_FRAMES_TO_IGNORE = {
238             "getNameOfCallingMethod()", "trace()"
239     };
getNameOfCallingMethod(int callerFramesToIgnore)240     private String getNameOfCallingMethod(int callerFramesToIgnore) {
241         final int frameNumOfInterest = callerFramesToIgnore + TRACE_FRAMES_TO_IGNORE.length;
242         // In some environments, it's much faster to get a stack trace from a Throwable
243         // https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6375302.
244         //
245         // While Dalvik optimizes the same-thread-stack-trace case,
246         // Throwable_nativeGetStackTrace() is still simpler than
247         // VMStack_getThreadStackTrace().
248         //
249         // Some crude benchmarking suggests that the cost of this approach is about
250         // 50 usec. go/logcatlog-trace-benchmark
251         StackTraceElement[] stackTrace = (new Throwable()).getStackTrace();
252         try {
253             return stackTrace[frameNumOfInterest].getMethodName();
254         } catch (ArrayIndexOutOfBoundsException e) {
255             return ("<unknown>");
256         }
257     }
258 }
259