Index: runtime/observatory/lib/src/elements/logging.dart |
diff --git a/runtime/observatory/lib/src/elements/logging.dart b/runtime/observatory/lib/src/elements/logging.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6e22aadb387e6dfeb6a25938b40d525e987a7b01 |
--- /dev/null |
+++ b/runtime/observatory/lib/src/elements/logging.dart |
@@ -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>(); |
+} |