blob: d0e58ab0b2620c8e7b48af15e34ea2105da2254f [file]
// Copyright (c) 2025, 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.
import 'package:analysis_server/src/session_logger/entry_keys.dart' as key;
import 'package:analysis_server/src/session_logger/entry_kind.dart';
import 'package:analysis_server/src/session_logger/log_entry.dart';
import 'package:analysis_server/src/session_logger/log_normalizer.dart';
import 'package:analysis_server/src/session_logger/process_id.dart';
import 'package:analysis_server/src/session_logger/session_logger_sink.dart';
/// Used to write information about a session to a log.
class SessionLogger {
/// The normalizer used to normalize paths in log entries.
final LogNormalizer normalizer;
/// The sink to which entries are to be written, or `null` if logging is not
/// to be done at this point.
final SessionLoggerSink? sink;
/// Instantiates a [SessionLogger] which writes log entries to a
/// [SessionLoggerInMemorySink].
///
/// If [filePath] is non-`null`, it also writes log entries to a file at
/// [filePath].
factory({String? filePath}) {
var normalizer = LogNormalizer();
var sink = SessionLoggerInMemorySink(
maxBufferLength: 1024,
normalizer: normalizer,
sessionLogFilePath: filePath,
);
return SessionLogger._(sink: sink, normalizer: normalizer);
}
new _({required this.sink, required this.normalizer});
/// Adds normalization replacements for the package roots.
///
/// The [packageRoots] maps package names to package root paths.
void addPackageRoots({
required int index,
required Map<String, Uri> packageRoots,
}) {
for (var MapEntry(key: packageName, value: uri) in packageRoots.entries) {
normalizer.addReplacementsForUri(
uri,
'context-$index:package-root:$packageName',
);
}
}
/// Log that the given [arguments] were included on the command-line.
void logCommandLine({required List<String> arguments}) {
sink?.writeLogEntry({
key.time: DateTime.now().millisecondsSinceEpoch,
key.kind: EntryKind.commandLine.name,
key.argList: arguments,
});
}
/// Log that the given [arguments] were included on the command-line.
void logException({
required Object exception,
StackTrace? stackTrace,
List<Object>? attachments,
}) {
// TODO(brianwilkerson): Provide special handling for `CaughtException` and
// `FatalException`. See implementations of
// `InstrumentationService.logException` for more details.
// TODO(brianwilkerson): `InstrumentationService` is defined in the
// `analyzer` package and is used in several places to report exceptions.
// Those eceptions are not currently being recorded. There are two possible
// paths:
// - move `SessionLogger` to the analyzer package
// - pass in an `InstrumentationService` that forwards exceptions to a
// session logger
sink?.writeLogEntry({
key.time: DateTime.now().millisecondsSinceEpoch,
key.kind: EntryKind.exception.name,
key.message: exception.toString(),
key.stackTrace: ?stackTrace?.toString(),
key.attachments: ?attachments,
});
}
/// Log unstructured text information for debugging purposes.
void logInfo(String message) {
sink?.writeLogEntry({
key.time: DateTime.now().millisecondsSinceEpoch,
key.kind: EntryKind.info.name,
key.message: message,
});
}
/// Logs that the given [message] was sent [from] one process [to] another.
void logMessage({
required ProcessId from,
required ProcessId to,
required JsonMap message,
}) {
if (from == ProcessId.ide && to == ProcessId.server) {
var msg = Message(message);
if (msg.isInitializeRequest) {
normalizer.addLspWorkspaceReplacements(msg);
}
}
sink?.writeLogEntry({
key.time: DateTime.now().millisecondsSinceEpoch,
key.kind: EntryKind.message.name,
key.sender: from.name,
key.receiver: to.name,
key.message: message,
});
}
/// Shuts down the logger.
Future<void> shutdown() async {
await sink?.close();
}
}