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