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 */ 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 */ 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 */ 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 183 public String getName() { 184 return mName; 185 } 186 187 public String getTag() { 188 return mTag; 189 } 190 191 public String getText() { 192 return mText; 193 } 194 195 public String getPid() { 196 return mPid; 197 } 198 199 public String getAppName() { 200 return mAppName; 201 } 202 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 */ 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 */ 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 */ 267 public void resetUnreadCount() { 268 mUnreadCount = 0; 269 } 270 271 /** 272 * Get current value for the unread message counter. 273 */ 274 public int getUnreadCount() { 275 return mUnreadCount; 276 } 277 278 /** Make this filter transient: It will not be persisted across sessions. */ 279 public void setTransient() { 280 mTransient = true; 281 } 282 283 public boolean isTransient() { 284 return mTransient; 285 } 286 } 287