blob: 8f263050e1657d803f9ba75e41e129b4c26b3959 [file] [log] [blame]
// 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);
for (var logRecord in pendingLogRecords) {
_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>();
}