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