blob: 9a04a1960359a6ef553607186f2be83c61a1db3d [file] [log] [blame]
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'dart:convert';
import 'package:meta/meta.dart';
import 'package:stack_trace/stack_trace.dart';
import 'log_sink.dart';
import 'severity.dart';
/// An implementation of [LogSink] that records [messages] in memory.
final class BufferedLogger with LogSink {
/// Creates a new emtpy buffered logger.
factory BufferedLogger() = BufferedLogger.forTesting;
@visibleForTesting
BufferedLogger.forTesting({DateTime Function() now = DateTime.now})
: _now = now;
final DateTime Function() _now;
/// Messages that were recorded as a result of [log], in order of invocation.
///
/// To clear the buffer, use [clear].
late final Iterable<LogRecord> messages = UnmodifiableListView(_messages);
final _messages = <LogRecord>[];
/// Clear the buffer.
void clear() {
_messages.clear();
}
@override
void log(
String message, {
Severity severity = Severity.info,
Object? error,
StackTrace? trace,
}) {
_messages.add(
StringLogRecord._(
message,
severity: severity,
error: error,
trace: trace,
recordedAt: _now(),
),
);
}
@override
void logJson(
Object? message, {
Severity severity = Severity.info,
Object? error,
StackTrace? trace,
}) {
_messages.add(
JsonLogRecord._(
message,
severity: severity,
error: error,
trace: trace,
recordedAt: _now(),
),
);
}
@override
String toString() {
return 'BufferedLogger ${const JsonEncoder.withIndent(' ').convert(_messages)}';
}
}
/// A struct that contains the parameters when invoking [BufferedLogger.log].
///
/// If [BufferedLogger.logJson] was invoked, [JsonLogRecord] is the subtype.
@immutable
sealed class LogRecord {
LogRecord({
required this.severity,
required this.error,
required this.trace,
required this.recordedAt,
});
/// The message, either a [String] or a JSON-encodable object.
///
/// To distinguish, pattern match to [StringLogRecord] or [JsonLogRecord].
Object? get message;
/// The severity of the message.
final Severity severity;
/// The error provided with the log, or `null` if omitted.
final Object? error;
/// The stack trace provided with the log, or `null` if omitted.
final StackTrace? trace;
/// The time and date when the log was recorded.
final DateTime recordedAt;
@override
@nonVirtual
bool operator ==(Object other) {
return other is LogRecord &&
message == other.message &&
severity == other.severity &&
error == other.error &&
trace == other.trace &&
recordedAt == other.recordedAt;
}
@override
@nonVirtual
int get hashCode {
return Object.hash(message, severity, error, trace, recordedAt);
}
@override
@nonVirtual
String toString() {
return 'LogRecord ${const JsonEncoder.withIndent(' ').convert(toJson())}';
}
/// Returns a JSON object representation of the log record.
@nonVirtual
Map<String, Object?> toJson() {
return {
'message': message,
'severity': severity.name,
'recordedAt': recordedAt.toIso8601String(),
if (error case final error?) 'error': '$error',
if (trace case final trace?)
'trace': [...Trace.from(trace).frames.map((f) => '$f')],
};
}
}
/// A [LogRecord] that was invoked as a result of [BufferedLogger.log].
final class StringLogRecord extends LogRecord {
StringLogRecord._(
this.message, {
required super.severity,
required super.error,
required super.trace,
required super.recordedAt,
});
@override
final String message;
}
///
final class JsonLogRecord extends LogRecord {
JsonLogRecord._(
this.message, {
required super.severity,
required super.error,
required super.trace,
required super.recordedAt,
});
@override
final Object? message;
}