Skip to content

Commit

Permalink
Added log observer (#388)
Browse files Browse the repository at this point in the history
* Added log observer

* Updated flutter error logging

* updated main

* Updated & Refactored some initialization logic

* Updated docs

* Style updates

* updated error message

* Updated docs

* Added no op tracking manager

* Reworked error reporter

* Format
  • Loading branch information
hawkkiller authored Nov 28, 2024
1 parent aa37ff2 commit 293948e
Show file tree
Hide file tree
Showing 18 changed files with 413 additions and 491 deletions.
12 changes: 1 addition & 11 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
import 'dart:async';

import 'package:sizzle_starter/src/core/utils/logger.dart';
import 'package:sizzle_starter/src/feature/initialization/logic/app_runner.dart';

void main() {
final logger = DeveloperLogger();

runZonedGuarded(
() => AppRunner(logger).initializeAndRun(),
logger.logZoneError,
);
}
void main() => AppRunner.startup();
Original file line number Diff line number Diff line change
@@ -1,44 +1,44 @@
import 'package:sizzle_starter/src/feature/initialization/model/environment.dart';

/// Application configuration
class Config {
/// Creates a new [Config] instance.
const Config();
class ApplicationConfig {
/// Creates a new [ApplicationConfig] instance.
const ApplicationConfig();

/// The current environment.
Environment get environment {
var env = const String.fromEnvironment('ENVIRONMENT');
var env = const String.fromEnvironment('ENVIRONMENT').trim();

if (env.isNotEmpty) {
return Environment.from(env);
}

env = const String.fromEnvironment('FLUTTER_APP_FLAVOR');
env = const String.fromEnvironment('FLUTTER_APP_FLAVOR').trim();

return Environment.from(env);
}

/// The Sentry DSN.
String get sentryDsn => const String.fromEnvironment('SENTRY_DSN');
String get sentryDsn => const String.fromEnvironment('SENTRY_DSN').trim();

/// Whether Sentry is enabled.
bool get enableSentry => sentryDsn.isNotEmpty;
}

/// {@template testing_dependencies_container}
/// A special version of [Config] that is used in tests.
/// A special version of [ApplicationConfig] that is used in tests.
///
/// In order to use [Config] in tests, it is needed to
/// In order to use [ApplicationConfig] in tests, it is needed to
/// extend this class and provide the dependencies that are needed for the test.
/// {@endtemplate}
base class TestConfig implements Config {
base class TestConfig implements ApplicationConfig {
/// {@macro testing_dependencies_container}
const TestConfig();

@override
Object noSuchMethod(Invocation invocation) {
throw UnimplementedError(
'The test tries to access ${invocation.memberName} config option, but '
'The test tries to access ${invocation.memberName} (${invocation.runtimeType}) config option, but '
'it was not provided. Please provide the option in the test. '
'You can do it by extending this class and providing the option.',
);
Expand Down
2 changes: 1 addition & 1 deletion lib/src/core/rest_client/src/http/rest_client_http.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import 'package:http/http.dart' as http;
import 'package:sizzle_starter/src/core/rest_client/rest_client.dart';
import 'package:sizzle_starter/src/core/rest_client/src/http/check_exception_io.dart'
if (dart.library.js_interop) 'package:sizzle_starter/src/core/rest_client/src/http/check_exception_browser.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';
import 'package:sizzle_starter/src/core/utils/logger/logger.dart';

// coverage:ignore-start
/// Creates an [http.Client] based on the current platform.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:sizzle_starter/src/core/utils/analytics/analytics_reporter.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';
import 'package:sizzle_starter/src/core/utils/logger/logger.dart';

/// {@template firebase_analytics_reporter}
/// An implementation of [AnalyticsReporter] that reports events to Firebase
Expand Down
2 changes: 1 addition & 1 deletion lib/src/core/utils/app_bloc_observer.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:sizzle_starter/src/core/utils/extensions/string_extension.dart';
import 'package:sizzle_starter/src/core/utils/logger.dart';
import 'package:sizzle_starter/src/core/utils/logger/logger.dart';

/// [BlocObserver] which logs all bloc state changes, errors and events.
class AppBlocObserver extends BlocObserver {
Expand Down
78 changes: 78 additions & 0 deletions lib/src/core/utils/error_reporter/error_reporter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'dart:async';

import 'package:sizzle_starter/src/core/utils/logger/logger.dart';

/// {@template error_reporter}
/// An interface for reporting errors.
///
/// Implementations should report errors to a service like Sentry/Crashlytics.
/// {@endtemplate}
abstract interface class ErrorReporter {
/// Returns `true` if the error reporting service is initialized
/// and ready to report errors.
///
/// If this returns `false`, the error reporting service should not be used.
bool get isInitialized;

/// Initializes the error reporting service.
Future<void> initialize();

/// Closes the error reporting service.
Future<void> close();

/// Capture an exception to be reported.
///
/// The [throwable] is the exception that was thrown.
/// The [stackTrace] is the stack trace associated with the exception.
Future<void> captureException({
required Object throwable,
StackTrace? stackTrace,
});
}

/// {@template error_reporter_log_observer}
/// An observer that reports logs to the error reporter if it is active.
/// {@endtemplate}
final class ErrorReporterLogObserver extends LogObserver {
/// {@macro error_reporter_log_observer}
const ErrorReporterLogObserver(this._errorReporter);

/// Error reporter used to report errors.
final ErrorReporter _errorReporter;

@override
void onLog(LogMessage logMessage) {
// If the error reporter is not initialized, do nothing
if (!_errorReporter.isInitialized) return;

// If the log level is error or higher, report the error
if (logMessage.level.index >= LogLevel.error.index) {
_errorReporter.captureException(
throwable: logMessage.error ?? ReportedMessageException(logMessage.message),
stackTrace: logMessage.stackTrace ?? StackTrace.current,
);
}
}
}

/// An exception used for error logs without an exception, only a message.
class ReportedMessageException implements Exception {
/// Constructs an instance of [ReportedMessageException].
const ReportedMessageException(this.message);

/// The message that was reported.
final String message;

@override
String toString() => message;

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;

return other is ReportedMessageException && other.message == message;
}

@override
int get hashCode => message.hashCode;
}
49 changes: 49 additions & 0 deletions lib/src/core/utils/error_reporter/sentry_error_reporter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:flutter/foundation.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sizzle_starter/src/core/utils/error_reporter/error_reporter.dart';

/// {@template sentry_error_reporter}
/// An implementation of [ErrorReporter] that reports errors to Sentry.
/// {@endtemplate}
class SentryErrorReporter implements ErrorReporter {
/// {@macro sentry_error_reporter}
const SentryErrorReporter({
required this.sentryDsn,
required this.environment,
});

/// The Sentry DSN.
final String sentryDsn;

/// The Sentry environment.
final String environment;

@override
bool get isInitialized => Sentry.isEnabled;

@override
Future<void> initialize() async {
await SentryFlutter.init(
(options) => options
..dsn = sentryDsn
..tracesSampleRate = 0.10
..debug = kDebugMode
..environment = environment
..anrEnabled = true
..sendDefaultPii = true,
);
}

@override
Future<void> close() async {
await Sentry.close();
}

@override
Future<void> captureException({
required Object throwable,
StackTrace? stackTrace,
}) async {
await Sentry.captureException(throwable, stackTrace: stackTrace);
}
}

This file was deleted.

This file was deleted.

Loading

0 comments on commit 293948e

Please sign in to comment.