1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * This view displays the log messages from various resources in an
7 * interactive log visualizer
10 * - Filter text inputs
11 * - Display the log by different sections: time|level|process|description
14 var CrosLogVisualizerView = (function() {
17 // Inherits from DivView.
18 var superClass = DivView;
20 // Special classes (defined in log_visualizer_view.css)
21 var LOG_CONTAINER_CLASSNAME = 'cros-log-visualizer-container';
22 var LOG_FILTER_PNAME_BLOCK_CLASSNAME =
23 'cros-log-visualizer-filter-pname-block';
24 var LOG_CELL_HEADER_CLASSNAME = 'cros-log-visualizer-td-head';
25 var LOG_CELL_TIME_CLASSNAME = 'cros-log-visualizer-td-time';
26 var LOG_CELL_PNAME_CLASSNAME = 'cros-log-visualizer-td-pname';
27 var LOG_CELL_PID_CLASSNAME = 'cros-log-visualizer-td-pid';
28 var LOG_CELL_DESCRIPTION_CLASSNAME = 'cros-log-visualizer-td-description';
29 var LOG_CELL_LEVEL_CLASSNAME = 'cros-log-visualizer-td-level';
30 var LOG_CELL_LEVEL_CLASSNAME_LIST = {
31 'Error': 'cros-log-visualizer-td-level-error',
32 'Warning': 'cros-log-visualizer-td-level-warning',
33 'Info': 'cros-log-visualizer-td-level-info',
34 'Unknown': 'cros-log-visualizer-td-level-unknown'
40 function CrosLogVisualizerView() {
41 assertFirstConstructorCall(CrosLogVisualizerView);
43 // Call superclass's constructor.
44 superClass.call(this, CrosLogVisualizerView.MAIN_BOX_ID);
46 // Stores log entry objects
48 // Stores current search query
49 this.currentQuery = '';
50 // Stores raw text data of log
52 // Stores all the unique process names
54 // References to special HTML elements in log_visualizer_view.html
55 this.pNameCheckboxes = {};
56 this.levelCheckboxes = {};
57 this.tableEntries = [];
62 CrosLogVisualizerView.TAB_ID = 'tab-handle-cros-log-visualizer';
63 CrosLogVisualizerView.TAB_NAME = 'Log Visualizer';
64 CrosLogVisualizerView.TAB_HASH = '#visualizer';
66 // IDs for special HTML elements in log_visualizer_view.html
67 CrosLogVisualizerView.MAIN_BOX_ID = 'cros-log-visualizer-tab-content';
68 CrosLogVisualizerView.LOG_TABLE_ID = 'cros-log-visualizer-log-table';
69 CrosLogVisualizerView.LOG_FILTER_PNAME_ID =
70 'cros-log-visualizer-filter-pname';
71 CrosLogVisualizerView.LOG_SEARCH_INPUT_ID =
72 'cros-log-visualizer-search-input';
73 CrosLogVisualizerView.LOG_SEARCH_SAVE_BTN_ID = 'cros-log-visualizer-save-btn';
74 CrosLogVisualizerView.LOG_VISUALIZER_CONTAINER_ID =
75 'cros-log-visualizer-visualizer-container';
77 cr.addSingletonGetter(CrosLogVisualizerView);
80 * Contains types of logs we are interested in
88 * Contains headers of the log table
90 var TABLE_HEADERS_LIST = ['Level', 'Time', 'Process', 'PID', 'Description'];
92 CrosLogVisualizerView.prototype = {
93 // Inherit the superclass's methods.
94 __proto__: superClass.prototype,
97 * Called during the initialization of the View. Adds the system log
98 * listener into Browser_Bridge so that the system log can be retrieved.
100 initialize: function() {
101 g_browser.addSystemLogObserver(this);
102 $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID).addEventListener('keyup',
103 this.onSearchQueryChange_.bind(this));
104 $(CrosLogVisualizerView.LOG_SEARCH_SAVE_BTN_ID).addEventListener(
105 'click', this.onSaveBtnClicked_.bind(this));
109 * Called when the save button is clicked. Saves the current filter query
110 * to the mark history. And highlights the matched text with colors.
112 onSaveBtnClicked_: function() {
113 this.marker.addMarkHistory(this.currentQuery);
114 // Clears the filter query
115 $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID).value = '';
116 this.currentQuery = '';
118 this.populateTable();
122 onSearchQueryChange_: function() {
123 var inputField = $(CrosLogVisualizerView.LOG_SEARCH_INPUT_ID);
124 this.currentQuery = inputField.value;
129 * Creates the log table where each row represents a entry of log.
130 * This function is called if and only if the log is received from system
133 populateTable: function() {
134 var logTable = $(CrosLogVisualizerView.LOG_TABLE_ID);
135 logTable.innerHTML = '';
136 this.tableEntries.length = 0;
138 for (var i = 0; i < this.logEntries.length; i++) {
139 this.logEntries[i].rowNum = i;
140 var row = this.createTableRow(this.logEntries[i]);
141 logTable.appendChild(row);
146 * Creates the single row of the table where each row is a representation
147 * of the logEntry object.
149 createTableRow: function(entry) {
150 var row = document.createElement('tr');
151 for (var i = 0; i < 5; i++) {
155 var cells = row.childNodes;
157 cells[0].className = LOG_CELL_LEVEL_CLASSNAME;
158 var levelTag = addNodeWithText(cells[0], 'p', entry.level);
159 levelTag.className = LOG_CELL_LEVEL_CLASSNAME_LIST[entry.level];
162 cells[1].className = LOG_CELL_TIME_CLASSNAME;
163 cells[1].textContent = entry.getTime();
166 cells[2].className = LOG_CELL_PNAME_CLASSNAME;
167 this.marker.getHighlightedEntry(entry, 'processName', cells[2]);
170 cells[3].className = LOG_CELL_PID_CLASSNAME;
171 this.marker.getHighlightedEntry(entry, 'processID', cells[3]);
174 cells[4].className = LOG_CELL_DESCRIPTION_CLASSNAME;
175 this.marker.getHighlightedEntry(entry, 'description', cells[4]);
177 // Add the row into this.tableEntries for future reference
178 this.tableEntries.push(row);
183 * Regenerates the table and filter.
185 refresh: function() {
187 this.createLogMaker();
188 this.populateTable();
189 this.createVisualizer();
193 * Uses the search query to match the pattern in different fields of entry.
195 patternMatch: function(entry, pattern) {
196 return entry.processID.match(pattern) ||
197 entry.processName.match(pattern) ||
198 entry.level.match(pattern) ||
199 entry.description.match(pattern);
203 * Filters the log to show/hide the rows in the table.
204 * Each logEntry instance has a visibility property. This function
205 * shows or hides the row only based on this property.
207 filterLog: function() {
208 // Supports regular expression
209 var pattern = new RegExp(this.currentQuery, 'i');
210 for (var i = 0; i < this.logEntries.length; i++) {
211 var entry = this.logEntries[i];
212 // Filters the result by pname and level
213 var pNameCheckbox = this.pNameCheckboxes[entry.processName];
214 var levelCheckbox = this.levelCheckboxes[entry.level];
215 entry.visibility = pNameCheckbox.checked && levelCheckbox.checked &&
216 !this.visualizer.isOutOfBound(entry);
217 if (this.currentQuery) {
218 // If the search query is not empty, filter the result by query
219 entry.visibility = entry.visibility &&
220 this.patternMatch(entry, pattern);
222 // Changes style of HTML row based on the visibility of logEntry
223 if (entry.visibility) {
224 this.tableEntries[i].style.display = 'table-row';
226 this.tableEntries[i].style.display = 'none';
229 this.filterVisualizer();
233 * Initializes filter tags and checkboxes. There are two types of filters:
234 * Level and Process. Level filters are static that we have only 4 levels
235 * in total but process filters are dynamically changing based on the log.
236 * The filter layout looks like:
237 * |-----------------------------------------------------------------|
239 * | Section of process filter |
241 * |-----------------------------------------------------------------|
243 * | Section of level filter |
245 * |-----------------------------------------------------------------|
247 createFilter: function() {
248 this.createFilterByPName();
249 this.levelCheckboxes = {
250 'Error': $('checkbox-error'),
251 'Warning': $('checkbox-warning'),
252 'Info': $('checkbox-info'),
253 'Unknown': $('checkbox-unknown')
256 for (var level in this.levelCheckboxes) {
257 this.levelCheckboxes[level].addEventListener(
258 'change', this.onFilterChange_.bind(this));
263 * Helper function of createFilter(). Create filter section of
266 createFilterByPName: function() {
267 var filterContainerDiv = $(CrosLogVisualizerView.LOG_FILTER_PNAME_ID);
268 filterContainerDiv.innerHTML = 'Process: ';
269 for (var i = 0; i < this.pNames.length; i++) {
270 var pNameBlock = this.createPNameBlock(this.pNames[i]);
271 filterContainerDiv.appendChild(pNameBlock);
276 * Helper function of createFilterByPName(). Create a single filter block in
277 * the section of process filters.
279 createPNameBlock: function(pName) {
280 var block = document.createElement('span');
281 block.className = LOG_FILTER_PNAME_BLOCK_CLASSNAME;
283 var tag = document.createElement('label');
284 var span = document.createElement('span');
285 span.textContent = pName;
287 var checkbox = document.createElement('input');
288 checkbox.type = 'checkbox';
289 checkbox.name = pName;
290 checkbox.value = pName;
291 checkbox.checked = true;
292 checkbox.addEventListener('change', this.onFilterChange_.bind(this));
293 this.pNameCheckboxes[pName] = checkbox;
295 tag.appendChild(checkbox);
296 tag.appendChild(span);
297 block.appendChild(tag);
303 * Click handler for filter checkboxes. Everytime a checkbox is clicked,
304 * the visibility of related logEntries are changed.
306 onFilterChange_: function() {
311 * Creates a visualizer that visualizes the logs as a timeline graph
312 * during the initialization of the View.
314 createVisualizer: function() {
315 this.visualizer = new CrosLogVisualizer(this,
316 CrosLogVisualizerView.LOG_VISUALIZER_CONTAINER_ID);
317 this.visualizer.updateEvents(this.logEntries);
321 * Sync the visibility of log entries with the visualizer.
323 filterVisualizer: function() {
324 this.visualizer.updateEvents(this.logEntries);
328 * Called during the initialization. It creates the log marker that
329 * highlights log text.
331 createLogMaker: function() {
332 this.marker = new CrosLogMarker(this);
336 * Given a row text line of log, a logEntry instance is initialized and used
337 * for parsing. After the text is parsed, we put the instance into
338 * logEntries which is an array for storing. This function is called when
339 * the data is received from Browser Bridge.
341 addLogEntry: function(logType, textEntry) {
342 var newEntry = new CrosLogEntry();
343 if (logType == LOGS_LIST.NETWORK_LOG) {
344 newEntry.tokenizeNetworkLog(textEntry);
346 //TODO(shinfan): Add more if cases here
348 this.logEntries.push(newEntry);
351 var pName = newEntry.processName;
352 if (this.pNames.indexOf(pName) == -1) {
353 this.pNames.push(pName);
358 * Asynchronous call back function from Browser Bridge.
360 onSystemLogChanged: function(callback) {
361 if (callback.log == this.logData) return;
362 this.logData = callback.log;
363 // Clear the old array by setting length to zero
364 this.logEntries.length = 0;
365 var entries = callback.log.split('\n');
366 for (var i = 1; i < entries.length; i++) {
367 this.addLogEntry(LOGS_LIST.NETWORK_LOG, entries[i]);
373 return CrosLogVisualizerView;