// Copyright (c) 2019, 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:analyzer/instrumentation/logger.dart'; import 'package:analyzer/instrumentation/plugin_data.dart'; import 'package:analyzer/instrumentation/service.dart'; /// A class to adapt an [InstrumentationService] into a log using an [InstrumentationLogger]. class InstrumentationLogAdapter implements InstrumentationService { static const String TAG_ERROR = 'Err'; static const String TAG_EXCEPTION = 'Ex'; static const String TAG_INFO = 'Info'; static const String TAG_LOG_ENTRY = 'Log'; static const String TAG_NOTIFICATION = 'Noti'; static const String TAG_PLUGIN_ERROR = 'PluginErr'; static const String TAG_PLUGIN_EXCEPTION = 'PluginEx'; static const String TAG_PLUGIN_NOTIFICATION = 'PluginNoti'; static const String TAG_PLUGIN_REQUEST = 'PluginReq'; static const String TAG_PLUGIN_RESPONSE = 'PluginRes'; static const String TAG_PLUGIN_TIMEOUT = 'PluginTo'; static const String TAG_REQUEST = 'Req'; static const String TAG_RESPONSE = 'Res'; static const String TAG_VERSION = 'Ver'; static const String TAG_WATCH_EVENT = 'Watch'; /// A logger used to log instrumentation in string format. final InstrumentationLogger _instrumentationLogger; /// Files that should not have their watch events logged (to prevent feedback /// loops). final Set? _watchEventExclusionFiles; /// Initialize a newly created instrumentation service to communicate with the /// given [_instrumentationLogger]. /// /// Any file paths in [watchEventExclusionFiles] will be excluded from the /// logging of watch events (to prevent feedback loops). InstrumentationLogAdapter( this._instrumentationLogger, { Set? watchEventExclusionFiles, }) : _watchEventExclusionFiles = watchEventExclusionFiles; /// The current time, expressed as a decimal encoded number of milliseconds. String get _timestamp => DateTime.now().millisecondsSinceEpoch.toString(); @override void logError(String message) => _log(TAG_ERROR, message); @override void logException( dynamic exception, [ StackTrace? stackTrace, List? attachments, ]) { String message = _toString(exception); String trace = _toString(stackTrace); _instrumentationLogger.log(_join([TAG_EXCEPTION, message, trace])); } @override void logInfo(String message, [dynamic exception]) => _log(TAG_INFO, message + (exception == null ? "" : exception.toString())); @override void logLogEntry( String level, DateTime? time, String message, Object exception, StackTrace stackTrace, ) { String timeStamp = time == null ? 'null' : time.millisecondsSinceEpoch.toString(); String exceptionText = exception.toString(); String stackTraceText = stackTrace.toString(); _instrumentationLogger.log( _join([ TAG_LOG_ENTRY, level, timeStamp, message, exceptionText, stackTraceText, ]), ); } @override void logNotification(String notification) => _log(TAG_NOTIFICATION, notification); @override void logPluginError( PluginData plugin, String code, String message, String stackTrace, ) { List fields = [TAG_PLUGIN_ERROR, code, message, stackTrace]; plugin.addToFields(fields); _instrumentationLogger.log(_join(fields)); } @override void logPluginException( PluginData plugin, dynamic exception, StackTrace? stackTrace, ) { List fields = [ TAG_PLUGIN_EXCEPTION, _toString(exception), _toString(stackTrace), ]; plugin.addToFields(fields); _instrumentationLogger.log(_join(fields)); } @override void logPluginNotification(String pluginId, String notification) { _instrumentationLogger.log( _join([TAG_PLUGIN_NOTIFICATION, notification, pluginId, '', '']), ); } @override void logPluginRequest(String pluginId, String request) { _instrumentationLogger.log( _join([TAG_PLUGIN_REQUEST, request, pluginId, '', '']), ); } @override void logPluginResponse(String pluginId, String response) { _instrumentationLogger.log( _join([TAG_PLUGIN_RESPONSE, response, pluginId, '', '']), ); } @override void logPluginTimeout(PluginData plugin, String request) { List fields = [TAG_PLUGIN_TIMEOUT, request]; plugin.addToFields(fields); _instrumentationLogger.log(_join(fields)); } @override void logRequest(String request) => _log(TAG_REQUEST, request); @override void logResponse(String response) => _log(TAG_RESPONSE, response); @override void logVersion( String uuid, String clientId, String clientVersion, String serverVersion, String sdkVersion, ) { String normalize(String? value) => value != null && value.isNotEmpty ? value : 'unknown'; _instrumentationLogger.log( _join([ TAG_VERSION, uuid, normalize(clientId), normalize(clientVersion), serverVersion, sdkVersion, ]), ); } @override void logWatchEvent(String folderPath, String filePath, String changeType) { if (_watchEventExclusionFiles?.contains(filePath) ?? false) { return; } _instrumentationLogger.log( _join([TAG_WATCH_EVENT, folderPath, filePath, changeType]), ); } @override Future shutdown() async { await _instrumentationLogger.shutdown(); } /// Write an escaped version of the given [field] to the given [buffer]. void _escape(StringBuffer buffer, String field) { int index = field.indexOf(':'); if (index < 0) { buffer.write(field); return; } int start = 0; while (index >= 0) { buffer.write(field.substring(start, index)); buffer.write('::'); start = index + 1; index = field.indexOf(':', start); } buffer.write(field.substring(start)); } /// Return the result of joining the values of the given fields, escaping the /// separator character by doubling it. String _join(List fields) { StringBuffer buffer = StringBuffer(); buffer.write(_timestamp); int length = fields.length; for (int i = 0; i < length; i++) { buffer.write(':'); _escape(buffer, fields[i]); } return buffer.toString(); } /// Log the given message with the given tag. void _log(String tag, String message) { _instrumentationLogger.log(_join([tag, message])); } /// Convert the given [object] to a string. String _toString(Object? object) { if (object == null) { return 'null'; } return object.toString(); } }