Home | History | Annotate | Download | only in logcat
      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