mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 14:21:37 +00:00
Logging page
- Per isolate logging page. - Drop down to control log level threshold. - Console area where messages are displayed. - UI test. R=rmacnak@google.com Review URL: https://codereview.chromium.org//1250853006 .
This commit is contained in:
parent
ea65b867d3
commit
d3154f4247
|
@ -36,6 +36,7 @@ export 'package:observatory/src/elements/isolate_view.dart';
|
|||
export 'package:observatory/src/elements/json_view.dart';
|
||||
export 'package:observatory/src/elements/library_ref.dart';
|
||||
export 'package:observatory/src/elements/library_view.dart';
|
||||
export 'package:observatory/src/elements/logging.dart';
|
||||
export 'package:observatory/src/elements/metrics.dart';
|
||||
export 'package:observatory/src/elements/nav_bar.dart';
|
||||
export 'package:observatory/src/elements/object_common.dart';
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<link rel="import" href="src/elements/json_view.html">
|
||||
<link rel="import" href="src/elements/library_ref.html">
|
||||
<link rel="import" href="src/elements/library_view.html">
|
||||
<link rel="import" href="src/elements/logging.html">
|
||||
<link rel="import" href="src/elements/metrics.html">
|
||||
<link rel="import" href="src/elements/nav_bar.html">
|
||||
<link rel="import" href="src/elements/object_common.html">
|
||||
|
|
|
@ -138,6 +138,7 @@ class ObservatoryApplication extends Observable {
|
|||
_pageRegistry.add(new ErrorViewPage(this));
|
||||
_pageRegistry.add(new MetricsPage(this));
|
||||
_pageRegistry.add(new PortsPage(this));
|
||||
_pageRegistry.add(new LoggingPage(this));
|
||||
// Note that ErrorPage must be the last entry in the list as it is
|
||||
// the catch all.
|
||||
_pageRegistry.add(new ErrorPage(this));
|
||||
|
|
|
@ -294,6 +294,22 @@ class HeapSnapshotPage extends SimplePage {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class LoggingPage extends SimplePage {
|
||||
LoggingPage(app) : super('logging', 'logging-page', app);
|
||||
|
||||
void _visit(Uri uri) {
|
||||
super._visit(uri);
|
||||
getIsolate(uri).then((isolate) {
|
||||
if (element != null) {
|
||||
/// Update the page.
|
||||
LoggingPageElement page = element;
|
||||
page.isolate = isolate;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorViewPage extends Page {
|
||||
ErrorViewPage(app) : super(app);
|
||||
|
||||
|
|
|
@ -191,6 +191,11 @@
|
|||
See <a on-click="{{ goto }}" _href="{{ gotoLink('/ports', isolate) }}">ports</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memberItem">
|
||||
<div class="memberValue">
|
||||
See <a on-click="{{ goto }}" _href="{{ gotoLink('/logging', isolate) }}">logging</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Temporarily disabled until UI for dart:io is acceptable.
|
||||
<template if="{{ isolate.ioEnabled }}">
|
||||
<div class="memberItem">
|
||||
|
|
202
runtime/observatory/lib/src/elements/logging.dart
Normal file
202
runtime/observatory/lib/src/elements/logging.dart
Normal file
|
@ -0,0 +1,202 @@
|
|||
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
library logging_page;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:html';
|
||||
import 'observatory_element.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:observatory/service.dart';
|
||||
import 'package:observatory/app.dart';
|
||||
import 'package:observatory/elements.dart';
|
||||
import 'package:observatory/utils.dart';
|
||||
import 'package:polymer/polymer.dart';
|
||||
|
||||
@CustomTag('logging-page')
|
||||
class LoggingPageElement extends ObservatoryElement {
|
||||
static const _kPageSelector = '#page';
|
||||
static const _kLogSelector = '#log';
|
||||
static const _kSeverityLevelSelector = '#severityLevelSelector';
|
||||
|
||||
LoggingPageElement.created() : super.created();
|
||||
|
||||
domReady() {
|
||||
super.domReady();
|
||||
_insertLevels();
|
||||
}
|
||||
|
||||
attached() {
|
||||
super.attached();
|
||||
_sub();
|
||||
_resizeSubscription = window.onResize.listen((_) => _updatePageHeight());
|
||||
_updatePageHeight();
|
||||
// Turn on the periodic poll timer for this page.
|
||||
pollPeriod = const Duration(milliseconds:100);
|
||||
}
|
||||
|
||||
detached() {
|
||||
super.detached();
|
||||
if (_resizeSubscription != null) {
|
||||
_resizeSubscription.cancel();
|
||||
_resizeSubscription = null;
|
||||
}
|
||||
_unsub();
|
||||
}
|
||||
|
||||
void onPoll() {
|
||||
_flushPendingLogs();
|
||||
}
|
||||
|
||||
_updatePageHeight() {
|
||||
HtmlElement e = shadowRoot.querySelector(_kPageSelector);
|
||||
final totalHeight = window.innerHeight;
|
||||
final top = e.offset.top;
|
||||
final bottomMargin = 32;
|
||||
final mainHeight = totalHeight - top - bottomMargin;
|
||||
e.style.setProperty('height', '${mainHeight}px');
|
||||
}
|
||||
|
||||
_insertLevels() {
|
||||
SelectElement severityLevelSelector =
|
||||
shadowRoot.querySelector(_kSeverityLevelSelector);
|
||||
severityLevelSelector.children.clear();
|
||||
_maxLevelLabelLength = 0;
|
||||
for (var level in Level.LEVELS) {
|
||||
var option = new OptionElement();
|
||||
option.value = level.value.toString();
|
||||
option.label = level.name;
|
||||
if (level.name.length > _maxLevelLabelLength) {
|
||||
_maxLevelLabelLength = level.name.length;
|
||||
}
|
||||
severityLevelSelector.children.add(option);
|
||||
}
|
||||
severityLevelSelector.selectedIndex = 0;
|
||||
severityLevel = Level.ALL.value.toString();
|
||||
}
|
||||
|
||||
_reset() {
|
||||
logRecords.clear();
|
||||
_unsub();
|
||||
_sub();
|
||||
_renderFull();
|
||||
}
|
||||
|
||||
_unsub() {
|
||||
cancelFutureSubscription(_loggingSubscriptionFuture);
|
||||
_loggingSubscriptionFuture = null;
|
||||
}
|
||||
|
||||
_sub() {
|
||||
if (_loggingSubscriptionFuture != null) {
|
||||
// Already subscribed.
|
||||
return;
|
||||
}
|
||||
_loggingSubscriptionFuture =
|
||||
app.vm.listenEventStream(Isolate.kLoggingStream, _onEvent);
|
||||
}
|
||||
|
||||
_append(Map logRecord) {
|
||||
logRecords.add(logRecord);
|
||||
if (_shouldDisplay(logRecord)) {
|
||||
// Queue for display.
|
||||
pendingLogRecords.add(logRecord);
|
||||
}
|
||||
}
|
||||
|
||||
Element _renderAppend(Map logRecord) {
|
||||
DivElement logContainer = shadowRoot.querySelector(_kLogSelector);
|
||||
var element = new DivElement();
|
||||
element.classes.add('logItem');
|
||||
element.classes.add(logRecord['level'].name);
|
||||
element.appendText(
|
||||
'${logRecord["level"].name.padLeft(_maxLevelLabelLength)} '
|
||||
'${Utils.formatDateTime(logRecord["time"])} '
|
||||
'${logRecord["message"].valueAsString}\n');
|
||||
logContainer.children.add(element);
|
||||
return element;
|
||||
}
|
||||
|
||||
_renderFull() {
|
||||
DivElement logContainer = shadowRoot.querySelector(_kLogSelector);
|
||||
logContainer.children.clear();
|
||||
pendingLogRecords.clear();
|
||||
for (var logRecord in logRecords) {
|
||||
if (_shouldDisplay(logRecord)) {
|
||||
_renderAppend(logRecord);
|
||||
}
|
||||
}
|
||||
_scrollToBottom(logContainer);
|
||||
}
|
||||
|
||||
/// Is [container] scrolled to the within [threshold] pixels of the bottom?
|
||||
static bool _isScrolledToBottom(DivElement container, [int threshold = 2]) {
|
||||
if (container == null) {
|
||||
return false;
|
||||
}
|
||||
// scrollHeight -> complete height of element including scrollable area.
|
||||
// clientHeight -> height of element on page.
|
||||
// scrollTop -> how far is an element scrolled (from 0 to scrollHeight).
|
||||
final distanceFromBottom =
|
||||
container.scrollHeight - container.clientHeight - container.scrollTop;
|
||||
const threshold = 2; // 2 pixel slop.
|
||||
return distanceFromBottom <= threshold;
|
||||
}
|
||||
|
||||
/// Scroll [container] so the bottom content is visible.
|
||||
static _scrollToBottom(DivElement container) {
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
// Adjust scroll so that the bottom of the content is visible.
|
||||
container.scrollTop = container.scrollHeight - container.clientHeight;
|
||||
}
|
||||
|
||||
_flushPendingLogs() {
|
||||
DivElement logContainer = shadowRoot.querySelector(_kLogSelector);
|
||||
bool autoScroll = _isScrolledToBottom(logContainer);
|
||||
var lastElement;
|
||||
for (var logRecord in pendingLogRecords) {
|
||||
lastElement = _renderAppend(logRecord);
|
||||
}
|
||||
if (autoScroll) {
|
||||
_scrollToBottom(logContainer);
|
||||
}
|
||||
pendingLogRecords.clear();
|
||||
}
|
||||
|
||||
_onEvent(ServiceEvent event) {
|
||||
assert(event.kind == Isolate.kLoggingStream);
|
||||
_append(event.logRecord);
|
||||
}
|
||||
|
||||
void isolateChanged(oldValue) {
|
||||
_reset();
|
||||
}
|
||||
|
||||
void severityLevelChanged(oldValue) {
|
||||
_severityLevelValue = int.parse(severityLevel);
|
||||
_renderFull();
|
||||
}
|
||||
|
||||
Future clear() {
|
||||
logRecords.clear();
|
||||
pendingLogRecords.clear();
|
||||
_renderFull();
|
||||
return new Future.value(null);
|
||||
}
|
||||
|
||||
bool _shouldDisplay(Map logRecord) {
|
||||
return logRecord['level'].value >= _severityLevelValue;
|
||||
}
|
||||
|
||||
@observable Isolate isolate;
|
||||
@observable String severityLevel;
|
||||
int _severityLevelValue = 0;
|
||||
int _maxLevelLabelLength = 0;
|
||||
Future<StreamSubscription> _loggingSubscriptionFuture;
|
||||
StreamSubscription _resizeSubscription;
|
||||
final List<Map> logRecords = new List<Map>();
|
||||
final List<Map> pendingLogRecords = new List<Map>();
|
||||
}
|
77
runtime/observatory/lib/src/elements/logging.html
Normal file
77
runtime/observatory/lib/src/elements/logging.html
Normal file
|
@ -0,0 +1,77 @@
|
|||
<link rel="import" href="../../../../packages/polymer/polymer.html">
|
||||
<link rel="import" href="code_ref.html">
|
||||
<link rel="import" href="function_ref.html">
|
||||
<link rel="import" href="nav_bar.html">
|
||||
<link rel="import" href="observatory_element.html">
|
||||
<link rel="import" href="sliding_checkbox.html">
|
||||
<link rel="import" href="view_footer.html">
|
||||
|
||||
<polymer-element name="logging-page" extends="observatory-element">
|
||||
<template>
|
||||
<link rel="stylesheet" href="css/shared.css">
|
||||
<style>
|
||||
.outlined {
|
||||
-webkit-box-shadow: 0px 0px 2px 1px rgba(0,0,0,0.75);
|
||||
-moz-box-shadow: 0px 0px 2px 1px rgba(0,0,0,0.75);
|
||||
box-shadow: 0px 0px 2px 1px rgba(0,0,0,0.75);
|
||||
margin: 4px;
|
||||
}
|
||||
.logItem {
|
||||
font: normal 14px consolas, courier, monospace;
|
||||
white-space: pre;
|
||||
line-height: 125%;
|
||||
width: 100%;
|
||||
}
|
||||
.FINEST {
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
.FINER {
|
||||
background-color: #ECEFF1;
|
||||
}
|
||||
.FINE {
|
||||
background-color: #EFEBE9;
|
||||
}
|
||||
.CONFIG {
|
||||
background-color: #FFF3E0;
|
||||
}
|
||||
.INFO {
|
||||
background-color: #F1F8E9;
|
||||
}
|
||||
.WARNING {
|
||||
background-color: #FFE0B2;
|
||||
}
|
||||
.SEVERE {
|
||||
background-color: #FFCCBC;
|
||||
}
|
||||
.SHOUT {
|
||||
background-color: #FFCDD2;
|
||||
}
|
||||
#log {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
padding: 8px;
|
||||
}
|
||||
.fill {
|
||||
height: 80%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
<nav-bar>
|
||||
<top-nav-menu></top-nav-menu>
|
||||
<vm-nav-menu vm="{{ isolate.vm }}"></vm-nav-menu>
|
||||
<isolate-nav-menu isolate="{{ isolate }}"></isolate-nav-menu>
|
||||
<nav-menu link="{{ makeLink('/logging', isolate) }}" anchor="logging" last="{{ true }}"></nav-menu>
|
||||
<nav-refresh callback="{{ clear }}" label="Clear"></nav-refresh>
|
||||
</nav-bar>
|
||||
<div id="page" class="content-centered-big">
|
||||
<span>Show messages with severity <select id="severityLevelSelector" value="{{ severityLevel }}"></select> and higher</span>
|
||||
<hr>
|
||||
<div class="flex-row fill">
|
||||
<div id="log" class="outlined"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</polymer-element>
|
||||
|
||||
<script type="application/dart" src="logging.dart"></script>
|
|
@ -135,6 +135,13 @@ class Utils {
|
|||
return '${seconds}s';
|
||||
}
|
||||
|
||||
static String formatDateTime(DateTime now) {
|
||||
return '${now.year}-${now.month}-${now.day} '
|
||||
'${now.hour.toString().padLeft(2)}:'
|
||||
'${now.minute.toString().padLeft(2)}:'
|
||||
'${now.second.toString().padLeft(2)}';
|
||||
}
|
||||
|
||||
static String formatSeconds(double x) {
|
||||
return x.toStringAsFixed(2);
|
||||
}
|
||||
|
|
|
@ -103,6 +103,8 @@
|
|||
'lib/src/elements/library_ref.html',
|
||||
'lib/src/elements/library_view.dart',
|
||||
'lib/src/elements/library_view.html',
|
||||
'lib/src/elements/logging.dart',
|
||||
'lib/src/elements/logging.html',
|
||||
'lib/src/elements/metrics.dart',
|
||||
'lib/src/elements/metrics.html',
|
||||
'lib/src/elements/nav_bar.dart',
|
||||
|
|
29
runtime/observatory/tests/ui/log.dart
Normal file
29
runtime/observatory/tests/ui/log.dart
Normal file
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
// VMOptions=--compile_all --error_on_bad_type --error_on_bad_override
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:developer' as developer;
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
main() {
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((logRecord) {
|
||||
developer.log(
|
||||
sequenceNumber: logRecord.sequenceNumber,
|
||||
millisecondsSinceEpoch: logRecord.time.millisecondsSinceEpoch,
|
||||
level: logRecord.level.value,
|
||||
name: logRecord.loggerName,
|
||||
message: logRecord.message,
|
||||
zone: null,
|
||||
error: logRecord.error,
|
||||
stackTrace: logRecord.stackTrace);
|
||||
});
|
||||
new Timer.periodic(new Duration(seconds: 1), (t) {
|
||||
Logger.root.info('INFO MESSAGE');
|
||||
});
|
||||
new Timer.periodic(new Duration(seconds: 1), (t) {
|
||||
Logger.root.fine('FINE MESSAGE');
|
||||
});
|
||||
}
|
6
runtime/observatory/tests/ui/log.txt
Normal file
6
runtime/observatory/tests/ui/log.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
0. Run dart --observe log.dart
|
||||
1. Visit main isolate's logging page.
|
||||
2. You should see 'INFO MESSAGE' and 'FINE MESSAGE'.
|
||||
3. Adjust the level to be 'INFO' and see that 'FINE' messages are hidden.
|
||||
4. Adjust the level to be 'FINE' and see that all messages are displayed.
|
||||
5. Adjust the level to be 'SHOUT' and see that no messages are displayed.
|
Loading…
Reference in a new issue