blob: 011581a5b50348d095a952e06a3fa3749cddd043 [file] [log] [blame]
// 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.
*/
import 'dart:convert';
import 'dart:math' as math;
import 'package:analyzer/instrumentation/instrumentation.dart';
import 'package:path/path.dart' as path;
/**
* A boolean-valued function of one argument.
*/
typedef bool Predicate<T>(T value);
/**
* A description of a group of log entries.
*/
class EntryGroup {
/**
* A list of all of the instances of this class.
*/
static final List<EntryGroup> groups = <EntryGroup>[
new EntryGroup._(
'nonTask', 'Non-task', (LogEntry entry) => entry is! TaskEntry),
new EntryGroup._(
'errors',
'Errors',
(LogEntry entry) =>
entry is ErrorEntry ||
entry is ExceptionEntry ||
(entry is NotificationEntry && entry.isServerError)),
new EntryGroup._('malformed', 'Malformed',
(LogEntry entry) => entry is MalformedLogEntry),
new EntryGroup._('all', 'All', (LogEntry entry) => true),
];
/**
* The unique id of the group.
*/
final String id;
/**
* The human-readable name of the group.
*/
final String name;
/**
* The filter used to determine which entries belong to the group. The filter
* should return `true` for members and `false` for non-members.
*/
final Predicate<LogEntry> filter;
/**
* Initialize a newly created entry group with the given state.
*/
EntryGroup._(this.id, this.name, this.filter);
/**
* Given a list of [entries], return all of the entries in the list that are
* members of this group.
*/
List<LogEntry> computeMembers(List<LogEntry> entries) {
return entries.where(filter).toList();
}
/**
* Return the entry group with the given [id], or `null` if there is no group
* with the given id.
*/
static EntryGroup withId(String id) {
for (EntryGroup group in groups) {
if (group.id == id) {
return group;
}
}
return null;
}
}
/**
* 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 log entry representing an PluginErr entry.
*/
class GenericPluginEntry extends GenericEntry with PluginEntryMixin {
/**
* The components describing the plugin associated with this entry.
*/
final List<String> pluginData;
/**
* Initialize a newly created log entry.
*/
GenericPluginEntry(int index, int timeStamp, String entryKind,
List<String> components, this.pluginData)
: super(index, timeStamp, entryKind, components);
}
/**
* 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;
/**
* A table mapping the entry groups that have been computed to the list of
* entries in that group.
*/
Map<EntryGroup, List<LogEntry>> entryGroups = <EntryGroup, List<LogEntry>>{};
/**
* A table mapping entries that are paired with another entry to the entry
* with which they are paired.
*/
Map<LogEntry, LogEntry> _pairedEntries = <LogEntry, LogEntry>{};
/**
* A table mapping the id's of requests to the entry representing the request.
*/
Map<String, RequestEntry> _requestMap = <String, RequestEntry>{};
/**
* A table mapping the id's of plugin requests to the entry representing the
* request.
*/
Map<String, PluginRequestEntry> _pluginRequestMap =
<String, PluginRequestEntry>{};
/**
* A table mapping the id's of responses to the entry representing the
* response.
*/
Map<String, ResponseEntry> _responseMap = <String, ResponseEntry>{};
/**
* A table mapping the id's of plugin responses to the entry representing the
* response.
*/
Map<String, PluginResponseEntry> _pluginResponseMap =
<String, PluginResponseEntry>{};
/**
* A table mapping the ids of completion events to the events with those ids.
*/
Map<String, List<NotificationEntry>> _completionMap =
<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 log entries that are contained in the given [group].
*/
List<LogEntry> entriesInGroup(EntryGroup group) =>
entryGroups.putIfAbsent(group, () => group.computeMembers(logEntries));
/**
* 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 plugin request.
*/
PluginRequestEntry pluginRequestFor(PluginResponseEntry entry) =>
_pluginRequestMap[entry.id];
/**
* Return the response that corresponds to the given request.
*/
PluginResponseEntry pluginResponseFor(PluginRequestEntry entry) =>
_pluginResponseMap[entry.id];
/**
* 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 = logEntries[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;
}
/**
* Return `true` if the given [logContent] appears to be from session data.
*/
bool _isSessionData(List<String> logContent) {
if (logContent.length < 2) {
return false;
}
String firstLine = logContent[0];
return firstLine.startsWith('-----') && logContent[1].startsWith('~') ||
firstLine.startsWith('~');
}
/**
* 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);
}
extraLines.clear();
} else {
logContent.removeAt(i);
extraLines.insert(0, line);
}
}
if (extraLines.isNotEmpty) {
int count = math.min(extraLines.length, 10);
StringBuffer buffer = new StringBuffer();
buffer.writeln('${extraLines.length} non-entry lines before any entry');
buffer.writeln('First $count lines:');
for (int i = 0; i < count; i++) {
buffer.writeln(extraLines[i]);
}
throw new StateError(buffer.toString());
}
}
/**
* Parse the given [logContent] into a list of log entries.
*/
void _parseLogContent(List<String> logContent) {
if (_isSessionData(logContent)) {
if (logContent[0].startsWith('-----')) {
logContent.removeAt(0);
}
int lastIndex = logContent.length - 1;
if (logContent[lastIndex].startsWith('extraction complete')) {
logContent.removeAt(lastIndex);
}
} else {
_mergeEntries(logContent);
}
logEntries = <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 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);
}
}
} else if (entry is PluginRequestEntry) {
_pluginRequestMap[entry.id] = entry;
} else if (entry is PluginResponseEntry) {
_pluginResponseMap[entry.id] = entry;
PluginRequestEntry request = _pluginRequestMap[entry.id];
_pairedEntries[entry] = request;
_pairedEntries[request] = 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 = '&nbsp;&nbsp;&nbsp;';
/**
* 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('&nbsp;');
} 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 log entry representing a communication between the server and a plugin.
*/
abstract class JsonBasedPluginEntry extends JsonBasedEntry
with PluginEntryMixin {
/**
* The components describing the plugin associated with this entry.
*/
final List<String> pluginData;
/**
* Initialize a newly created entry to have the given [timeStamp] and
* [notificationData] and to be associated with the plugin with the given
* [pluginData].
*/
JsonBasedPluginEntry(
int index, int timeStamp, Map notificationData, this.pluginData)
: super(index, timeStamp, notificationData);
}
/**
* 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',
'Mal': 'Malformed entry',
'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;
}
try {
List<String> components = _parseComponents(entry);
int timeStamp;
String component = components[0];
if (component.startsWith('~')) {
component = component.substring(1);
}
timeStamp = int.parse(component);
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_PLUGIN_ERROR) {
return new PluginErrorEntry(index, timeStamp, entryKind,
components.sublist(2, 4), components.sublist(4));
} else if (entryKind == InstrumentationService.TAG_PLUGIN_EXCEPTION) {
return new PluginExceptionEntry(index, timeStamp, entryKind,
components.sublist(2, 5), components.sublist(5));
} else if (entryKind == InstrumentationService.TAG_PLUGIN_NOTIFICATION) {
Map requestData = json.decode(components[2]);
return new PluginNotificationEntry(
index, timeStamp, requestData, components.sublist(3));
} else if (entryKind == InstrumentationService.TAG_PLUGIN_REQUEST) {
Map requestData = json.decode(components[2]);
return new PluginRequestEntry(
index, timeStamp, requestData, components.sublist(3));
} else if (entryKind == InstrumentationService.TAG_PLUGIN_RESPONSE) {
Map responseData = json.decode(components[2]);
return new PluginResponseEntry(
index, timeStamp, responseData, components.sublist(3));
} else if (entryKind == InstrumentationService.TAG_PLUGIN_TIMEOUT) {
return new PluginErrorEntry(index, timeStamp, entryKind,
components.sublist(2, 3), components.sublist(3));
} 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));
} catch (exception) {
LogEntry logEntry = new MalformedLogEntry(index, entry);
logEntry.recordProblem(exception.toString());
return logEntry;
}
}
/**
* 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 representation of a malformed log entry.
*/
class MalformedLogEntry extends LogEntry {
final String entry;
MalformedLogEntry(int index, this.entry) : super(index, -1);
@override
String get kind => 'Mal';
@override
void _appendDetails(StringBuffer buffer) {
super._appendDetails(buffer);
buffer.write(entry);
buffer.write('<br>');
}
}
/**
* 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 error notification.
*/
bool get isServerError => event == 'server.error';
/**
* 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 communication between the server and a plugin.
*/
abstract class PluginEntryMixin {
/**
* The components describing the plugin associated with this entry.
*/
List<String> get pluginData;
/**
* The id of the plugin associated with this entry.
*/
String get pluginId => pluginData[0];
/**
* The name of the plugin associated with this entry.
*/
String get pluginName => pluginData[1];
/**
* The version of the plugin associated with this entry.
*/
String get pluginVersion => pluginData[2];
/**
* Return a shortened version of the plugin id.
*/
String get shortPluginId {
int index = pluginId.lastIndexOf(path.separator);
if (index > 0) {
return pluginId.substring(index + 1);
}
return pluginId;
}
}
/**
* A log entry representing an PluginErr entry.
*/
class PluginErrorEntry extends GenericPluginEntry {
/**
* Initialize a newly created log entry.
*/
PluginErrorEntry(int index, int timeStamp, String entryKind,
List<String> components, List<String> pluginData)
: super(index, timeStamp, entryKind, components, pluginData);
}
/**
* A log entry representing an PluginEx entry.
*/
class PluginExceptionEntry extends GenericPluginEntry {
/**
* Initialize a newly created log entry.
*/
PluginExceptionEntry(int index, int timeStamp, String entryKind,
List<String> components, List<String> pluginData)
: super(index, timeStamp, entryKind, components, pluginData);
}
/**
* A log entry representing a notification that was sent from a plugin to the
* server.
*/
class PluginNotificationEntry extends JsonBasedPluginEntry {
/**
* Initialize a newly created notification to have the given [timeStamp] and
* [notificationData].
*/
PluginNotificationEntry(
int index, int timeStamp, Map notificationData, List<String> pluginData)
: super(index, timeStamp, notificationData, pluginData);
/**
* Return the event field of the notification.
*/
String get event => data['event'];
@override
String get kind => 'PluginNoti';
/**
* 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 server to a plugin.
*/
class PluginRequestEntry extends JsonBasedPluginEntry {
/**
* Initialize a newly created response to have the given [timeStamp] and
* [requestData].
*/
PluginRequestEntry(
int index, int timeStamp, Map requestData, List<String> pluginData)
: super(index, timeStamp, requestData, pluginData);
/**
* Return the id field of the request.
*/
String get id => data['id'];
@override
String get kind => 'PluginReq';
/**
* 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 a plugin to the
* server.
*/
class PluginResponseEntry extends JsonBasedPluginEntry {
/**
* Initialize a newly created response to have the given [timeStamp] and
* [responseData].
*/
PluginResponseEntry(
int index, int timeStamp, Map responseData, List<String> pluginData)
: super(index, timeStamp, responseData, pluginData);
/**
* Return the id field of the response.
*/
String get id => data['id'];
@override
String get kind => 'PluginRes';
/**
* 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;
}
}
/**
* 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, '...');
}
}
}