• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
LogFilter(String name)94     public LogFilter(String name) {
95         mName = name;
96     }
97 
LogFilter()98     public LogFilter() {
99 
100     }
101 
102     @Override
toString()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 
loadFromString(String string)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. */
setName(String name)153     void setName(String name) {
154         mName = name;
155     }
156 
157     /**
158      * Returns the UI display name.
159      */
getName()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      */
setWidgets(TabItem tabItem, Table table)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      */
uiReady()177     public boolean uiReady() {
178         return (mTable != null && mTabItem != null);
179     }
180 
181     /**
182      * Returns the UI table object.
183      * @return
184      */
getTable()185     public Table getTable() {
186         return mTable;
187     }
188 
dispose()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      */
resetFilteringMode()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      */
getFilteringMode()207     public int getFilteringMode() {
208         return mMode;
209     }
210 
211     /**
212      * Adds PID to the current filtering mode.
213      * @param pid
214      */
setPidMode(int pid)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 */
getPidFilter()225     public int getPidFilter() {
226         if ((mMode & MODE_PID) == MODE_PID)
227             return mPid;
228         return -1;
229     }
230 
setTagMode(String tag)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 
getTagFilter()240     public String getTagFilter() {
241         if ((mMode & MODE_TAG) == MODE_TAG)
242             return mTag;
243         return null;
244     }
245 
setLogLevel(int level)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 
getLogLevel()256     public int getLogLevel() {
257         if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
258             return mLogLevel;
259         }
260 
261         return -1;
262     }
263 
264 
supportsDelete()265     public boolean supportsDelete() {
266         return mSupportsDelete ;
267     }
268 
supportsEdit()269     public boolean supportsEdit() {
270         return mSupportsEdit;
271     }
272 
273     /**
274      * Sets the selected state of the filter.
275      * @param selected selection state.
276      */
setSelectedState(boolean selected)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      */
addMessage(LogMessage newMessage, LogMessage oldMessage)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      */
clear()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      */
accept(LogMessage logMessage)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
flush()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 
setColors(LogColors colors)474     void setColors(LogColors colors) {
475         mColors = colors;
476     }
477 
getUnreadCount()478     int getUnreadCount() {
479         return mUnreadCount;
480     }
481 
setUnreadCount(int unreadCount)482     void setUnreadCount(int unreadCount) {
483         mUnreadCount = unreadCount;
484     }
485 
setSupportsDelete(boolean support)486     void setSupportsDelete(boolean support) {
487         mSupportsDelete = support;
488     }
489 
setSupportsEdit(boolean support)490     void setSupportsEdit(boolean support) {
491         mSupportsEdit = support;
492     }
493 
setTempKeywordFiltering(String[] segments)494     void setTempKeywordFiltering(String[] segments) {
495         mTempKeywordFilters = segments;
496         mTempFilteringStatus = true;
497     }
498 
setTempPidFiltering(int pid)499     void setTempPidFiltering(int pid) {
500         mTempPid = pid;
501         mTempFilteringStatus = true;
502     }
503 
setTempTagFiltering(String tag)504     void setTempTagFiltering(String tag) {
505         mTempTag = tag;
506         mTempFilteringStatus = true;
507     }
508 
resetTempFiltering()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 
resetTempFilteringStatus()519     void resetTempFilteringStatus() {
520         mTempFilteringStatus = false;
521     }
522 
getTempFilterStatus()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      */
addTableItem(LogMessage msg)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