import 'dart:convert'; class RuntimeDiagnostics { RuntimeDiagnostics({this.maxEntries = 100}); final int maxEntries; final List _entries = []; List get entries => List.unmodifiable(_entries); Map toDebugJson() { return { 'maxEntries': maxEntries, 'count': _entries.length, 'entries': _entries.map((entry) => entry.toDebugJson()).toList(), }; } String dumpText() { if (_entries.isEmpty) { return 'RuntimeDiagnostics: no entries'; } final buffer = StringBuffer( 'RuntimeDiagnostics (${_entries.length}/$maxEntries)', ); for (final entry in _entries) { buffer ..writeln() ..write(entry.dumpText()); } return buffer.toString(); } void record({ required RuntimeDiagnosticType type, required String message, Object? error, Map context = const {}, }) { if (_entries.length >= maxEntries) { _entries.removeAt(0); } _entries.add( RuntimeDiagnosticEntry( type: type, message: message, error: error, context: context, timestamp: DateTime.now(), ), ); } void clear() { _entries.clear(); } } class RuntimeDiagnosticEntry { const RuntimeDiagnosticEntry({ required this.type, required this.message, required this.timestamp, this.error, this.context = const {}, }); final RuntimeDiagnosticType type; final String message; final DateTime timestamp; final Object? error; final Map context; Map toDebugJson() { return { 'timestamp': timestamp.toIso8601String(), 'type': type.name, 'message': message, if (error != null) 'error': error.toString(), if (context.isNotEmpty) 'context': _toDebugValue(context), }; } String dumpText() { final buffer = StringBuffer( '[${timestamp.toIso8601String()}] ${type.name}: $message', ); if (error != null) { buffer ..writeln() ..write(' error: $error'); } if (context.isNotEmpty) { buffer ..writeln() ..write(' context: ${_formatDebugValue(context)}'); } return buffer.toString(); } } Object? _toDebugValue(Object? value) { if (value == null || value is String || value is num || value is bool) { return value; } if (value is DateTime) { return value.toIso8601String(); } if (value is Map) { final entries = value.entries.toList() ..sort((a, b) => a.key.toString().compareTo(b.key.toString())); return { for (final entry in entries) entry.key.toString(): _toDebugValue(entry.value), }; } if (value is Iterable) { return value.map(_toDebugValue).toList(); } return value.toString(); } String _formatDebugValue(Object? value) { try { return jsonEncode(_toDebugValue(value)); } catch (_) { return value.toString(); } } enum RuntimeDiagnosticType { luaLog, luaEventError, diffApplyError, packageActivationError, resourceLoadError, commandError, }