Home | History | Annotate | Download | only in logcat
      1 /*
      2  * Copyright (C) 2007 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.ddmuilib.logcat;
     18 
     19 import com.android.ddmlib.Log;
     20 import com.android.ddmlib.Log.LogLevel;
     21 import com.android.ddmuilib.annotation.UiThread;
     22 import com.android.ddmuilib.logcat.LogPanel.LogMessage;
     23 
     24 import org.eclipse.swt.SWT;
     25 import org.eclipse.swt.SWTException;
     26 import org.eclipse.swt.widgets.ScrollBar;
     27 import org.eclipse.swt.widgets.TabItem;
     28 import org.eclipse.swt.widgets.Table;
     29 import org.eclipse.swt.widgets.TableItem;
     30 
     31 import java.util.ArrayList;
     32 import java.util.regex.PatternSyntaxException;
     33 
     34 /** logcat output filter class */
     35 public class LogFilter {
     36 
     37     public final static int MODE_PID = 0x01;
     38     public final static int MODE_TAG = 0x02;
     39     public final static int MODE_LEVEL = 0x04;
     40 
     41     private String mName;
     42 
     43     /**
     44      * Filtering mode. Value can be a mix of MODE_PID, MODE_TAG, MODE_LEVEL
     45      */
     46     private int mMode = 0;
     47 
     48     /**
     49      * pid used for filtering. Only valid if mMode is MODE_PID.
     50      */
     51     private int mPid;
     52 
     53     /** Single level log level as defined in Log.mLevelChar. Only valid
     54      * if mMode is MODE_LEVEL */
     55     private int mLogLevel;
     56 
     57     /**
     58      * log tag filtering. Only valid if mMode is MODE_TAG
     59      */
     60     private String mTag;
     61 
     62     private Table mTable;
     63     private TabItem mTabItem;
     64     private boolean mIsCurrentTabItem = false;
     65     private int mUnreadCount = 0;
     66 
     67     /** Temp keyword filtering */
     68     private String[] mTempKeywordFilters;
     69 
     70     /** temp pid filtering */
     71     private int mTempPid = -1;
     72 
     73     /** temp tag filtering */
     74     private String mTempTag;
     75 
     76     /** temp log level filtering */
     77     private int mTempLogLevel = -1;
     78 
     79     private LogColors mColors;
     80 
     81     private boolean mTempFilteringStatus = false;
     82 
     83     private final ArrayList<LogMessage> mMessages = new ArrayList<LogMessage>();
     84     private final ArrayList<LogMessage> mNewMessages = new ArrayList<LogMessage>();
     85 
     86     private boolean mSupportsDelete = true;
     87     private boolean mSupportsEdit = true;
     88     private int mRemovedMessageCount = 0;
     89 
     90     /**
     91      * Creates a filter with a particular mode.
     92      * @param name The name to be displayed in the UI
     93      */
     94     public LogFilter(String name) {
     95         mName = name;
     96     }
     97 
     98     public LogFilter() {
     99 
    100     }
    101 
    102     @Override
    103     public String toString() {
    104         StringBuilder sb = new StringBuilder(mName);
    105 
    106         sb.append(':');
    107         sb.append(mMode);
    108         if ((mMode & MODE_PID) == MODE_PID) {
    109             sb.append(':');
    110             sb.append(mPid);
    111         }
    112 
    113         if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
    114             sb.append(':');
    115             sb.append(mLogLevel);
    116         }
    117 
    118         if ((mMode & MODE_TAG) == MODE_TAG) {
    119             sb.append(':');
    120             sb.append(mTag);
    121         }
    122 
    123         return sb.toString();
    124     }
    125 
    126     public boolean loadFromString(String string) {
    127         String[] segments = string.split(":"); // $NON-NLS-1$
    128         int index = 0;
    129 
    130         // get the name
    131         mName = segments[index++];
    132 
    133         // get the mode
    134         mMode = Integer.parseInt(segments[index++]);
    135 
    136         if ((mMode & MODE_PID) == MODE_PID) {
    137             mPid = Integer.parseInt(segments[index++]);
    138         }
    139 
    140         if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
    141             mLogLevel = Integer.parseInt(segments[index++]);
    142         }
    143 
    144         if ((mMode & MODE_TAG) == MODE_TAG) {
    145             mTag = segments[index++];
    146         }
    147 
    148         return true;
    149     }
    150 
    151 
    152     /** Sets the name of the filter. */
    153     void setName(String name) {
    154         mName = name;
    155     }
    156 
    157     /**
    158      * Returns the UI display name.
    159      */
    160     public String getName() {
    161         return mName;
    162     }
    163 
    164     /**
    165      * Set the Table ui widget associated with this filter.
    166      * @param tabItem The item in the TabFolder
    167      * @param table The Table object
    168      */
    169     public void setWidgets(TabItem tabItem, Table table) {
    170         mTable = table;
    171         mTabItem = tabItem;
    172     }
    173 
    174     /**
    175      * Returns true if the filter is ready for ui.
    176      */
    177     public boolean uiReady() {
    178         return (mTable != null && mTabItem != null);
    179     }
    180 
    181     /**
    182      * Returns the UI table object.
    183      * @return
    184      */
    185     public Table getTable() {
    186         return mTable;
    187     }
    188 
    189     public void dispose() {
    190         mTable.dispose();
    191         mTabItem.dispose();
    192         mTable = null;
    193         mTabItem = null;
    194     }
    195 
    196     /**
    197      * Resets the filtering mode to be 0 (i.e. no filter).
    198      */
    199     public void resetFilteringMode() {
    200         mMode = 0;
    201     }
    202 
    203     /**
    204      * Returns the current filtering mode.
    205      * @return A bitmask. Possible values are MODE_PID, MODE_TAG, MODE_LEVEL
    206      */
    207     public int getFilteringMode() {
    208         return mMode;
    209     }
    210 
    211     /**
    212      * Adds PID to the current filtering mode.
    213      * @param pid
    214      */
    215     public void setPidMode(int pid) {
    216         if (pid != -1) {
    217             mMode |= MODE_PID;
    218         } else {
    219             mMode &= ~MODE_PID;
    220         }
    221         mPid = pid;
    222     }
    223 
    224     /** Returns the pid filter if valid, otherwise -1 */
    225     public int getPidFilter() {
    226         if ((mMode & MODE_PID) == MODE_PID)
    227             return mPid;
    228         return -1;
    229     }
    230 
    231     public void setTagMode(String tag) {
    232         if (tag != null && tag.length() > 0) {
    233             mMode |= MODE_TAG;
    234         } else {
    235             mMode &= ~MODE_TAG;
    236         }
    237         mTag = tag;
    238     }
    239 
    240     public String getTagFilter() {
    241         if ((mMode & MODE_TAG) == MODE_TAG)
    242             return mTag;
    243         return null;
    244     }
    245 
    246     public void setLogLevel(int level) {
    247         if (level == -1) {
    248             mMode &= ~MODE_LEVEL;
    249         } else {
    250             mMode |= MODE_LEVEL;
    251             mLogLevel = level;
    252         }
    253 
    254     }
    255 
    256     public int getLogLevel() {
    257         if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
    258             return mLogLevel;
    259         }
    260 
    261         return -1;
    262     }
    263 
    264 
    265     public boolean supportsDelete() {
    266         return mSupportsDelete ;
    267     }
    268 
    269     public boolean supportsEdit() {
    270         return mSupportsEdit;
    271     }
    272 
    273     /**
    274      * Sets the selected state of the filter.
    275      * @param selected selection state.
    276      */
    277     public void setSelectedState(boolean selected) {
    278         if (selected) {
    279             if (mTabItem != null) {
    280                 mTabItem.setText(mName);
    281             }
    282             mUnreadCount = 0;
    283         }
    284         mIsCurrentTabItem = selected;
    285     }
    286 
    287     /**
    288      * Adds a new message and optionally removes an old message.
    289      * <p/>The new message is filtered through {@link #accept(LogMessage)}.
    290      * Calls to {@link #flush()} from a UI thread will display it (and other
    291      * pending messages) to the associated {@link Table}.
    292      * @param logMessage the MessageData object to filter
    293      * @return true if the message was accepted.
    294      */
    295     public boolean addMessage(LogMessage newMessage, LogMessage oldMessage) {
    296         synchronized (mMessages) {
    297             if (oldMessage != null) {
    298                 int index = mMessages.indexOf(oldMessage);
    299                 if (index != -1) {
    300                     // TODO check that index will always be -1 or 0, as only the oldest message is ever removed.
    301                     mMessages.remove(index);
    302                     mRemovedMessageCount++;
    303                 }
    304 
    305                 // now we look for it in mNewMessages. This can happen if the new message is added
    306                 // and then removed because too many messages are added between calls to #flush()
    307                 index = mNewMessages.indexOf(oldMessage);
    308                 if (index != -1) {
    309                     // TODO check that index will always be -1 or 0, as only the oldest message is ever removed.
    310                     mNewMessages.remove(index);
    311                 }
    312             }
    313 
    314             boolean filter = accept(newMessage);
    315 
    316             if (filter) {
    317                 // at this point the message is accepted, we add it to the list
    318                 mMessages.add(newMessage);
    319                 mNewMessages.add(newMessage);
    320             }
    321 
    322             return filter;
    323         }
    324     }
    325 
    326     /**
    327      * Removes all the items in the filter and its {@link Table}.
    328      */
    329     public void clear() {
    330         mRemovedMessageCount = 0;
    331         mNewMessages.clear();
    332         mMessages.clear();
    333         mTable.removeAll();
    334     }
    335 
    336     /**
    337      * Filters a message.
    338      * @param logMessage the Message
    339      * @return true if the message is accepted by the filter.
    340      */
    341     boolean accept(LogMessage logMessage) {
    342         // do the regular filtering now
    343         if ((mMode & MODE_PID) == MODE_PID && mPid != logMessage.data.pid) {
    344             return false;
    345         }
    346 
    347         if ((mMode & MODE_TAG) == MODE_TAG && (
    348                 logMessage.data.tag == null ||
    349                 logMessage.data.tag.equals(mTag) == false)) {
    350             return false;
    351         }
    352 
    353         int msgLogLevel = logMessage.data.logLevel.getPriority();
    354 
    355         // test the temp log filtering first, as it replaces the old one
    356         if (mTempLogLevel != -1) {
    357             if (mTempLogLevel > msgLogLevel) {
    358                 return false;
    359             }
    360         } else if ((mMode & MODE_LEVEL) == MODE_LEVEL &&
    361                 mLogLevel > msgLogLevel) {
    362             return false;
    363         }
    364 
    365         // do the temp filtering now.
    366         if (mTempKeywordFilters != null) {
    367             String msg = logMessage.msg;
    368 
    369             for (String kw : mTempKeywordFilters) {
    370                 try {
    371                     if (msg.contains(kw) == false && msg.matches(kw) == false) {
    372                         return false;
    373                     }
    374                 } catch (PatternSyntaxException e) {
    375                     // if the string is not a valid regular expression,
    376                     // this exception is thrown.
    377                     return false;
    378                 }
    379             }
    380         }
    381 
    382         if (mTempPid != -1 && mTempPid != logMessage.data.pid) {
    383            return false;
    384         }
    385 
    386         if (mTempTag != null && mTempTag.length() > 0) {
    387             if (mTempTag.equals(logMessage.data.tag) == false) {
    388                 return false;
    389             }
    390         }
    391 
    392         return true;
    393     }
    394 
    395     /**
    396      * Takes all the accepted messages and display them.
    397      * This must be called from a UI thread.
    398      */
    399     @UiThread
    400     public void flush() {
    401         // if scroll bar is at the bottom, we will scroll
    402         ScrollBar bar = mTable.getVerticalBar();
    403         boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();
    404 
    405         // if we are not going to scroll, get the current first item being shown.
    406         int topIndex = mTable.getTopIndex();
    407 
    408         // disable drawing
    409         mTable.setRedraw(false);
    410 
    411         int totalCount = mNewMessages.size();
    412 
    413         try {
    414             // remove the items of the old messages.
    415             for (int i = 0 ; i < mRemovedMessageCount && mTable.getItemCount() > 0 ; i++) {
    416                 mTable.remove(0);
    417             }
    418 
    419             if (mUnreadCount > mTable.getItemCount()) {
    420                 mUnreadCount = mTable.getItemCount();
    421             }
    422 
    423             // add the new items
    424             for (int i = 0  ; i < totalCount ; i++) {
    425                 LogMessage msg = mNewMessages.get(i);
    426                 addTableItem(msg);
    427             }
    428         } catch (SWTException e) {
    429             // log the error and keep going. Content of the logcat table maybe unexpected
    430             // but at least ddms won't crash.
    431             Log.e("LogFilter", e);
    432         }
    433 
    434         // redraw
    435         mTable.setRedraw(true);
    436 
    437         // scroll if needed, by showing the last item
    438         if (scroll) {
    439             totalCount = mTable.getItemCount();
    440             if (totalCount > 0) {
    441                 mTable.showItem(mTable.getItem(totalCount-1));
    442             }
    443         } else if (mRemovedMessageCount > 0) {
    444             // we need to make sure the topIndex is still visible.
    445             // Because really old items are removed from the list, this could make it disappear
    446             // if we don't change the scroll value at all.
    447 
    448             topIndex -= mRemovedMessageCount;
    449             if (topIndex < 0) {
    450                 // looks like it disappeared. Lets just show the first item
    451                 mTable.showItem(mTable.getItem(0));
    452             } else {
    453                 mTable.showItem(mTable.getItem(topIndex));
    454             }
    455         }
    456 
    457         // if this filter is not the current one, we update the tab text
    458         // with the amount of unread message
    459         if (mIsCurrentTabItem == false) {
    460             mUnreadCount += mNewMessages.size();
    461             totalCount = mTable.getItemCount();
    462             if (mUnreadCount > 0) {
    463                 mTabItem.setText(mName + " (" // $NON-NLS-1$
    464                         + (mUnreadCount > totalCount ? totalCount : mUnreadCount)
    465                         + ")");  // $NON-NLS-1$
    466             } else {
    467                 mTabItem.setText(mName);  // $NON-NLS-1$
    468             }
    469         }
    470 
    471         mNewMessages.clear();
    472     }
    473 
    474     void setColors(LogColors colors) {
    475         mColors = colors;
    476     }
    477 
    478     int getUnreadCount() {
    479         return mUnreadCount;
    480     }
    481 
    482     void setUnreadCount(int unreadCount) {
    483         mUnreadCount = unreadCount;
    484     }
    485 
    486     void setSupportsDelete(boolean support) {
    487         mSupportsDelete = support;
    488     }
    489 
    490     void setSupportsEdit(boolean support) {
    491         mSupportsEdit = support;
    492     }
    493 
    494     void setTempKeywordFiltering(String[] segments) {
    495         mTempKeywordFilters = segments;
    496         mTempFilteringStatus = true;
    497     }
    498 
    499     void setTempPidFiltering(int pid) {
    500         mTempPid = pid;
    501         mTempFilteringStatus = true;
    502     }
    503 
    504     void setTempTagFiltering(String tag) {
    505         mTempTag = tag;
    506         mTempFilteringStatus = true;
    507     }
    508 
    509     void resetTempFiltering() {
    510         if (mTempPid != -1 || mTempTag != null || mTempKeywordFilters != null) {
    511             mTempFilteringStatus = true;
    512         }
    513 
    514         mTempPid = -1;
    515         mTempTag = null;
    516         mTempKeywordFilters = null;
    517     }
    518 
    519     void resetTempFilteringStatus() {
    520         mTempFilteringStatus = false;
    521     }
    522 
    523     boolean getTempFilterStatus() {
    524         return mTempFilteringStatus;
    525     }
    526 
    527 
    528     /**
    529      * Add a TableItem for the index-th item of the buffer
    530      * @param filter The index of the table in which to insert the item.
    531      */
    532     private void addTableItem(LogMessage msg) {
    533         TableItem item = new TableItem(mTable, SWT.NONE);
    534         item.setText(0, msg.data.time);
    535         item.setText(1, new String(new char[] { msg.data.logLevel.getPriorityLetter() }));
    536         item.setText(2, msg.data.pidString);
    537         item.setText(3, msg.data.tag);
    538         item.setText(4, msg.msg);
    539 
    540         // add the buffer index as data
    541         item.setData(msg);
    542 
    543         if (msg.data.logLevel == LogLevel.INFO) {
    544             item.setForeground(mColors.infoColor);
    545         } else if (msg.data.logLevel == LogLevel.DEBUG) {
    546             item.setForeground(mColors.debugColor);
    547         } else if (msg.data.logLevel == LogLevel.ERROR) {
    548             item.setForeground(mColors.errorColor);
    549         } else if (msg.data.logLevel == LogLevel.WARN) {
    550             item.setForeground(mColors.warningColor);
    551         } else {
    552             item.setForeground(mColors.verboseColor);
    553         }
    554     }
    555 }
    556