// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://resources/ash/common/cr_elements/cr_button/cr_button.js';
import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
import 'chrome://resources/ash/common/cr_elements/cr_shared_vars.css.js';
import './log_object.js';
import './shared_style.css.js';
import {WebUiListenerMixin} from 'chrome://resources/ash/common/cr_elements/web_ui_listener_mixin.js';
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {NearbyLogsBrowserProxy} from './cross_device_logs_browser_proxy.js';
import {getTemplate} from './logging_tab.html.js';
import type {LogMessage, LogProvider, SelectOption} from './types.js';
import {Severity} from './types.js';
/**
* Converts log message to string format for saved download file.
*/
function logToSavedString(log: LogMessage): string {
// Convert to string value for |line.severity|.
let severity;
switch (log.severity) {
case Severity.INFO:
severity = 'INFO';
break;
case Severity.WARNING:
severity = 'WARNING';
break;
case Severity.ERROR:
severity = 'ERROR';
break;
case Severity.VERBOSE:
severity = 'VERBOSE';
break;
}
// Reduce the file path to just the file name for logging simplification.
const file = log.file.substring(log.file.lastIndexOf('/') + 1);
return `[${log.time} ${severity} ${file} (${log.line})] ${log.text}\n`;
}
const nearbyShareLogProvider: LogProvider = {
messageAddedEventName: 'log-message-added',
bufferClearedEventName: 'log-buffer-cleared',
logFilePrefix: 'nearby_internals_logs_',
getLogMessages: () => NearbyLogsBrowserProxy.getInstance().getLogMessages(),
};
const quickPairLogProvider: LogProvider = {
messageAddedEventName: 'quick-pair-log-message-added',
bufferClearedEventName: 'quick-pair-log-buffer-cleared',
logFilePrefix: 'fast_pair_logs_',
getLogMessages: () =>
NearbyLogsBrowserProxy.getInstance().getQuickPairLogMessages(),
};
/**
* Gets a log provider instance for a feature.
*/
function getLogProvider(feature: string): LogProvider {
switch (feature) {
case 'nearby-share':
return nearbyShareLogProvider;
case 'quick-pair':
return quickPairLogProvider;
default:
return quickPairLogProvider;
}
}
const LoggingTabElementBase = WebUiListenerMixin(PolymerElement);
class LoggingTabElement extends LoggingTabElementBase {
static get is() {
return 'logging-tab';
}
static get template() {
return getTemplate();
}
static get properties() {
return {
logList_: {
type: Array,
value: () => [],
},
filteredLogList_: {
type: Array,
value: () => [],
},
feature: {
type: String,
},
currentFilter_: {
type: String,
},
currentSeverity_: {
type: Severity,
value: Severity.VERBOSE,
},
logLevelList_: {
type: Array,
value: [
{name: 'VERBOSE', value: Severity.VERBOSE},
{name: 'INFO', value: Severity.INFO},
{name: 'WARNING', value: Severity.WARNING},
{name: 'ERROR', value: Severity.ERROR},
],
},
};
}
private logList_: LogMessage[];
private filteredLogList_: LogMessage[];
private feature: string;
private currentFilter_: string;
private currentSeverity_: Severity;
private logLevelList_: SelectOption[];
private logProvider_: LogProvider;
/**
* When the page is initialized, notify the C++ layer and load in the
* contents of its log buffer. Initialize WebUI Listeners.
*/
override connectedCallback() {
super.connectedCallback();
this.logProvider_ = getLogProvider(this.feature);
this.addWebUiListener(
this.logProvider_.messageAddedEventName,
(log: LogMessage) => this.onLogMessageAdded_(log));
this.addWebUiListener(
this.logProvider_.bufferClearedEventName,
() => this.onWebUiLogBufferCleared_());
this.logProvider_.getLogMessages().then(
logs => this.onGetLogMessages_(logs));
}
/**
* Clears javascript logs displayed, but c++ log buffer remains.
*/
private onClearLogsButtonClicked_(): void {
this.clearLogBuffer_();
}
/**
* Saves and downloads all javascript logs.
*/
private onSaveUnfilteredLogsButtonClicked_(): void {
this.onSaveLogsButtonClicked_(false);
}
/**
* Saves and downloads javascript logs that currently appear on the page.
*/
private onSaveFilteredLogsButtonClicked_(): void {
this.onSaveLogsButtonClicked_(true);
}
/**
* Saves and downloads javascript logs.
*/
private onSaveLogsButtonClicked_(filtered: boolean): void {
let blob;
if (filtered) {
blob = new Blob(
this.filteredLogList_.map(logToSavedString),
{type: 'text/plain;charset=utf-8'});
} else {
blob = new Blob(
this.logList_.map(logToSavedString),
{type: 'text/plain;charset=utf-8'});
}
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download =
this.logProvider_.logFilePrefix + new Date().toJSON() + '.txt';
document.body.appendChild(anchorElement);
anchorElement.click();
window.setTimeout(function() {
document.body.removeChild(anchorElement);
window.URL.revokeObjectURL(url);
}, 0);
}
/**
* Adds a log message to the javascript log list displayed. Called from the
* C++ WebUI handler when a log message is added to the log buffer.
*/
private onLogMessageAdded_(log: LogMessage): void {
this.push('logList_', log);
if ((log.text.match(this.currentFilter_) ||
log.file.match(this.currentFilter_)) &&
log.severity >= this.currentSeverity_) {
this.push('filteredLogList_', log);
}
}
private addLogFilter_(): void {
const logLevelSelector: HTMLSelectElement|null =
this.shadowRoot!.querySelector('#logLevelSelector');
if (logLevelSelector) {
switch (Number(logLevelSelector.value)) {
case Severity.VERBOSE:
this.set(
'filteredLogList_',
this.logList_.filter((log) => log.severity >= Severity.VERBOSE));
this.currentSeverity_ = Severity.VERBOSE;
break;
case Severity.INFO:
this.set(
'filteredLogList_',
this.logList_.filter((log) => log.severity >= Severity.INFO));
this.currentSeverity_ = Severity.INFO;
break;
case Severity.WARNING:
this.set(
'filteredLogList_',
this.logList_.filter((log) => log.severity >= Severity.WARNING));
this.currentSeverity_ = Severity.WARNING;
break;
case Severity.ERROR:
this.set(
'filteredLogList_',
this.logList_.filter((log) => log.severity >= Severity.ERROR));
this.currentSeverity_ = Severity.ERROR;
break;
}
}
const elem: HTMLSelectElement|null =
this.shadowRoot!.querySelector('#logSearch');
if (elem) {
this.currentFilter_ = elem.value;
this.set(
'filteredLogList_',
this.filteredLogList_.filter(
(log) =>
(log.text.match(this.currentFilter_) ||
log.file.match(this.currentFilter_))));
}
}
/**
* Called in response to WebUI handler clearing log buffer.
*/
private onWebUiLogBufferCleared_(): void {
this.clearLogBuffer_();
}
/**
* Parses an array of log messages and adds to the javascript list sent in
* from the initial page load.
*/
private onGetLogMessages_(logs: LogMessage[]): void {
this.logList_ = logs.concat(this.logList_);
this.filteredLogList_ = logs.concat(this.filteredLogList_);
}
/**
* Clears the javascript log buffer.
*/
private clearLogBuffer_(): void {
this.logList_ = [];
this.filteredLogList_ = [];
}
}
customElements.define(LoggingTabElement.is, LoggingTabElement);