blob: fa6927a8286abcb8cc9cb0ff7a455da01e0e9328 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* This view displays the log messages from various resources in an
* interactive log analyzer
*
* - Filter checkboxes
* - Filter text inputs
* - Display the log by different sections: time|level|process|description
*
*/
var CrosLogAnalyzerView = (function() {
'use strict';
// Inherits from DivView.
var superClass = DivView;
// Special classes (defined in log_analyzer_view.css)
var LOG_CONTAINER_CLASSNAME = 'cros-log-analyzer-container';
var LOG_FILTER_PNAME_BLOCK_CLASSNAME = 'cros-log-analyzer-filter-pname-block';
var LOG_CELL_HEADER_CLASSNAME = 'cros-log-analyzer-td-head';
var LOG_CELL_TIME_CLASSNAME = 'cros-log-analyzer-td-time';
var LOG_CELL_PNAME_CLASSNAME = 'cros-log-analyzer-td-pname';
var LOG_CELL_PID_CLASSNAME = 'cros-log-analyzer-td-pid';
var LOG_CELL_DESCRIPTION_CLASSNAME = 'cros-log-analyzer-td-description';
var LOG_CELL_LEVEL_CLASSNAME = 'cros-log-analyzer-td-level';
var LOG_CELL_LEVEL_CLASSNAME_LIST = {
'Error': 'cros-log-analyzer-td-level-error',
'Warning': 'cros-log-analyzer-td-level-warning',
'Info': 'cros-log-analyzer-td-level-info',
'Unknown': 'cros-log-analyzer-td-level-unknown'
};
/**
* @constructor
*/
function CrosLogAnalyzerView() {
assertFirstConstructorCall(CrosLogAnalyzerView);
// Call superclass's constructor.
superClass.call(this, CrosLogAnalyzerView.MAIN_BOX_ID);
// Stores log entry objects
this.logEntries = [];
// Stores current search query
this.currentQuery = '';
// Stores raw text data of log
this.logData = '';
// Stores all the unique process names
this.pNames = [];
// References to special HTML elements in log_analyzer_view.html
this.pNameCheckboxes = {};
this.levelCheckboxes = {};
this.tableEntries = [];
this.initialize();
}
CrosLogAnalyzerView.TAB_ID = 'tab-handle-cros-log-analyzer';
CrosLogAnalyzerView.TAB_NAME = 'Log Analyzer';
CrosLogAnalyzerView.TAB_HASH = '#analyzer';
// IDs for special HTML elements in log_analyzer_view.html
CrosLogAnalyzerView.MAIN_BOX_ID = 'cros-log-analyzer-tab-content';
CrosLogAnalyzerView.LOG_TABLE_ID = 'cros-log-analyzer-log-table';
CrosLogAnalyzerView.LOG_FILTER_PNAME_ID = 'cros-log-analyzer-filter-pname';
CrosLogAnalyzerView.LOG_SEARCH_INPUT_ID = 'cros-log-analyzer-search-input';
CrosLogAnalyzerView.LOG_SEARCH_SAVE_BTN_ID = 'cros-log-analyzer-save-btn';
CrosLogAnalyzerView.LOG_VISUALIZER_CONTAINER_ID =
'cros-log-analyzer-visualizer-container';
cr.addSingletonGetter(CrosLogAnalyzerView);
/**
* Contains types of logs we are interested in
*/
var LOGS_LIST = {
'NETWORK_LOG': 1,
'SYSTEM_LOG': 2
};
/**
* Contains headers of the log table
*/
var TABLE_HEADERS_LIST = ['Level', 'Time', 'Process', 'PID', 'Description'];
CrosLogAnalyzerView.prototype = {
// Inherit the superclass's methods.
__proto__: superClass.prototype,
/**
* Called during the initialization of the View. Adds the system log
* listener into Browser_Bridge so that the system log can be retrieved.
*/
initialize: function() {
g_browser.addSystemLogObserver(this);
$(CrosLogAnalyzerView.LOG_SEARCH_INPUT_ID).addEventListener('keyup',
this.onSearchQueryChange_.bind(this));
$(CrosLogAnalyzerView.LOG_SEARCH_SAVE_BTN_ID).addEventListener(
'click', this.onSaveBtnClicked_.bind(this));
},
/**
* Called when the save button is clicked. Saves the current filter query
* to the mark history. And highlights the matched text with colors.
*/
onSaveBtnClicked_: function() {
this.marker.addMarkHistory(this.currentQuery);
// Clears the filter query
$(CrosLogAnalyzerView.LOG_SEARCH_INPUT_ID).value = '';
this.currentQuery = '';
// Refresh the table
this.populateTable();
this.filterLog();
},
onSearchQueryChange_: function() {
var inputField = $(CrosLogAnalyzerView.LOG_SEARCH_INPUT_ID);
this.currentQuery = inputField.value;
this.filterLog();
},
/**
* Creates the log table where each row represents a entry of log.
* This function is called if and only if the log is received from system
* level.
*/
populateTable: function() {
var logTable = $(CrosLogAnalyzerView.LOG_TABLE_ID);
logTable.innerHTML = '';
this.tableEntries.length = 0;
// Create entries
for (var i = 0; i < this.logEntries.length; i++) {
this.logEntries[i].rowNum = i;
var row = this.createTableRow(this.logEntries[i]);
logTable.appendChild(row);
}
},
/**
* Creates the single row of the table where each row is a representation
* of the logEntry object.
*/
createTableRow: function(entry) {
var row = document.createElement('tr');
for (var i = 0; i < 5; i++) {
// Creates rows
addNode(row, 'td');
}
var cells = row.childNodes;
// Level cell
cells[0].className = LOG_CELL_LEVEL_CLASSNAME;
var levelTag = addNodeWithText(cells[0], 'p', entry.level);
levelTag.className = LOG_CELL_LEVEL_CLASSNAME_LIST[entry.level];
// Time cell
cells[1].className = LOG_CELL_TIME_CLASSNAME;
cells[1].textContent = entry.getTime();
// Process name cell
cells[2].className = LOG_CELL_PNAME_CLASSNAME;
this.marker.getHighlightedEntry(entry, 'processName', cells[2]);
// Process ID cell
cells[3].className = LOG_CELL_PID_CLASSNAME;
this.marker.getHighlightedEntry(entry, 'processID', cells[3]);
// Description cell
cells[4].className = LOG_CELL_DESCRIPTION_CLASSNAME;
this.marker.getHighlightedEntry(entry, 'description', cells[4]);
// Add the row into this.tableEntries for future reference
this.tableEntries.push(row);
return row;
},
/**
* Regenerates the table and filter.
*/
refresh: function() {
this.createFilter();
this.createLogMaker();
this.populateTable();
this.createVisualizer();
},
/**
* Uses the search query to match the pattern in different fields of entry.
*/
patternMatch: function(entry, pattern) {
return entry.processID.match(pattern) ||
entry.processName.match(pattern) ||
entry.level.match(pattern) ||
entry.description.match(pattern);
},
/**
* Filters the log to show/hide the rows in the table.
* Each logEntry instance has a visibility property. This function
* shows or hides the row only based on this property.
*/
filterLog: function() {
// Supports regular expression
var pattern = new RegExp(this.currentQuery, 'i');
for (var i = 0; i < this.logEntries.length; i++) {
var entry = this.logEntries[i];
// Filters the result by pname and level
var pNameCheckbox = this.pNameCheckboxes[entry.processName];
var levelCheckbox = this.levelCheckboxes[entry.level];
entry.visibility = pNameCheckbox.checked && levelCheckbox.checked &&
!this.visualizer.isOutOfBound(entry);
if (this.currentQuery) {
// If the search query is not empty, filter the result by query
entry.visibility = entry.visibility &&
this.patternMatch(entry, pattern);
}
// Changes style of HTML row based on the visibility of logEntry
if (entry.visibility) {
this.tableEntries[i].style.display = 'table-row';
} else {
this.tableEntries[i].style.display = 'none';
}
}
this.filterVisualizer();
},
/**
* Initializes filter tags and checkboxes. There are two types of filters:
* Level and Process. Level filters are static that we have only 4 levels
* in total but process filters are dynamically changing based on the log.
* The filter layout looks like:
* |-----------------------------------------------------------------|
* | |
* | Section of process filter |
* | |
* |-----------------------------------------------------------------|
* | |
* | Section of level filter |
* | |
* |-----------------------------------------------------------------|
*/
createFilter: function() {
this.createFilterByPName();
this.levelCheckboxes = {
'Error': $('checkbox-error'),
'Warning': $('checkbox-warning'),
'Info': $('checkbox-info'),
'Unknown': $('checkbox-unknown')
};
for (var level in this.levelCheckboxes) {
this.levelCheckboxes[level].addEventListener(
'change', this.onFilterChange_.bind(this));
}
},
/**
* Helper function of createFilter(). Create filter section of
* process filters.
*/
createFilterByPName: function() {
var filterContainerDiv = $(CrosLogAnalyzerView.LOG_FILTER_PNAME_ID);
filterContainerDiv.innerHTML = 'Process: ';
for (var i = 0; i < this.pNames.length; i++) {
var pNameBlock = this.createPNameBlock(this.pNames[i]);
filterContainerDiv.appendChild(pNameBlock);
}
},
/**
* Helper function of createFilterByPName(). Create a single filter block in
* the section of process filters.
*/
createPNameBlock: function(pName) {
var block = document.createElement('span');
block.className = LOG_FILTER_PNAME_BLOCK_CLASSNAME;
var tag = document.createElement('label');
var span = document.createElement('span');
span.textContent = pName;
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.name = pName;
checkbox.value = pName;
checkbox.checked = true;
checkbox.addEventListener('change', this.onFilterChange_.bind(this));
this.pNameCheckboxes[pName] = checkbox;
tag.appendChild(checkbox);
tag.appendChild(span);
block.appendChild(tag);
return block;
},
/**
* Click handler for filter checkboxes. Everytime a checkbox is clicked,
* the visibility of related logEntries are changed.
*/
onFilterChange_: function() {
this.filterLog();
},
/**
* Creates a visualizer that visualizes the logs as a timeline graph
* during the initialization of the View.
*/
createVisualizer: function() {
this.visualizer = new CrosLogVisualizer(this,
CrosLogAnalyzerView.LOG_VISUALIZER_CONTAINER_ID);
this.visualizer.updateEvents(this.logEntries);
},
/**
* Sync the visibility of log entries with the visualizer.
*/
filterVisualizer: function() {
this.visualizer.updateEvents(this.logEntries);
},
/**
* Called during the initialization. It creates the log marker that
* highlights log text.
*/
createLogMaker: function() {
this.marker = new CrosLogMarker(this);
},
/**
* Given a row text line of log, a logEntry instance is initialized and used
* for parsing. After the text is parsed, we put the instance into
* logEntries which is an array for storing. This function is called when
* the data is received from Browser Bridge.
*/
addLogEntry: function(logType, textEntry) {
var newEntry = new CrosLogEntry();
if (logType == LOGS_LIST.NETWORK_LOG) {
newEntry.tokenizeNetworkLog(textEntry);
} else {
//TODO(shinfan): Add more if cases here
}
this.logEntries.push(newEntry);
// Record pname
var pName = newEntry.processName;
if (this.pNames.indexOf(pName) == -1) {
this.pNames.push(pName);
}
},
/*
* Asynchronous call back function from Browser Bridge.
*/
onSystemLogChanged: function(callback) {
if (callback.log == this.logData) return;
this.logData = callback.log;
// Clear the old array by setting length to zero
this.logEntries.length = 0;
var entries = callback.log.split('\n');
for (var i = 1; i < entries.length; i++) {
this.addLogEntry(LOGS_LIST.NETWORK_LOG, entries[i]);
}
this.refresh();
}
};
return CrosLogAnalyzerView;
})();