• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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 package com.android.ddmuilib.logcat;
17 
18 import com.android.ddmlib.Log;
19 import com.android.ddmlib.Log.LogLevel;
20 
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25 import java.util.regex.PatternSyntaxException;
26 
27 /**
28  * A Filter for logcat messages. A filter can be constructed to match
29  * different fields of a logcat message. It can then be queried to see if
30  * a message matches the filter's settings.
31  */
32 public final class LogCatFilter {
33     private static final String PID_KEYWORD = "pid:";   //$NON-NLS-1$
34     private static final String APP_KEYWORD = "app:";   //$NON-NLS-1$
35     private static final String TAG_KEYWORD = "tag:";   //$NON-NLS-1$
36     private static final String TEXT_KEYWORD = "text:"; //$NON-NLS-1$
37 
38     private final String mName;
39     private final String mTag;
40     private final String mText;
41     private final String mPid;
42     private final String mAppName;
43     private final LogLevel mLogLevel;
44 
45     /** Indicates the number of messages that match this filter, but have not
46      * yet been read by the user. This is really metadata about this filter
47      * necessary for the UI. If we ever end up needing to store more metadata,
48      * then it is probably better to move it out into a separate class. */
49     private int mUnreadCount;
50 
51     /** Indicates that this filter is transient, and should not be persisted
52      * across Eclipse sessions. */
53     private boolean mTransient;
54 
55     private boolean mCheckPid;
56     private boolean mCheckAppName;
57     private boolean mCheckTag;
58     private boolean mCheckText;
59 
60     private Pattern mAppNamePattern;
61     private Pattern mTagPattern;
62     private Pattern mTextPattern;
63 
64     /**
65      * Construct a filter with the provided restrictions for the logcat message. All the text
66      * fields accept Java regexes as input, but ignore invalid regexes. Filters are saved and
67      * restored across Eclipse sessions unless explicitly marked transient using
68      * {@link LogCatFilter#setTransient}.
69      * @param name name for the filter
70      * @param tag value for the logcat message's tag field.
71      * @param text value for the logcat message's text field.
72      * @param pid value for the logcat message's pid field.
73      * @param appName value for the logcat message's app name field.
74      * @param logLevel value for the logcat message's log level. Only messages of
75      * higher priority will be accepted by the filter.
76      */
LogCatFilter(String name, String tag, String text, String pid, String appName, LogLevel logLevel)77     public LogCatFilter(String name, String tag, String text, String pid, String appName,
78             LogLevel logLevel) {
79         mName = name.trim();
80         mTag = tag.trim();
81         mText = text.trim();
82         mPid = pid.trim();
83         mAppName = appName.trim();
84         mLogLevel = logLevel;
85 
86         mUnreadCount = 0;
87 
88         // By default, all filters are persistent. Transient filters should explicitly
89         // mark it so by calling setTransient.
90         mTransient = false;
91 
92         mCheckPid = mPid.length() != 0;
93 
94         if (mAppName.length() != 0) {
95             try {
96                 mAppNamePattern = Pattern.compile(mAppName, getPatternCompileFlags(mAppName));
97                 mCheckAppName = true;
98             } catch (PatternSyntaxException e) {
99                 Log.e("LogCatFilter", "Ignoring invalid app name regex.");
100                 Log.e("LogCatFilter", e.getMessage());
101                 mCheckAppName = false;
102             }
103         }
104 
105         if (mTag.length() != 0) {
106             try {
107                 mTagPattern = Pattern.compile(mTag, getPatternCompileFlags(mTag));
108                 mCheckTag = true;
109             } catch (PatternSyntaxException e) {
110                 Log.e("LogCatFilter", "Ignoring invalid tag regex.");
111                 Log.e("LogCatFilter", e.getMessage());
112                 mCheckTag = false;
113             }
114         }
115 
116         if (mText.length() != 0) {
117             try {
118                 mTextPattern = Pattern.compile(mText, getPatternCompileFlags(mText));
119                 mCheckText = true;
120             } catch (PatternSyntaxException e) {
121                 Log.e("LogCatFilter", "Ignoring invalid text regex.");
122                 Log.e("LogCatFilter", e.getMessage());
123                 mCheckText = false;
124             }
125         }
126     }
127 
128     /**
129      * Obtain the flags to pass to {@link Pattern#compile(String, int)}. This method
130      * tries to figure out whether case sensitive matching should be used. It is based on
131      * the following heuristic: if the regex has an upper case character, then the match
132      * will be case sensitive. Otherwise it will be case insensitive.
133      */
getPatternCompileFlags(String regex)134     private int getPatternCompileFlags(String regex) {
135         for (char c : regex.toCharArray()) {
136             if (Character.isUpperCase(c)) {
137                 return 0;
138             }
139         }
140 
141         return Pattern.CASE_INSENSITIVE;
142     }
143 
144     /**
145      * Construct a list of {@link LogCatFilter} objects by decoding the query.
146      * @param query encoded search string. The query is simply a list of words (can be regexes)
147      * a user would type in a search bar. These words are searched for in the text field of
148      * each collected logcat message. To search in a different field, the word could be prefixed
149      * with a keyword corresponding to the field name. Currently, the following keywords are
150      * supported: "pid:", "tag:" and "text:". Invalid regexes are ignored.
151      * @param minLevel minimum log level to match
152      * @return list of filter settings that fully match the given query
153      */
fromString(String query, LogLevel minLevel)154     public static List<LogCatFilter> fromString(String query, LogLevel minLevel) {
155         List<LogCatFilter> filterSettings = new ArrayList<LogCatFilter>();
156 
157         for (String s : query.trim().split(" ")) {
158             String tag = "";
159             String text = "";
160             String pid = "";
161             String app = "";
162 
163             if (s.startsWith(PID_KEYWORD)) {
164                 pid = s.substring(PID_KEYWORD.length());
165             } else if (s.startsWith(APP_KEYWORD)) {
166                 app = s.substring(APP_KEYWORD.length());
167             } else if (s.startsWith(TAG_KEYWORD)) {
168                 tag = s.substring(TAG_KEYWORD.length());
169             } else {
170                 if (s.startsWith(TEXT_KEYWORD)) {
171                     text = s.substring(TEXT_KEYWORD.length());
172                 } else {
173                     text = s;
174                 }
175             }
176             filterSettings.add(new LogCatFilter("livefilter-" + s,
177                     tag, text, pid, app, minLevel));
178         }
179 
180         return filterSettings;
181     }
182 
getName()183     public String getName() {
184         return mName;
185     }
186 
getTag()187     public String getTag() {
188         return mTag;
189     }
190 
getText()191     public String getText() {
192         return mText;
193     }
194 
getPid()195     public String getPid() {
196         return mPid;
197     }
198 
getAppName()199     public String getAppName() {
200         return mAppName;
201     }
202 
getLogLevel()203     public LogLevel getLogLevel() {
204         return mLogLevel;
205     }
206 
207     /**
208      * Check whether a given message will make it through this filter.
209      * @param m message to check
210      * @return true if the message matches the filter's conditions.
211      */
matches(LogCatMessage m)212     public boolean matches(LogCatMessage m) {
213         /* filter out messages of a lower priority */
214         if (m.getLogLevel().getPriority() < mLogLevel.getPriority()) {
215             return false;
216         }
217 
218         /* if pid filter is enabled, filter out messages whose pid does not match
219          * the filter's pid */
220         if (mCheckPid && !m.getPid().equals(mPid)) {
221             return false;
222         }
223 
224         /* if app name filter is enabled, filter out messages not matching the app name */
225         if (mCheckAppName) {
226             Matcher matcher = mAppNamePattern.matcher(m.getAppName());
227             if (!matcher.find()) {
228                 return false;
229             }
230         }
231 
232         /* if tag filter is enabled, filter out messages not matching the tag */
233         if (mCheckTag) {
234             Matcher matcher = mTagPattern.matcher(m.getTag());
235             if (!matcher.find()) {
236                 return false;
237             }
238         }
239 
240         if (mCheckText) {
241             Matcher matcher = mTextPattern.matcher(m.getMessage());
242             if (!matcher.find()) {
243                 return false;
244             }
245         }
246 
247         return true;
248     }
249 
250     /**
251      * Update the unread count based on new messages received. The unread count
252      * is incremented by the count of messages in the received list that will be
253      * accepted by this filter.
254      * @param newMessages list of new messages.
255      */
updateUnreadCount(List<LogCatMessage> newMessages)256     public void updateUnreadCount(List<LogCatMessage> newMessages) {
257         for (LogCatMessage m : newMessages) {
258             if (matches(m)) {
259                 mUnreadCount++;
260             }
261         }
262     }
263 
264     /**
265      * Reset count of unread messages.
266      */
resetUnreadCount()267     public void resetUnreadCount() {
268         mUnreadCount = 0;
269     }
270 
271     /**
272      * Get current value for the unread message counter.
273      */
getUnreadCount()274     public int getUnreadCount() {
275         return mUnreadCount;
276     }
277 
278     /** Make this filter transient: It will not be persisted across sessions. */
setTransient()279     public void setTransient() {
280         mTransient = true;
281     }
282 
isTransient()283     public boolean isTransient() {
284         return mTransient;
285     }
286 }
287