Commit 1d0b25f6 authored by Juliet Levesque's avatar Juliet Levesque Committed by Commit Bot

[Multidevice] Add logging tab and send PA_LOG to MultiDevice Internals.

Add logging tab and support to send logs created with PA_LOG macro to
chrome://multidevice-internals page for display. Logs color coded by
severity and displayed with timestamp and log file. Screenshot of
display here:
https://screenshot.googleplex.com/bc0d994b-4f58-4793-84f8-144a150f1496

Change-Id: Ic68aad70c400c5bdc55f7fa37fb20c36c4669233
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2366654
Commit-Queue: Kyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Reviewed-by: default avatarLei Zhang <thestig@chromium.org>
Reviewed-by: default avatarJosh Nohle <nohle@chromium.org>
Cr-Commit-Position: refs/heads/master@{#800787}
parent e2077a9a
// Copyright 2020 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.
module.exports = {
'env': {
'browser': true,
'es6': true,
},
'rules': {'eqeqeq': ['error', 'always', {'null': 'ignore'}]},
};
......@@ -7,15 +7,60 @@ import("//tools/polymer/html_to_js.gni")
js_type_check("closure_compile") {
is_polymer3 = true
deps = [ ":multidevice_internals" ]
deps = [
":log_object",
":logging_tab",
":multidevice_internals",
":types",
]
}
js_library("multidevice_internals") {
deps = [
":log_object",
":logging_tab",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:cr.m",
]
}
js_library("logging_tab") {
deps = [
":log_object",
":multidevice_logs_browser_proxy",
":types",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
"//ui/webui/resources/js:cr.m",
"//ui/webui/resources/js:web_ui_listener_behavior.m",
]
}
js_library("log_object") {
deps = [
":multidevice_logs_browser_proxy",
":types",
"//third_party/polymer/v3_0/components-chromium/polymer:polymer_bundled",
]
}
js_library("multidevice_logs_browser_proxy") {
deps = [
":types",
"//ui/webui/resources/js:cr.m",
]
}
js_library("types") {
}
js_library("shared_style") {
}
html_to_js("web_components") {
js_files = [ "multidevice_internals.js" ]
js_files = [
"log_object.js",
"logging_tab.js",
"multidevice_internals.js",
"shared_style.js",
]
}
<style>
.warning-log {
background-color: rgb(255, 252, 239);
}
.error-log {
background-color: rgb(255, 241, 241);
}
.verbose-log {
background-color: rgb(235, 235, 235);
}
.default-log {
background-color: rgb(255, 255, 255);
}
#item-metadata {
color: #888;
display: flex;
font-size: 10px;
padding: 6px;
}
#flex {
flex: 1;
}
#text {
display: inline-block;
margin: 3px;
padding: 5px;
text-align: start;
width: 100%;
}
</style>
<div id="item" severity="[[item.severity]]">
<p id="text">[[item.text]]</p>
<div id="item-metadata">
<span>[[item.time]]</span>
<div id="flex"></div>
<span>[[item.file]]:[[item.line]]</span>
</div>
</div>
// Copyright 2020 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.
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {Severity} from './types.js';
Polymer({
is: 'log-object',
_template: html`{__html_template__}`,
properties: {
/**
* Underlying LogMessage data for this item. Contains read-only fields
* from the Multidevice backend, as well as fields computed by logging tab.
* Type: {!LogMessage}
*/
item: {
type: Object,
observer: 'itemChanged_',
},
},
/**
* Sets the log message style based on severity level.
* @private
*/
itemChanged_() {
switch (this.item.severity) {
case Severity.WARNING:
this.$['item'].className = 'warning-log';
break;
case Severity.ERROR:
this.$['item'].className = 'error-log';
break;
case Severity.VERBOSE:
this.$['item'].className = 'verbose-log';
break;
default:
this.$['item'].className = 'default-log';
break;
}
},
});
<style include="shared-style">
:host {
--standard-border: 1px solid black;
}
#logs-list {
display: flex;
flex-direction: column;
height: 100%;
white-space: pre-wrap;
}
log-object {
border-inline-end: var(--standard-border);
border-inline-start: var(--standard-border);
border-top: var(--standard-border);
}
log-object:last-child {
border-bottom: var(--standard-border);
}
</style>
<cr-button disabled="[[!logList_.length]]" class="internals-button"
on-click="onSaveLogsButtonClicked_">
Save Logs
</cr-button>
<cr-button disabled="[[!logList_.length]]" class="internals-button"
on-click="onClearLogsButtonClicked_">
Clear Logs
</cr-button>
<iron-list items="[[logList_]]" as="log" id="logs-list">
<template>
<log-object item="[[log]]">
</log-object>
</template>
</iron-list>
// Copyright 2020 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.
import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/polymer/v3_0/iron-list/iron-list.js';
import 'chrome://resources/cr_elements/shared_vars_css.m.js';
import './log_object.js';
import './shared_style.js';
import {WebUIListenerBehavior} from 'chrome://resources/js/web_ui_listener_behavior.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {MultideviceLogsBrowserProxy} from './multidevice_logs_browser_proxy.js';
import {LogMessage, Severity} from './types.js';
/**
* Converts log message to string format for saved download file.
* @param {!LogMessage} log
* @return {string}
*/
function logToSavedString_(log) {
// 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}`;
}
Polymer({
is: 'logging-tab',
_template: html`{__html_template__}`,
behaviors: [
WebUIListenerBehavior,
],
properties: {
/**
* @private {!Array<!LogMessage>}
*/
logList_: {
type: Array,
value: [],
},
},
/** @private {?MultideviceLogsBrowserProxy}*/
browserProxy_: null,
/**
* Initialize |browserProxy_|.
* @override
*/
created() {
this.browserProxy_ = MultideviceLogsBrowserProxy.getInstance();
},
/**
* When the page is initialized, start listening for log message events
* and load in the contents of its log buffer. Initialize WebUI Listeners.
* @override
*/
attached() {
this.addWebUIListener(
'multidevice-log-message-added', log => this.onLogMessageAdded_(log));
this.addWebUIListener(
'multidevice-log-buffer-cleared',
() => this.onWebUILogBufferCleared_());
this.browserProxy_.getLogMessages().then(
logs => this.onGetLogMessages_(logs));
},
/**
* Clears javascript logs displayed, but c++ log buffer remains.
* @private
*/
onClearLogsButtonClicked_() {
this.clearLogBuffer_();
},
/**
* Saves and downloads javascript logs that appear on the page.
* @private
*/
onSaveLogsButtonClicked_() {
const blob = new Blob(
this.getSerializedLogStrings_(), {type: 'text/plain;charset=utf-8'});
const url = URL.createObjectURL(blob);
const anchorElement = document.createElement('a');
anchorElement.href = url;
anchorElement.download =
'multidevice_internals_logs_' + new Date().toJSON() + '.txt';
document.body.appendChild(anchorElement);
anchorElement.click();
window.setTimeout(function() {
document.body.removeChild(anchorElement);
window.URL.revokeObjectURL(url);
}, 0);
},
/**
* Iterates through log messages in |logList_| and prepares them for download.
* @private
* @return {!Array<string>}
*/
getSerializedLogStrings_() {
return this.logList_.map(logToSavedString_);
},
/**
* 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.
* @param {!Array<!LogMessage>} log
* @private
*/
onLogMessageAdded_(log) {
this.unshift('logList_', log);
},
/**
* Called in response to WebUI handler clearing log buffer.
* @private
*/
onWebUILogBufferCleared_() {
this.clearLogBuffer_();
},
/**
* Parses an array of log messages and adds to the javascript list sent in
* from the initial page load.
* @param {!Array<!LogMessage>} logs
* @private
*/
onGetLogMessages_(logs) {
this.logList_ = logs.reverse().concat(this.logList_);
},
/**
* Clears the javascript log buffer.
* @private
*/
clearLogBuffer_() {
this.logList_ = [];
},
});
<h1>Hello, world!!</h1>
<style>
:host {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
app-header {
background-color: rgb(0, 134, 179);
color: white;
font-size: 200%;
padding: 8px;
text-align: center;
}
iron-pages {
flex: 1;
overflow: hidden;
}
iron-pages > * {
display: block;
height: 100%;
overflow: auto;
}
</style>
<app-header>
<app-toolbar>MultiDevice Internals</app-toolbar>
</app-header>
<iron-location path="{{path_}}"></iron-location>
<cr-tabs selected="{{selectedTabIndex_}}" tab-names="[[tabNames_]]"></cr-tabs>
<iron-pages selected="[[selectedTabIndex_]]">
<logging-tab></logging-tab>
</iron-pages>
......@@ -2,10 +2,81 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'chrome://resources/cr_elements/cr_tabs/cr_tabs.m.js';
import 'chrome://resources/polymer/v3_0/iron-location/iron-location.js';
import 'chrome://resources/polymer/v3_0/iron-pages/iron-pages.js';
import './logging_tab.js';
import './shared_style.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
Polymer({
is: 'multidevice-internals',
_template: html`{__html_template__}`,
properties: {
/** @private */
selectedTabIndex_: {
type: Number,
value: 0,
observer: 'selectedTabChanged_',
},
/** @private */
path_: {
type: String,
value: '',
observer: 'pathChanged_',
},
/** @private */
tabNames_: {
type: Array,
value: () => ['Logs'],
readonly: true,
},
},
/**
* Updates the current tab location to reflect selection change
* @param {number} newValue
* @param {number|undefined} oldValue
* @private
*/
selectedTabChanged_(newValue, oldValue) {
if (!oldValue) {
return;
}
const defaultTab = this.tabNames_[0].toLowerCase();
const lowerCaseTabName = this.tabNames_[newValue].toLowerCase();
this.path_ =
'/' + (lowerCaseTabName === defaultTab ? '' : lowerCaseTabName);
},
/**
* Returns the index of the currently selected tab corresponding to the
* path or zero if no match.
* @param {string} path
* @return {number}
* @private
*/
selectedTabFromPath_(path) {
const index = this.tabNames_.findIndex(tab => path === tab.toLowerCase());
if (index < 0) {
return 0;
}
return index;
},
/**
* Updates the selection property on path change.
* @param {string} newValue
* @param {string|undefined} oldValue
* @private
*/
pathChanged_(newValue, oldValue) {
this.selectedTabIndex_ = this.selectedTabFromPath_(newValue.substr(1));
},
});
......@@ -15,10 +15,28 @@
<include name="IDR_MULTIDEVICE_INTERNALS_INDEX_HTML"
file="index.html"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_LOG_OBJECT_JS"
file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\log_object.js"
use_base_dir="false"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_MULTIDEVICE_INTERNALS_JS"
file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\multidevice_internals.js"
use_base_dir="false"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_LOGGING_TAB_JS"
file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\logging_tab.js"
use_base_dir="false"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_SHARED_STYLE_JS"
file="${root_gen_dir}\chrome\browser\resources\chromeos\multidevice_internals\shared_style.js"
use_base_dir="false"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_MULTIDEVICE_LOGS_BROWSER_PROXY_JS"
file="multidevice_logs_browser_proxy.js"
type="BINDATA"/>
<include name="IDR_MULTIDEVICE_INTERNALS_TYPES_JS"
file="types.js"
type="BINDATA"/>
</includes>
</release>
</grit>
// Copyright 2020 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.
import {addSingletonGetter, sendWithPromise} from 'chrome://resources/js/cr.m.js';
import {LogMessage} from './types.js';
/**
* JavaScript hooks into the native WebUI handler to pass LogMessages to the
* logging tab.
*/
export class MultideviceLogsBrowserProxy {
/**
* @return {!Promise<!Array<!LogMessage>>}
*/
getLogMessages() {
return sendWithPromise('getMultideviceLogMessages');
}
}
addSingletonGetter(MultideviceLogsBrowserProxy);
<template>
<style include="cr-shared-style">
:host {
cursor: default;
font-family: monospace;
font-size: 12px;
}
.internals-button {
background-color: rgb(0, 134, 179);
color: white;
font-family: Roboto, Roboto, sans-serif;
margin: 5px;
}
</style>
</template>
// Copyright 2020 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.
import 'chrome://resources/cr_elements/shared_style_css.m.js';
const template = document.createElement('template');
template.innerHTML = `
<dom-module id="shared-style">{__html_template__}</dom-module>
`;
document.body.appendChild(template.content.cloneNode(true));
// Copyright 2020 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.
/**
* Severity enum based on LogMessage format. Needs to stay in sync with the
* MultideviceInternalsLogsHandler.
* @enum {number}
*/
export const Severity = {
VERBOSE: -1,
INFO: 0,
WARNING: 1,
ERROR: 2,
};
/**
* The type of log message object. The definition is based on
* chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.cc:
* LogMessageToDictionary()
* @typedef {{text: string,
* time: string,
* file: string,
* line: number,
* severity: Severity}}
*/
export let LogMessage;
......@@ -2305,6 +2305,8 @@ static_library("ui") {
"webui/chromeos/machine_learning/machine_learning_internals_page_handler.h",
"webui/chromeos/machine_learning/machine_learning_internals_ui.cc",
"webui/chromeos/machine_learning/machine_learning_internals_ui.h",
"webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.cc",
"webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.h",
"webui/chromeos/multidevice_internals/multidevice_internals_ui.cc",
"webui/chromeos/multidevice_internals/multidevice_internals_ui.h",
"webui/chromeos/multidevice_setup/multidevice_setup_dialog.cc",
......
// Copyright 2020 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.
#include "chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.h"
#include "base/bind.h"
#include "base/i18n/time_formatting.h"
#include "base/values.h"
#include "chromeos/components/multidevice/logging/logging.h"
namespace chromeos {
namespace multidevice {
namespace {
// Keys in the JSON representation of a log message
const char kLogMessageTextKey[] = "text";
const char kLogMessageTimeKey[] = "time";
const char kLogMessageFileKey[] = "file";
const char kLogMessageLineKey[] = "line";
const char kLogMessageSeverityKey[] = "severity";
// Converts |log_message| to a raw dictionary value used as a JSON argument to
// JavaScript functions.
base::Value LogMessageToDictionary(
const chromeos::multidevice::LogBuffer::LogMessage& log_message) {
base::Value dictionary(base::Value::Type::DICTIONARY);
dictionary.SetStringKey(kLogMessageTextKey, log_message.text);
dictionary.SetStringKey(
kLogMessageTimeKey,
base::TimeFormatTimeOfDayWithMilliseconds(log_message.time));
dictionary.SetStringKey(kLogMessageFileKey, log_message.file);
dictionary.SetIntKey(kLogMessageLineKey, log_message.line);
dictionary.SetIntKey(kLogMessageSeverityKey, log_message.severity);
return dictionary;
}
} // namespace
MultideviceLogsHandler::MultideviceLogsHandler() : observer_(this) {}
MultideviceLogsHandler::~MultideviceLogsHandler() = default;
void MultideviceLogsHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"getMultideviceLogMessages",
base::BindRepeating(&MultideviceLogsHandler::HandleGetLogMessages,
base::Unretained(this)));
}
void MultideviceLogsHandler::OnJavascriptAllowed() {
observer_.Add(multidevice::LogBuffer::GetInstance());
}
void MultideviceLogsHandler::OnJavascriptDisallowed() {
observer_.RemoveAll();
}
void MultideviceLogsHandler::HandleGetLogMessages(const base::ListValue* args) {
AllowJavascript();
const base::Value& callback_id = args->GetList()[0];
base::Value list(base::Value::Type::LIST);
for (const auto& log :
*chromeos::multidevice::LogBuffer::GetInstance()->logs()) {
list.Append(LogMessageToDictionary(log));
}
ResolveJavascriptCallback(callback_id, list);
}
void MultideviceLogsHandler::OnLogBufferCleared() {
FireWebUIListener("multidevice-log-buffer-cleared");
}
void MultideviceLogsHandler::OnLogMessageAdded(
const chromeos::multidevice::LogBuffer::LogMessage& log_message) {
FireWebUIListener("multidevice-log-message-added",
LogMessageToDictionary(log_message));
}
} // namespace multidevice
} // namespace chromeos
// Copyright 2020 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.
#ifndef CHROME_BROWSER_UI_WEBUI_CHROMEOS_MULTIDEVICE_INTERNALS_MULTIDEVICE_INTERNALS_LOGS_HANDLER_H_
#define CHROME_BROWSER_UI_WEBUI_CHROMEOS_MULTIDEVICE_INTERNALS_MULTIDEVICE_INTERNALS_LOGS_HANDLER_H_
#include "base/scoped_observer.h"
#include "chromeos/components/multidevice/logging/log_buffer.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "content/public/browser/web_ui_message_handler.h"
namespace base {
class ListValue;
} // namespace base
namespace chromeos {
namespace multidevice {
// WebUIMessageHandler for the PA_LOG Macro to pass logging messages to the
// chrome://multidevice-internals logging tab.
class MultideviceLogsHandler : public content::WebUIMessageHandler,
public multidevice::LogBuffer::Observer {
public:
MultideviceLogsHandler();
MultideviceLogsHandler(const MultideviceLogsHandler&) = delete;
MultideviceLogsHandler& operator=(const MultideviceLogsHandler&) = delete;
~MultideviceLogsHandler() override;
// content::WebUIMessageHandler:
void RegisterMessages() override;
void OnJavascriptAllowed() override;
void OnJavascriptDisallowed() override;
// chromeos::multidevice::LogBuffer::Observer:
void OnLogMessageAdded(
const multidevice::LogBuffer::LogMessage& log_message) override;
void OnLogBufferCleared() override;
private:
// Message handler callback that returns the Log Buffer in dictionary form.
void HandleGetLogMessages(const base::ListValue* args);
// Message handler callback that clears the Log Buffer.
void ClearLogBuffer(const base::ListValue* args);
ScopedObserver<multidevice::LogBuffer, multidevice::LogBuffer::Observer>
observer_{this};
};
} // namespace multidevice
} // namespace chromeos
#endif // CHROME_BROWSER_UI_WEBUI_CHROMEOS_MULTIDEVICE_INTERNALS_MULTIDEVICE_INTERNALS_LOGS_HANDLER_H_
......@@ -6,6 +6,7 @@
#include "base/containers/span.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/chromeos/multidevice_internals/multidevice_internals_logs_handler.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/multidevice_internals_resources.h"
......@@ -24,7 +25,7 @@ constexpr char kMultideviceInternalsGeneratedPath[] =
} // namespace
MultideviceInternalsUI::MultideviceInternalsUI(content::WebUI* web_ui)
: ui::MojoWebUIController(web_ui) {
: ui::MojoWebUIController(web_ui, /*enable_chrome_send=*/true) {
Profile* profile = Profile::FromWebUI(web_ui);
content::WebUIDataSource* html_source = content::WebUIDataSource::Create(
chrome::kChromeUIMultiDeviceInternalsHost);
......@@ -36,6 +37,8 @@ MultideviceInternalsUI::MultideviceInternalsUI(content::WebUI* web_ui)
kMultideviceInternalsGeneratedPath, IDR_MULTIDEVICE_INTERNALS_INDEX_HTML);
content::WebUIDataSource::Add(profile, html_source);
web_ui->AddMessageHandler(
std::make_unique<multidevice::MultideviceLogsHandler>());
}
MultideviceInternalsUI::~MultideviceInternalsUI() = default;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment