diff --git a/packages/dart/lib/sentry.dart b/packages/dart/lib/sentry.dart index bb349a6a3c..8d01a3181c 100644 --- a/packages/dart/lib/sentry.dart +++ b/packages/dart/lib/sentry.dart @@ -61,3 +61,5 @@ export 'src/utils/url_details.dart'; // ignore: invalid_export_of_internal_element export 'src/utils/breadcrumb_log_level.dart'; export 'src/sentry_logger.dart'; +// ignore: invalid_export_of_internal_element +export 'src/utils/internal_logger.dart' show SentryInternalLogger; diff --git a/packages/dart/lib/src/runtime_checker.dart b/packages/dart/lib/src/runtime_checker.dart index d458448b69..aac13ae4d5 100644 --- a/packages/dart/lib/src/runtime_checker.dart +++ b/packages/dart/lib/src/runtime_checker.dart @@ -9,20 +9,40 @@ class RuntimeChecker { bool? isRootZone, }) : isRootZone = isRootZone ?? Zone.current == Zone.root; - /// Check if running in release/production environment - bool isReleaseMode() { - return const bool.fromEnvironment('dart.vm.product', defaultValue: false); - } + /// Whether running in release/production environment as a compile-time constant for guaranteed tree-shaking. + /// + /// If the code needs to be testable, use [isReleaseMode] instead. + static const bool kReleaseMode = + bool.fromEnvironment('dart.vm.product', defaultValue: false); - /// Check if running in debug environment - bool isDebugMode() { - return !isReleaseMode() && !isProfileMode(); - } + /// Whether running in profile environment as a compile-time constant for guaranteed tree-shaking. + /// + /// If the code needs to be testable, use [isProfileMode] instead. + static const bool kProfileMode = + bool.fromEnvironment('dart.vm.profile', defaultValue: false); - /// Check if running in profile environment - bool isProfileMode() { - return const bool.fromEnvironment('dart.vm.profile', defaultValue: false); - } + /// Whether running in debug environment as a compile-time constant for guaranteed tree-shaking. + /// + /// If the code needs to be testable, use [isDebugMode] instead. + static const bool kDebugMode = !kReleaseMode && !kProfileMode; + + /// Whether running in release/production environment. + /// + /// Code paths using this method are not guaranteed to be tree-shaken in release builds. + /// If tree-shaking needs to be guaranteed, use [RuntimeChecker.kReleaseMode] instead. + bool isReleaseMode() => kReleaseMode; + + /// Whether running in debug environment. + /// + /// Code paths using this method are not guaranteed to be tree-shaken in non-debug builds. + /// If tree-shaking needs to be guaranteed, use [RuntimeChecker.kDebugMode] instead. + bool isDebugMode() => kDebugMode; + + /// Whether running in profile environment. + /// + /// Code paths using this method are not guaranteed to be tree-shaken in profile builds. + /// If tree-shaking needs to be guaranteed, use [RuntimeChecker.kProfileMode] instead. + bool isProfileMode() => kProfileMode; /// Check if the Dart code is obfuscated. bool isAppObfuscated() { diff --git a/packages/dart/lib/src/sentry_options.dart b/packages/dart/lib/src/sentry_options.dart index ba92e6df89..9cb8b881fa 100644 --- a/packages/dart/lib/src/sentry_options.dart +++ b/packages/dart/lib/src/sentry_options.dart @@ -162,6 +162,7 @@ class SentryOptions { set debug(bool newValue) { _debug = newValue; + _configureInternalLogger(); if (_debug == true && (log == noOpLog || diagnosticLog?.logger == noOpLog)) { log = debugLog; @@ -175,7 +176,19 @@ class SentryOptions { bool _debug = false; /// minimum LogLevel to be used if debug is enabled - SentryLevel diagnosticLevel = _defaultDiagnosticLevel; + SentryLevel get diagnosticLevel => _diagnosticLevel; + + set diagnosticLevel(SentryLevel newValue) { + _diagnosticLevel = newValue; + _configureInternalLogger(); + } + + SentryLevel _diagnosticLevel = _defaultDiagnosticLevel; + + void _configureInternalLogger() { + SentryInternalLogger.configure( + isEnabled: _debug, minLevel: _diagnosticLevel); + } /// Sentry client name used for the HTTP authHeader and userAgent eg /// sentry.{language}.{platform}/{version} eg sentry.java.android/2.0.0 would be a valid case diff --git a/packages/dart/lib/src/utils/internal_logger.dart b/packages/dart/lib/src/utils/internal_logger.dart new file mode 100644 index 0000000000..3f8c43a06d --- /dev/null +++ b/packages/dart/lib/src/utils/internal_logger.dart @@ -0,0 +1,156 @@ +import 'dart:developer' as dev; + +import 'package:meta/meta.dart'; + +import '../../sentry.dart'; + +typedef LogOutputFunction = void Function({ + required String name, + required SentryLevel level, + required String message, + Object? error, + StackTrace? stackTrace, +}); + +/// Lightweight isolate compatible diagnostic logger for the Sentry SDK. +/// +/// Logger naming convention: +/// - `sentry_dart` – core dart package +/// - `sentry_flutter` – flutter package +/// - `sentry_` – integration packages (dio, hive, etc.) +/// +/// Each package should have one top-level logger instance. +/// +/// All log methods accept [Object] for the message parameter. +/// If the message is a [Function], it will be lazily evaluated. +/// Non-string values are converted via `toString()`. +/// +/// Example: +/// ```dart +/// const logger = SentryInternalLogger('sentry_flutter'); +/// +/// logger.warning('Simple message'); +/// logger.debug(() => 'Expensive: ${computeDebugInfo()}'); // Lazy evaluation +/// ``` +@internal +class SentryInternalLogger { + final String _name; + + const SentryInternalLogger(this._name); + + static bool _isEnabled = false; + static SentryLevel _minLevel = SentryLevel.warning; + static LogOutputFunction _logOutput = _defaultLogOutput; + + @visibleForTesting + static bool get isEnabled => _isEnabled; + + @visibleForTesting + static SentryLevel get minLevel => _minLevel; + + /// Configure logging for the current isolate. + /// + /// This needs to be called for each new spawned isolate before logging. + static void configure({ + required bool isEnabled, + SentryLevel minLevel = SentryLevel.warning, + LogOutputFunction? logOutput, + }) { + _isEnabled = isEnabled; + _minLevel = minLevel; + if (logOutput != null) { + _logOutput = logOutput; + } + } + + static void _defaultLogOutput({ + required String name, + required SentryLevel level, + required String message, + Object? error, + StackTrace? stackTrace, + }) { + dev.log( + '[${level.name}] $message', + name: name, + level: level.toDartLogLevel(), + error: error, + stackTrace: stackTrace, + time: DateTime.now(), + ); + } + + static bool _isLoggable(SentryLevel level) { + return _isEnabled && level.ordinal >= _minLevel.ordinal; + } + + void debug( + Object message, { + Object? error, + StackTrace? stackTrace, + }) => + _log(SentryLevel.debug, message, error: error, stackTrace: stackTrace); + + void info( + Object message, { + Object? error, + StackTrace? stackTrace, + }) => + _log(SentryLevel.info, message, error: error, stackTrace: stackTrace); + + void warning( + Object message, { + Object? error, + StackTrace? stackTrace, + }) => + _log(SentryLevel.warning, message, error: error, stackTrace: stackTrace); + + void error( + Object message, { + Object? error, + StackTrace? stackTrace, + }) => + _log(SentryLevel.error, message, error: error, stackTrace: stackTrace); + + void fatal( + Object message, { + Object? error, + StackTrace? stackTrace, + }) => + _log(SentryLevel.fatal, message, error: error, stackTrace: stackTrace); + + @pragma('vm:prefer-inline') + void _log( + SentryLevel level, + Object message, { + Object? error, + StackTrace? stackTrace, + }) { + // Guarantee tree-shaking with const kDebugMode + if (!RuntimeChecker.kDebugMode) return; + if (!_isLoggable(level)) return; + + if (message is Function) { + message = (message as Object Function())(); + } + + String finalMessage; + if (message is String) { + finalMessage = message; + } else { + finalMessage = message.toString(); + } + + _logOutput( + name: _name, + level: level, + message: finalMessage, + error: error, + stackTrace: stackTrace, + ); + } +} + +/// Logger for the Sentry Dart SDK. +@internal +const internalLogger = SentryInternalLogger('sentry_dart'); diff --git a/packages/dart/test/utils/internal_logger_test.dart b/packages/dart/test/utils/internal_logger_test.dart new file mode 100644 index 0000000000..4724c4e8eb --- /dev/null +++ b/packages/dart/test/utils/internal_logger_test.dart @@ -0,0 +1,339 @@ +import 'package:sentry/sentry.dart'; +import 'package:sentry/src/utils/internal_logger.dart'; +import 'package:test/test.dart'; + +import '../test_utils.dart'; + +void main() { + group(SentryInternalLogger, () { + late List<_CapturedLog> logs; + + LogOutputFunction captureLogOutput() { + return ({ + required String name, + required SentryLevel level, + required String message, + Object? error, + StackTrace? stackTrace, + }) { + logs.add(_CapturedLog( + name: name, + level: level, + message: message, + error: error, + stackTrace: stackTrace, + )); + }; + } + + setUp(() { + logs = []; + }); + + tearDown(() { + SentryInternalLogger.configure(isEnabled: false); + }); + + group('configuration', () { + test('enables logging when isEnabled is true', () { + SentryInternalLogger.configure(isEnabled: true); + + expect(SentryInternalLogger.isEnabled, isTrue); + }); + + test('disables logging when isEnabled is false', () { + SentryInternalLogger.configure(isEnabled: false); + + expect(SentryInternalLogger.isEnabled, isFalse); + }); + + test('sets minimum level', () { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.error, + ); + + expect(SentryInternalLogger.isEnabled, isTrue); + expect(SentryInternalLogger.minLevel, equals(SentryLevel.error)); + }); + + test('defaults minLevel to warning', () { + SentryInternalLogger.configure(isEnabled: true); + + expect(SentryInternalLogger.minLevel, equals(SentryLevel.warning)); + }); + + test('SentryOptions.debug enables logger', () { + final options = defaultTestOptions(); + + expect(options.debug, isFalse); + options.debug = true; + + expect(SentryInternalLogger.isEnabled, isTrue); + }); + + test('SentryOptions.diagnosticLevel sets minLevel when set before debug', + () { + final options = defaultTestOptions(); + + options.diagnosticLevel = SentryLevel.error; + options.debug = true; + + expect(SentryInternalLogger.isEnabled, isTrue); + expect(SentryInternalLogger.minLevel, equals(SentryLevel.error)); + }); + + test( + 'SentryOptions.diagnosticLevel updates minLevel when set after debug', + () { + final options = defaultTestOptions(); + + options.debug = true; + expect(SentryInternalLogger.minLevel, equals(SentryLevel.warning)); + + options.diagnosticLevel = SentryLevel.error; + + expect(SentryInternalLogger.isEnabled, isTrue); + expect(SentryInternalLogger.minLevel, equals(SentryLevel.error)); + }); + }); + + group('logging when enabled', () { + setUp(() { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.debug, + logOutput: captureLogOutput(), + ); + }); + + test('debug logs message with correct level', () { + internalLogger.debug('debug message'); + + expect(logs, hasLength(1)); + expect(logs.first.level, SentryLevel.debug); + expect(logs.first.message, 'debug message'); + expect(logs.first.name, 'sentry_dart'); + }); + + test('info logs message with correct level', () { + internalLogger.info('info message'); + + expect(logs, hasLength(1)); + expect(logs.first.level, SentryLevel.info); + expect(logs.first.message, 'info message'); + }); + + test('warning logs message with correct level', () { + internalLogger.warning('warning message'); + + expect(logs, hasLength(1)); + expect(logs.first.level, SentryLevel.warning); + expect(logs.first.message, 'warning message'); + }); + + test('error logs message with correct level', () { + internalLogger.error('error message'); + + expect(logs, hasLength(1)); + expect(logs.first.level, SentryLevel.error); + expect(logs.first.message, 'error message'); + }); + + test('fatal logs message with correct level', () { + internalLogger.fatal('fatal message'); + + expect(logs, hasLength(1)); + expect(logs.first.level, SentryLevel.fatal); + expect(logs.first.message, 'fatal message'); + }); + + test('includes error object in log entry', () { + final exception = Exception('test exception'); + + internalLogger.error('error occurred', error: exception); + + expect(logs, hasLength(1)); + expect(logs.first.error, exception); + }); + + test('includes stackTrace in log entry', () { + final stackTrace = StackTrace.current; + + internalLogger.error('error occurred', stackTrace: stackTrace); + + expect(logs, hasLength(1)); + expect(logs.first.stackTrace, stackTrace); + }); + }); + + group('logger instances', () { + test('logs with custom logger name', () { + const customLogger = SentryInternalLogger('sentry_flutter'); + SentryInternalLogger.configure( + isEnabled: true, + logOutput: captureLogOutput(), + ); + + customLogger.warning('test from flutter logger'); + + expect(logs, hasLength(1)); + expect(logs.first.name, 'sentry_flutter'); + }); + }); + + group('lazy evaluation', () { + test('does not evaluate function when logging is disabled', () { + SentryInternalLogger.configure(isEnabled: false); + var wasCalled = false; + + internalLogger.debug(() { + wasCalled = true; + return 'expensive message'; + }); + + expect(wasCalled, isFalse); + }); + + test('evaluates function when logging is enabled', () { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.debug, + ); + var wasCalled = false; + + internalLogger.debug(() { + wasCalled = true; + return 'expensive message'; + }); + + expect(wasCalled, isTrue); + }); + + test('does not evaluate function when level is below minLevel', () { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.warning, + ); + var wasCalled = false; + + internalLogger.debug(() { + wasCalled = true; + return 'debug message'; + }); + + expect(wasCalled, isFalse); + }); + }); + + group('level filtering', () { + test('logs message at minLevel', () { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.warning, + ); + var wasCalled = false; + + internalLogger.warning(() { + wasCalled = true; + return 'warning message'; + }); + + expect(wasCalled, isTrue); + }); + + test('logs message above minLevel', () { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.warning, + ); + var wasCalled = false; + + internalLogger.error(() { + wasCalled = true; + return 'error message'; + }); + + expect(wasCalled, isTrue); + }); + + test('does not log message below minLevel', () { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.error, + ); + var wasCalled = false; + + internalLogger.warning(() { + wasCalled = true; + return 'warning message'; + }); + + expect(wasCalled, isFalse); + }); + }); + + group('message conversion', () { + setUp(() { + SentryInternalLogger.configure( + isEnabled: true, + minLevel: SentryLevel.debug, + logOutput: captureLogOutput(), + ); + }); + + test('converts int to string', () { + internalLogger.info(42); + + expect(logs, hasLength(1)); + expect(logs.first.message, '42'); + }); + + test('converts custom object using toString', () { + internalLogger.info(_TestMessage('custom')); + + expect(logs, hasLength(1)); + expect(logs.first.message, 'TestMessage: custom'); + }); + + test('converts list to string', () { + internalLogger.info([1, 2, 3]); + + expect(logs, hasLength(1)); + expect(logs.first.message, '[1, 2, 3]'); + }); + + test('evaluates function and converts result', () { + internalLogger.info(() => 123); + + expect(logs, hasLength(1)); + expect(logs.first.message, '123'); + }); + }); + }); +} + +class _CapturedLog { + final String name; + final SentryLevel level; + final String message; + final Object? error; + final StackTrace? stackTrace; + + _CapturedLog({ + required this.name, + required this.level, + required this.message, + this.error, + this.stackTrace, + }); +} + +class _TestMessage { + final String value; + + _TestMessage(this.value); + + @override + String toString() => 'TestMessage: $value'; +} diff --git a/packages/flutter/lib/src/isolate/isolate_logger.dart b/packages/flutter/lib/src/isolate/isolate_logger.dart deleted file mode 100644 index 2b6d8c3667..0000000000 --- a/packages/flutter/lib/src/isolate/isolate_logger.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:developer' as developer; - -import 'package:meta/meta.dart'; - -import '../../sentry_flutter.dart'; - -/// Static logger for Isolates that writes diagnostic messages to `dart:developer.log`. -/// -/// Intended for worker/background isolates where a `SentryOptions` instance -/// or hub may not be available. Because Dart statics are isolate-local, -/// you must call [configure] once per isolate before using [log]. -class IsolateLogger { - IsolateLogger._(); - - static late bool _debug; - static late SentryLevel _level; - static late String _loggerName; - static bool _isConfigured = false; - - /// Configures this logger for the current isolate. - /// - /// Must be called once per isolate before invoking [log]. - /// Throws [StateError] if called more than once without calling [reset] first. - /// - /// - [debug]: when false, suppresses all logs except [SentryLevel.fatal]. - /// - [level]: minimum severity threshold (inclusive) when [debug] is true. - /// - [loggerName]: logger name for the call sites - static void configure( - {required bool debug, - required SentryLevel level, - required String loggerName}) { - if (_isConfigured) { - throw StateError( - 'IsolateLogger.configure has already been called. It can only be configured once per isolate.'); - } - _debug = debug; - _level = level; - _loggerName = loggerName; - _isConfigured = true; - } - - /// Resets the logger state to allow reconfiguration. - /// - /// This is intended for testing purposes only. - @visibleForTesting - static void reset() { - _isConfigured = false; - } - - /// Emits a log entry if enabled. - /// - /// Messages are forwarded to [developer.log]. The provided [level] is - /// mapped via [SentryLevel.toDartLogLevel] to a `developer.log` numeric level. - /// If logging is disabled or [level] is below the configured threshold, - /// nothing is emitted. [SentryLevel.fatal] is always emitted. - static void log( - SentryLevel level, - String message, { - String? logger, - Object? exception, - StackTrace? stackTrace, - }) { - assert( - _isConfigured, 'IsolateLogger.configure must be called before logging'); - if (_isEnabled(level)) { - developer.log( - '[${level.name}] $message', - level: level.toDartLogLevel(), - name: logger ?? _loggerName, - time: DateTime.now(), - error: exception, - stackTrace: stackTrace, - ); - } - } - - static bool _isEnabled(SentryLevel level) { - return (_debug && level.ordinal >= _level.ordinal) || - level == SentryLevel.fatal; - } -} diff --git a/packages/flutter/lib/src/isolate/isolate_worker.dart b/packages/flutter/lib/src/isolate/isolate_worker.dart index 8b3b375393..3057541d43 100644 --- a/packages/flutter/lib/src/isolate/isolate_worker.dart +++ b/packages/flutter/lib/src/isolate/isolate_worker.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:isolate'; import '../../sentry_flutter.dart'; -import 'isolate_logger.dart'; +import '../utils/internal_logger.dart'; typedef SpawnWorkerFn = Future Function(WorkerConfig, WorkerEntry); @@ -141,20 +141,18 @@ void runWorker( SendPort host, WorkerHandler handler, ) { - IsolateLogger.configure( - debug: config.debug, - level: config.diagnosticLevel, - loggerName: config.debugName, - ); + // ignore: invalid_use_of_internal_member + SentryInternalLogger.configure( + isEnabled: config.debug, minLevel: config.diagnosticLevel); final inbox = ReceivePort(); host.send(inbox.sendPort); inbox.listen((msg) async { if (msg == _shutdownCommand) { - IsolateLogger.log(SentryLevel.debug, 'Isolate received shutdown'); + internalLogger.debug('${config.debugName}: isolate received shutdown'); inbox.close(); - IsolateLogger.log(SentryLevel.debug, 'Isolate closed'); + internalLogger.debug('${config.debugName}: isolate closed'); return; } @@ -172,8 +170,10 @@ void runWorker( try { await handler.onMessage(msg); } catch (exception, stackTrace) { - IsolateLogger.log(SentryLevel.error, 'Isolate failed to handle message', - exception: exception, stackTrace: stackTrace); + internalLogger.error( + '${config.debugName}: isolate failed to handle message', + error: exception, + stackTrace: stackTrace); } }); } diff --git a/packages/flutter/lib/src/native/java/android_envelope_sender.dart b/packages/flutter/lib/src/native/java/android_envelope_sender.dart index 88e73c37f1..344d2e5153 100644 --- a/packages/flutter/lib/src/native/java/android_envelope_sender.dart +++ b/packages/flutter/lib/src/native/java/android_envelope_sender.dart @@ -7,7 +7,7 @@ import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; import '../../isolate/isolate_worker.dart'; -import '../../isolate/isolate_logger.dart'; +import '../../utils/internal_logger.dart'; import 'binding.dart' as native; class AndroidEnvelopeSender { @@ -74,7 +74,8 @@ class _AndroidEnvelopeHandler extends WorkerHandler { final data = transferable.materialize().asUint8List(); _captureEnvelope(data, containsUnhandledException); } else { - IsolateLogger.log(SentryLevel.warning, 'Unexpected message type: $msg'); + internalLogger + .warning('${_config.debugName}: unexpected message type: $msg'); } } @@ -88,12 +89,12 @@ class _AndroidEnvelopeHandler extends WorkerHandler { byteArray, containsUnhandledException); if (id == null) { - IsolateLogger.log(SentryLevel.error, - 'Native Android SDK returned null when capturing envelope'); + internalLogger.error( + '${_config.debugName}: native Android SDK returned null when capturing envelope'); } } catch (exception, stackTrace) { - IsolateLogger.log(SentryLevel.error, 'Failed to capture envelope', - exception: exception, stackTrace: stackTrace); + internalLogger.error('${_config.debugName}: failed to capture envelope', + error: exception, stackTrace: stackTrace); if (_config.automatedTestMode) { rethrow; } diff --git a/packages/flutter/lib/src/native/java/android_replay_recorder.dart b/packages/flutter/lib/src/native/java/android_replay_recorder.dart index a7f31f7271..38b7006ea7 100644 --- a/packages/flutter/lib/src/native/java/android_replay_recorder.dart +++ b/packages/flutter/lib/src/native/java/android_replay_recorder.dart @@ -6,10 +6,10 @@ import 'package:jni/jni.dart'; import 'package:meta/meta.dart'; import '../../../sentry_flutter.dart'; -import '../../isolate/isolate_logger.dart'; import '../../isolate/isolate_worker.dart'; import '../../replay/scheduled_recorder.dart'; import '../../screenshot/screenshot.dart'; +import '../../utils/internal_logger.dart'; import 'binding.dart' as native; // Note, this is currently not unit-tested because mocking JNI calls is @@ -105,15 +105,15 @@ class _AndroidReplayHandler extends WorkerHandler { @override FutureOr onMessage(Object? message) { - IsolateLogger.log( - SentryLevel.warning, 'Unexpected fire-and-forget message: $message'); + internalLogger.warning( + '${_config.debugName}: Unexpected fire-and-forget message: $message'); } @override FutureOr onRequest(Object? payload) { if (payload is! _WorkItem) { - IsolateLogger.log( - SentryLevel.warning, 'Unexpected payload type: $payload'); + internalLogger + .warning('${_config.debugName}: Unexpected payload type: $payload'); return null; } @@ -142,8 +142,8 @@ class _AndroidReplayHandler extends WorkerHandler { return null; } catch (exception, stackTrace) { - IsolateLogger.log(SentryLevel.error, 'Failed to add replay screenshot', - exception: exception, stackTrace: stackTrace); + internalLogger.error('Failed to add replay screenshot', + error: exception, stackTrace: stackTrace); if (_config.automatedTestMode) { rethrow; } diff --git a/packages/flutter/lib/src/utils/internal_logger.dart b/packages/flutter/lib/src/utils/internal_logger.dart new file mode 100644 index 0000000000..9520a05ba7 --- /dev/null +++ b/packages/flutter/lib/src/utils/internal_logger.dart @@ -0,0 +1,6 @@ +import 'package:meta/meta.dart'; +import 'package:sentry/sentry.dart'; + +/// Logger for the Sentry Flutter SDK. +@internal +const internalLogger = SentryInternalLogger('sentry_flutter'); diff --git a/packages/flutter/test/isolate/isolate_logger_test.dart b/packages/flutter/test/isolate/isolate_logger_test.dart deleted file mode 100644 index 5d6fddfc96..0000000000 --- a/packages/flutter/test/isolate/isolate_logger_test.dart +++ /dev/null @@ -1,70 +0,0 @@ -@TestOn('vm') -library; - -import 'dart:isolate'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:sentry_flutter/src/isolate/isolate_logger.dart'; - -void _entryUnconfigured(SendPort sendPort) { - try { - IsolateLogger.log(SentryLevel.info, 'x'); - sendPort.send('no-error'); - } catch (e) { - sendPort.send(e.runtimeType.toString()); - } -} - -void main() { - setUp(() { - IsolateLogger.reset(); - }); - - test('configure required before log (debug builds)', () async { - final rp = ReceivePort(); - await Isolate.spawn(_entryUnconfigured, rp.sendPort, - debugName: 'LoggerUnconfigured'); - final result = await rp.first; - rp.close(); - - expect(result, '_AssertionError'); - }); - - test('fatal logs even when debug=false', () { - IsolateLogger.configure( - debug: false, - level: SentryLevel.error, - loggerName: 't', - ); - expect(() => IsolateLogger.log(SentryLevel.fatal, 'fatal ok'), - returnsNormally); - }); - - test('threshold gating (no-throw at info below warning)', () { - IsolateLogger.configure( - debug: true, - level: SentryLevel.warning, - loggerName: 't', - ); - expect( - () => IsolateLogger.log(SentryLevel.info, 'info ok'), returnsNormally); - expect(() => IsolateLogger.log(SentryLevel.warning, 'warn ok'), - returnsNormally); - }); - - test('prevents reconfiguration without reset', () { - IsolateLogger.configure( - debug: true, - level: SentryLevel.info, - loggerName: 't', - ); - expect( - () => IsolateLogger.configure( - debug: false, - level: SentryLevel.error, - loggerName: 't2', - ), - throwsStateError); - }); -}