| Index: pkg/analysis_server/tool/instrumentation/log/log.dart
|
| diff --git a/pkg/analysis_server/tool/instrumentation/log/log.dart b/pkg/analysis_server/tool/instrumentation/log/log.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..7042fc467dd5e13f07f925411b9e1cd82f0d087b
|
| --- /dev/null
|
| +++ b/pkg/analysis_server/tool/instrumentation/log/log.dart
|
| @@ -0,0 +1,807 @@
|
| +// Copyright (c) 2016, 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.
|
| +
|
| +/**
|
| + * A representation of the contents of an instrumentation log.
|
| + */
|
| +library analysis_server.tool.instrumentation.log;
|
| +
|
| +import 'dart:collection';
|
| +import 'dart:convert';
|
| +
|
| +import 'package:analyzer/instrumentation/instrumentation.dart';
|
| +
|
| +/**
|
| + * A range of log entries, represented by the index of the first and last
|
| + * entries in the range.
|
| + */
|
| +class EntryRange {
|
| + /**
|
| + * The index of the first entry in the range.
|
| + */
|
| + int firstIndex;
|
| +
|
| + /**
|
| + * The index of the first entry in the range.
|
| + */
|
| + int lastIndex;
|
| +
|
| + /**
|
| + * Initialize a newly created range to represent the entries between the
|
| + * [firstIndex] and the [lastIndex], inclusive.
|
| + */
|
| + EntryRange(this.firstIndex, this.lastIndex);
|
| +}
|
| +
|
| +/**
|
| + * A log entry representing an Err entry.
|
| + */
|
| +class ErrorEntry extends GenericEntry {
|
| + /**
|
| + * Initialize a newly created log entry.
|
| + */
|
| + ErrorEntry(
|
| + int index, int timeStamp, String entryKind, List<String> components)
|
| + : super(index, timeStamp, entryKind, components);
|
| +}
|
| +
|
| +/**
|
| + * A log entry representing an Ex entry.
|
| + */
|
| +class ExceptionEntry extends GenericEntry {
|
| + /**
|
| + * Initialize a newly created log entry.
|
| + */
|
| + ExceptionEntry(
|
| + int index, int timeStamp, String entryKind, List<String> components)
|
| + : super(index, timeStamp, entryKind, components);
|
| +}
|
| +
|
| +/**
|
| + * A representation of a generic log entry.
|
| + */
|
| +class GenericEntry extends LogEntry {
|
| + /**
|
| + * The kind of the log entry.
|
| + */
|
| + String entryKind;
|
| +
|
| + /**
|
| + * The components in the entry that follow the time stamp and entry kind.
|
| + */
|
| + List<String> components;
|
| +
|
| + /**
|
| + * Initialize a newly created generic log entry to have the given [timeStamp],
|
| + * [entryKind] and list of [components]
|
| + */
|
| + GenericEntry(int index, int timeStamp, this.entryKind, this.components)
|
| + : super(index, timeStamp);
|
| +
|
| + @override
|
| + String get kind => entryKind;
|
| +
|
| + @override
|
| + void _appendDetails(StringBuffer buffer) {
|
| + super._appendDetails(buffer);
|
| + for (String component in components) {
|
| + buffer.write(component);
|
| + buffer.write('<br>');
|
| + }
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A representation of an instrumentation log.
|
| + */
|
| +class InstrumentationLog {
|
| + /**
|
| + * The paths of the log files containing the entries.
|
| + */
|
| + List<String> logFilePaths;
|
| +
|
| + /**
|
| + * The entries in the instrumentation log.
|
| + */
|
| + List<LogEntry> logEntries;
|
| +
|
| + /**
|
| + * The entries in the instrumentation log that are not instances of
|
| + * [TaskEntry].
|
| + */
|
| + List<LogEntry> nonTaskEntries;
|
| +
|
| + /**
|
| + * A table mapping entries that are paired with another entry to the entry
|
| + * with which they are paired.
|
| + */
|
| + Map<LogEntry, LogEntry> _pairedEntries = new HashMap<LogEntry, LogEntry>();
|
| +
|
| + /**
|
| + * A table mapping the id's of requests to the entry representing the request.
|
| + */
|
| + Map<String, RequestEntry> _requestMap = new HashMap<String, RequestEntry>();
|
| +
|
| + /**
|
| + * A table mapping the id's of responses to the entry representing the
|
| + * response.
|
| + */
|
| + Map<String, ResponseEntry> _responseMap =
|
| + new HashMap<String, ResponseEntry>();
|
| +
|
| + /**
|
| + * A table mapping the ids of completion events to the events with those ids.
|
| + */
|
| + Map<String, List<NotificationEntry>> _completionMap =
|
| + new HashMap<String, List<NotificationEntry>>();
|
| +
|
| + /**
|
| + * The ranges of entries that are between analysis start and analysis end
|
| + * notifications.
|
| + */
|
| + List<EntryRange> analysisRanges;
|
| +
|
| + /**
|
| + * Initialize a newly created instrumentation log by parsing each of the lines
|
| + * in the [logContent] into a separate entry. The log contents should be the
|
| + * contents of the files whose paths are in the given list of [logFilePaths].
|
| + */
|
| + InstrumentationLog(this.logFilePaths, List<String> logContent) {
|
| + _parseLogContent(logContent);
|
| + }
|
| +
|
| + /**
|
| + * Return a list of the completion events associated with the given [id].
|
| + */
|
| + List<NotificationEntry> completionEventsWithId(String id) =>
|
| + _completionMap[id];
|
| +
|
| + /**
|
| + * Return the entry that is paired with the given [entry], or `null` if there
|
| + * is no entry paired with it.
|
| + */
|
| + LogEntry pairedEntry(LogEntry entry) => _pairedEntries[entry];
|
| +
|
| + /**
|
| + * Return the response that corresponds to the given request.
|
| + */
|
| + RequestEntry requestFor(ResponseEntry entry) => _requestMap[entry.id];
|
| +
|
| + /**
|
| + * Return the response that corresponds to the given request.
|
| + */
|
| + ResponseEntry responseFor(RequestEntry entry) => _responseMap[entry.id];
|
| +
|
| + /**
|
| + * Return a list containing all of the task entries between the start of
|
| + * analysis notification at the given [startIndex] and the matching end of
|
| + * analysis notification (or the end of the log if the log does not contain a
|
| + * corresponding end notification.
|
| + */
|
| + List<TaskEntry> taskEntriesFor(int startIndex) {
|
| + List<TaskEntry> taskEntries = <TaskEntry>[];
|
| + NotificationEntry startEntry = nonTaskEntries[startIndex];
|
| + LogEntry endEntry = pairedEntry(startEntry);
|
| + int lastIndex = endEntry == null ? logEntries.length : endEntry.index;
|
| + for (int i = startEntry.index + 1; i < lastIndex; i++) {
|
| + LogEntry entry = logEntries[i];
|
| + if (entry is TaskEntry) {
|
| + taskEntries.add(entry);
|
| + }
|
| + }
|
| + return taskEntries;
|
| + }
|
| +
|
| + /**
|
| + * Merge any multi-line entries into a single line so that every element in
|
| + * the given [logContent] is a single entry.
|
| + */
|
| + void _mergeEntries(List<String> logContent) {
|
| + bool isStartOfEntry(String line) {
|
| + return line.startsWith(LogEntry.entryRegExp);
|
| + }
|
| +
|
| + String merge(String line, List<String> extraLines) {
|
| + StringBuffer buffer = new StringBuffer();
|
| + buffer.writeln(line);
|
| + for (String extraLine in extraLines) {
|
| + buffer.writeln(extraLine);
|
| + }
|
| + return buffer.toString();
|
| + }
|
| +
|
| + List<String> extraLines = <String>[];
|
| + for (int i = logContent.length - 1; i >= 0; i--) {
|
| + String line = logContent[i];
|
| + if (isStartOfEntry(line)) {
|
| + if (extraLines.isNotEmpty) {
|
| + logContent[i] = merge(line, extraLines);
|
| + }
|
| + } else {
|
| + logContent.removeAt(i);
|
| + extraLines.insert(0, line);
|
| + }
|
| + }
|
| + if (extraLines.isNotEmpty) {
|
| + throw new StateError(
|
| + '${extraLines.length} non-entry lines before any entry');
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Parse the given [logContent] into a list of log entries.
|
| + */
|
| + void _parseLogContent(List<String> logContent) {
|
| + _mergeEntries(logContent);
|
| + logEntries = <LogEntry>[];
|
| + nonTaskEntries = <LogEntry>[];
|
| + analysisRanges = <EntryRange>[];
|
| + NotificationEntry analysisStartEntry = null;
|
| + int analysisStartIndex = -1;
|
| + NotificationEntry pubStartEntry = null;
|
| + for (String line in logContent) {
|
| + LogEntry entry = new LogEntry.from(logEntries.length, line);
|
| + if (entry != null) {
|
| + logEntries.add(entry);
|
| + if (entry is! TaskEntry) {
|
| + nonTaskEntries.add(entry);
|
| + }
|
| + if (entry is RequestEntry) {
|
| + _requestMap[entry.id] = entry;
|
| + } else if (entry is ResponseEntry) {
|
| + _responseMap[entry.id] = entry;
|
| + RequestEntry request = _requestMap[entry.id];
|
| + _pairedEntries[entry] = request;
|
| + _pairedEntries[request] = entry;
|
| + } else if (entry is NotificationEntry) {
|
| + if (entry.isServerStatus) {
|
| + var analysisStatus = entry.param('analysis');
|
| + if (analysisStatus is Map) {
|
| + if (analysisStatus['isAnalyzing']) {
|
| + if (analysisStartEntry != null) {
|
| + analysisStartEntry.recordProblem(
|
| + 'Analysis started without being terminated.');
|
| + }
|
| + analysisStartEntry = entry;
|
| + analysisStartIndex = logEntries.length - 1;
|
| + } else {
|
| + if (analysisStartEntry == null) {
|
| + entry.recordProblem(
|
| + 'Analysis terminated without being started.');
|
| + } else {
|
| + int analysisEnd = logEntries.length - 1;
|
| + analysisRanges
|
| + .add(new EntryRange(analysisStartIndex, analysisEnd));
|
| + _pairedEntries[entry] = analysisStartEntry;
|
| + _pairedEntries[analysisStartEntry] = entry;
|
| + analysisStartEntry = null;
|
| + analysisStartIndex = -1;
|
| + }
|
| + }
|
| + }
|
| + var pubStatus = entry.param('pub');
|
| + if (pubStatus is Map) {
|
| + if (pubStatus['isListingPackageDirs']) {
|
| + if (pubStartEntry != null) {
|
| + pubStartEntry.recordProblem(
|
| + 'Pub started without previous being terminated.');
|
| + }
|
| + pubStartEntry = entry;
|
| + } else {
|
| + if (pubStartEntry == null) {
|
| + entry.recordProblem('Pub terminated without being started.');
|
| + } else {
|
| + _pairedEntries[entry] = pubStartEntry;
|
| + _pairedEntries[pubStartEntry] = entry;
|
| + pubStartEntry = null;
|
| + }
|
| + }
|
| + }
|
| + } else if (entry.event == 'completion.results') {
|
| + String id = entry.param('id');
|
| + if (id != null) {
|
| + _completionMap
|
| + .putIfAbsent(id, () => new List<NotificationEntry>())
|
| + .add(entry);
|
| + }
|
| + }
|
| + }
|
| + }
|
| + }
|
| + if (analysisStartEntry != null) {
|
| + analysisStartEntry
|
| + .recordProblem('Analysis started without being terminated.');
|
| + }
|
| + if (pubStartEntry != null) {
|
| + pubStartEntry
|
| + .recordProblem('Pub started without previous being terminated.');
|
| + }
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A log entry that has a single JSON encoded component following the time stamp
|
| + * and entry kind.
|
| + */
|
| +abstract class JsonBasedEntry extends LogEntry {
|
| + /**
|
| + * The HTML string used to indent text when formatting the JSON [data].
|
| + */
|
| + static const String singleIndent = ' ';
|
| +
|
| + /**
|
| + * The decoded form of the JSON encoded component.
|
| + */
|
| + final Map data;
|
| +
|
| + /**
|
| + * Initialize a newly created log entry to have the given [timeStamp] and
|
| + * [data].
|
| + */
|
| + JsonBasedEntry(int index, int timeStamp, this.data) : super(index, timeStamp);
|
| +
|
| + @override
|
| + void _appendDetails(StringBuffer buffer) {
|
| + super._appendDetails(buffer);
|
| + _format(buffer, '', data);
|
| + }
|
| +
|
| + /**
|
| + * Encode any character in the given [string] that would prevent source code
|
| + * from being displayed correctly: end of line markers and spaces.
|
| + */
|
| + String _encodeSourceCode(String string) {
|
| + // TODO(brianwilkerson) This method isn't working completely. Some source
|
| + // code produces an error of
|
| + // "log?start=3175:261 Uncaught SyntaxError: missing ) after argument list"
|
| + // in the sample log I was using.
|
| + StringBuffer buffer = new StringBuffer();
|
| + int length = string.length;
|
| + int index = 0;
|
| + while (index < length) {
|
| + int char = string.codeUnitAt(index);
|
| + index++;
|
| + // TODO(brianwilkerson) Handle tabs and other special characters.
|
| + if (char == '\r'.codeUnitAt(0)) {
|
| + if (index < length && string.codeUnitAt(index) == '\n'.codeUnitAt(0)) {
|
| + index++;
|
| + }
|
| + buffer.write('<br>');
|
| + } else if (char == '\n'.codeUnitAt(0)) {
|
| + buffer.write('<br>');
|
| + } else if (char == ' '.codeUnitAt(0)) {
|
| + // Encode all spaces in order to accurately reproduce the original
|
| + // source code when displaying it.
|
| + buffer.write(' ');
|
| + } else {
|
| + buffer.writeCharCode(char);
|
| + }
|
| + }
|
| + return buffer.toString();
|
| + }
|
| +
|
| + /**
|
| + * Write an HTML representation the given JSON [object] to the given [buffer],
|
| + * using the given [indent] to make the output more readable.
|
| + */
|
| + void _format(StringBuffer buffer, String indent, Object object) {
|
| + if (object is String) {
|
| + buffer.write('"');
|
| + buffer.write(_encodeSourceCode(object));
|
| + buffer.write('"');
|
| + } else if (object is int || object is bool) {
|
| + buffer.write(object);
|
| + } else if (object is Map) {
|
| + buffer.write('{<br>');
|
| + object.forEach((Object key, Object value) {
|
| + String newIndent = indent + singleIndent;
|
| + buffer.write(newIndent);
|
| + _format(buffer, newIndent, key);
|
| + buffer.write(' : ');
|
| + _format(buffer, newIndent, value);
|
| + buffer.write('<br>');
|
| + });
|
| + buffer.write(indent);
|
| + buffer.write('}');
|
| + } else if (object is List) {
|
| + buffer.write('[<br>');
|
| + object.forEach((Object element) {
|
| + String newIndent = indent + singleIndent;
|
| + buffer.write(newIndent);
|
| + _format(buffer, newIndent, element);
|
| + buffer.write('<br>');
|
| + });
|
| + buffer.write(indent);
|
| + buffer.write(']');
|
| + }
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A single entry in an instrumentation log.
|
| + */
|
| +abstract class LogEntry {
|
| + /**
|
| + * The character used to separate fields within an entry.
|
| + */
|
| + static final int fieldSeparator = ':'.codeUnitAt(0);
|
| +
|
| + /**
|
| + * A regular expression that will match the beginning of a valid log entry.
|
| + */
|
| + static final RegExp entryRegExp = new RegExp('[0-9]+\\:');
|
| +
|
| + /**
|
| + * A table mapping kinds to the names of those kinds.
|
| + */
|
| + static final Map<String, String> kindMap = {
|
| + 'Err': 'Error',
|
| + 'Ex': 'Exception',
|
| + 'Log': 'Log message',
|
| + 'Noti': 'Notification',
|
| + 'Read': 'Read file',
|
| + 'Req': 'Request',
|
| + 'Res': 'Response',
|
| + 'Perf': 'Performance data',
|
| + 'SPResult': 'Subprocess result',
|
| + 'SPStart': 'Subprocess start',
|
| + 'Task': 'Task',
|
| + 'Ver': 'Version information',
|
| + 'Watch': 'Watch event',
|
| + };
|
| +
|
| + /**
|
| + * The index of this entry in the log file.
|
| + */
|
| + final int index;
|
| +
|
| + /**
|
| + * The time at which the entry occurred.
|
| + */
|
| + final int timeStamp;
|
| +
|
| + /**
|
| + * A list containing the descriptions of problems that were found while
|
| + * processing the log file, or `null` if no problems were found.
|
| + */
|
| + List<String> _problems = null;
|
| +
|
| + /**
|
| + * Initialize a newly created log entry with the given [timeStamp].
|
| + */
|
| + LogEntry(this.index, this.timeStamp);
|
| +
|
| + /**
|
| + * Create a log entry from the given encoded form of the [entry].
|
| + */
|
| + factory LogEntry.from(int index, String entry) {
|
| + if (entry.isEmpty) {
|
| + return null;
|
| + }
|
| + List<String> components = _parseComponents(entry);
|
| + int timeStamp;
|
| + try {
|
| + timeStamp = int.parse(components[0]);
|
| + } catch (exception) {
|
| + print('Invalid time stamp in "${components[0]}"; entry = "$entry"');
|
| + return null;
|
| + }
|
| + String entryKind = components[1];
|
| + if (entryKind == InstrumentationService.TAG_ANALYSIS_TASK) {
|
| + return new TaskEntry(index, timeStamp, components[2], components[3]);
|
| + } else if (entryKind == InstrumentationService.TAG_ERROR) {
|
| + return new ErrorEntry(index, timeStamp, entryKind, components.sublist(2));
|
| + } else if (entryKind == InstrumentationService.TAG_EXCEPTION) {
|
| + return new ExceptionEntry(
|
| + index, timeStamp, entryKind, components.sublist(2));
|
| + } else if (entryKind == InstrumentationService.TAG_FILE_READ) {
|
| + // Fall through
|
| + } else if (entryKind == InstrumentationService.TAG_LOG_ENTRY) {
|
| + // Fall through
|
| + } else if (entryKind == InstrumentationService.TAG_NOTIFICATION) {
|
| + Map requestData = JSON.decode(components[2]);
|
| + return new NotificationEntry(index, timeStamp, requestData);
|
| + } else if (entryKind == InstrumentationService.TAG_PERFORMANCE) {
|
| + // Fall through
|
| + } else if (entryKind == InstrumentationService.TAG_REQUEST) {
|
| + Map requestData = JSON.decode(components[2]);
|
| + return new RequestEntry(index, timeStamp, requestData);
|
| + } else if (entryKind == InstrumentationService.TAG_RESPONSE) {
|
| + Map responseData = JSON.decode(components[2]);
|
| + return new ResponseEntry(index, timeStamp, responseData);
|
| + } else if (entryKind == InstrumentationService.TAG_SUBPROCESS_START) {
|
| + // Fall through
|
| + } else if (entryKind == InstrumentationService.TAG_SUBPROCESS_RESULT) {
|
| + // Fall through
|
| + } else if (entryKind == InstrumentationService.TAG_VERSION) {
|
| + // Fall through
|
| + } else if (entryKind == InstrumentationService.TAG_WATCH_EVENT) {
|
| + // Fall through
|
| + }
|
| + return new GenericEntry(index, timeStamp, entryKind, components.sublist(2));
|
| + }
|
| +
|
| + /**
|
| + * Return `true` if any problems were found while processing the log file.
|
| + */
|
| + bool get hasProblems => _problems != null;
|
| +
|
| + /**
|
| + * Return the value of the component used to indicate the kind of the entry.
|
| + * This is the abbreviation recorded in the entry.
|
| + */
|
| + String get kind;
|
| +
|
| + /**
|
| + * Return a human-readable representation of the kind of this entry.
|
| + */
|
| + String get kindName => kindMap[kind] ?? kind;
|
| +
|
| + /**
|
| + * Return a list containing the descriptions of problems that were found while
|
| + * processing the log file, or `null` if no problems were found.
|
| + */
|
| + List<String> get problems => _problems;
|
| +
|
| + /**
|
| + * Return a date that is equivalent to the [timeStamp].
|
| + */
|
| + DateTime get toTime => new DateTime.fromMillisecondsSinceEpoch(timeStamp);
|
| +
|
| + /**
|
| + * Return an HTML representation of the details of the entry.
|
| + */
|
| + String details() {
|
| + StringBuffer buffer = new StringBuffer();
|
| + _appendDetails(buffer);
|
| + return buffer.toString();
|
| + }
|
| +
|
| + /**
|
| + * Record that the given [problem] was found while processing the log file.
|
| + */
|
| + void recordProblem(String problem) {
|
| + _problems ??= <String>[];
|
| + _problems.add(problem);
|
| + }
|
| +
|
| + /**
|
| + * Append details related to this entry to the given [buffer].
|
| + */
|
| + void _appendDetails(StringBuffer buffer) {
|
| + if (_problems != null) {
|
| + for (String problem in _problems) {
|
| + buffer.write('<p><span class="error">$problem</span></p>');
|
| + }
|
| + }
|
| + }
|
| +
|
| + /**
|
| + * Parse the given encoded form of the [entry] into a list of components. The
|
| + * first component is always the time stamp for when the entry was generated.
|
| + * The second component is always the kind of the entry. The remaining
|
| + * components depend on the kind of the entry. Return the components that were
|
| + * parsed.
|
| + */
|
| + static List<String> _parseComponents(String entry) {
|
| + List<String> components = <String>[];
|
| + StringBuffer component = new StringBuffer();
|
| + int length = entry.length;
|
| + for (int i = 0; i < length; i++) {
|
| + int char = entry.codeUnitAt(i);
|
| + if (char == fieldSeparator) {
|
| + if (entry.codeUnitAt(i + 1) == fieldSeparator) {
|
| + component.write(':');
|
| + i++;
|
| + } else {
|
| + components.add(component.toString());
|
| + component.clear();
|
| + }
|
| + } else {
|
| + component.writeCharCode(char);
|
| + }
|
| + }
|
| + components.add(component.toString());
|
| + return components;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A log entry representing a notification that was sent from the server to the
|
| + * client.
|
| + */
|
| +class NotificationEntry extends JsonBasedEntry {
|
| + /**
|
| + * Initialize a newly created response to have the given [timeStamp] and
|
| + * [notificationData].
|
| + */
|
| + NotificationEntry(int index, int timeStamp, Map notificationData)
|
| + : super(index, timeStamp, notificationData);
|
| +
|
| + /**
|
| + * Return the event field of the request.
|
| + */
|
| + String get event => data['event'];
|
| +
|
| + /**
|
| + * Return `true` if this is a server status notification.
|
| + */
|
| + bool get isServerStatus => event == 'server.status';
|
| +
|
| + @override
|
| + String get kind => 'Noti';
|
| +
|
| + /**
|
| + * Return the value of the parameter with the given [parameterName], or `null`
|
| + * if there is no such parameter.
|
| + */
|
| + dynamic param(String parameterName) {
|
| + var parameters = data['params'];
|
| + if (parameters is Map) {
|
| + return parameters[parameterName];
|
| + }
|
| + return null;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A log entry representing a request that was sent from the client to the
|
| + * server.
|
| + */
|
| +class RequestEntry extends JsonBasedEntry {
|
| + /**
|
| + * Initialize a newly created response to have the given [timeStamp] and
|
| + * [requestData].
|
| + */
|
| + RequestEntry(int index, int timeStamp, Map requestData)
|
| + : super(index, timeStamp, requestData);
|
| +
|
| + /**
|
| + * Return the clientRequestTime field of the request.
|
| + */
|
| + int get clientRequestTime => data['clientRequestTime'];
|
| +
|
| + /**
|
| + * Return the id field of the request.
|
| + */
|
| + String get id => data['id'];
|
| +
|
| + @override
|
| + String get kind => 'Req';
|
| +
|
| + /**
|
| + * Return the method field of the request.
|
| + */
|
| + String get method => data['method'];
|
| +
|
| + /**
|
| + * Return the value of the parameter with the given [parameterName], or `null`
|
| + * if there is no such parameter.
|
| + */
|
| + dynamic param(String parameterName) {
|
| + var parameters = data['params'];
|
| + if (parameters is Map) {
|
| + return parameters[parameterName];
|
| + }
|
| + return null;
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * A log entry representing a response that was sent from the server to the
|
| + * client.
|
| + */
|
| +class ResponseEntry extends JsonBasedEntry {
|
| + /**
|
| + * Initialize a newly created response to have the given [timeStamp] and
|
| + * [responseData].
|
| + */
|
| + ResponseEntry(int index, int timeStamp, Map responseData)
|
| + : super(index, timeStamp, responseData);
|
| +
|
| + /**
|
| + * Return the id field of the response.
|
| + */
|
| + String get id => data['id'];
|
| +
|
| + @override
|
| + String get kind => 'Res';
|
| +
|
| + /**
|
| + * Return the value of the result with the given [resultName], or `null` if
|
| + * there is no such result.
|
| + */
|
| + dynamic result(String resultName) {
|
| + var results = data['result'];
|
| + if (results is Map) {
|
| + return results[resultName];
|
| + }
|
| + return null;
|
| + }
|
| +}
|
| +
|
| +class TaskEntry extends LogEntry {
|
| + /**
|
| + * The path to the directory at the root of the context in which analysis was
|
| + * being performed.
|
| + */
|
| + final String context;
|
| +
|
| + /**
|
| + * A description of the task that was performed.
|
| + */
|
| + final String description;
|
| +
|
| + /**
|
| + * The name of the class implementing the task.
|
| + */
|
| + String _taskName = null;
|
| +
|
| + /**
|
| + * The description of the target of the task.
|
| + */
|
| + String _target = null;
|
| +
|
| + /**
|
| + * Initialize a newly created entry with the given [index] and [timeStamp] to
|
| + * represent the execution of an analysis task in the given [context] that is
|
| + * described by the given [description].
|
| + */
|
| + TaskEntry(int index, int timeStamp, this.context, this.description)
|
| + : super(index, timeStamp);
|
| +
|
| + @override
|
| + String get kind => 'Task';
|
| +
|
| + /**
|
| + * Return the description of the target of the task.
|
| + */
|
| + String get target {
|
| + if (_target == null) {
|
| + _splitDescription();
|
| + }
|
| + return _target;
|
| + }
|
| +
|
| + /**
|
| + * Return the name of the class implementing the task.
|
| + */
|
| + String get taskName {
|
| + if (_taskName == null) {
|
| + _splitDescription();
|
| + }
|
| + return _taskName;
|
| + }
|
| +
|
| + @override
|
| + void _appendDetails(StringBuffer buffer) {
|
| + super._appendDetails(buffer);
|
| + buffer.write('<span class="label">Context:</span> ');
|
| + buffer.write(context);
|
| + buffer.write('<br><span class="label">Description: </span> ');
|
| + buffer.write(description);
|
| + }
|
| +
|
| + /**
|
| + * Split the description to get the task name and target description.
|
| + */
|
| + void _splitDescription() {
|
| + int index = description.indexOf(' ');
|
| + if (index < 0) {
|
| + _taskName = '';
|
| + } else {
|
| + _taskName = description.substring(0, index);
|
| + }
|
| + index = description.lastIndexOf(' ');
|
| + _target = description.substring(index + 1);
|
| + int slash = context.lastIndexOf('/');
|
| + if (slash < 0) {
|
| + slash = context.lastIndexOf('\\');
|
| + }
|
| + if (slash >= 0) {
|
| + String prefix = context.substring(0, slash);
|
| + _target = _target.replaceAll(prefix, '...');
|
| + }
|
| + }
|
| +}
|
|
|