mirror of
https://github.com/hanskokx/arcane_framework.git
synced 2026-05-14 10:29:06 +02:00
Logging service:
- added: reset() - added: `skipAutodetection` option for module, method, and metadata - added: unregisterInterface() - Created FileAndLineNumber mixin Signed-off-by: Hans Kokx <hans.d.kokx@gmail.com>
This commit is contained in:
@@ -38,9 +38,7 @@ class ArcaneServiceProvider extends InheritedNotifier {
|
|||||||
/// This always returns `true`, meaning dependents will always be notified
|
/// This always returns `true`, meaning dependents will always be notified
|
||||||
/// when this widget is rebuilt.
|
/// when this widget is rebuilt.
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(ArcaneServiceProvider oldWidget) {
|
bool updateShouldNotify(_) => true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the nearest `ArcaneServiceProvider` in the widget tree.
|
/// Retrieves the nearest `ArcaneServiceProvider` in the widget tree.
|
||||||
///
|
///
|
||||||
@@ -105,7 +103,7 @@ extension ServiceProvider on BuildContext {
|
|||||||
/// to notify listeners of changes. Services are typically registered in
|
/// to notify listeners of changes. Services are typically registered in
|
||||||
/// `ArcaneServiceProvider` and can be accessed using the `serviceOfType`
|
/// `ArcaneServiceProvider` and can be accessed using the `serviceOfType`
|
||||||
/// method on `BuildContext`.
|
/// method on `BuildContext`.
|
||||||
abstract class ArcaneService with ChangeNotifier {
|
abstract class ArcaneService<T> with ChangeNotifier {
|
||||||
static T? of<T extends ArcaneService>(BuildContext context) =>
|
static T? of<T extends ArcaneService>(BuildContext context) =>
|
||||||
context.serviceOfType<T>();
|
context.serviceOfType();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
mixin class FileAndLineNumber {
|
||||||
|
static String get _parts => StackTrace.current
|
||||||
|
.toString()
|
||||||
|
.split("\n")[2]
|
||||||
|
.split(RegExp("#2"))[1]
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
static String? get module =>
|
||||||
|
_parts.split(".").firstOrNull?.replaceFirst("new ", "");
|
||||||
|
static String? get method {
|
||||||
|
if (_parts.length <= 1) return null;
|
||||||
|
return _parts[1].split(" ").firstOrNull?.replaceAll("<anonymous", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String? get fileAndLine {
|
||||||
|
final List<String> fileAndLineParts = [
|
||||||
|
...?_parts.split("(package:").lastOrNull?.split(":"),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (fileAndLineParts.length <= 2) {
|
||||||
|
return fileAndLineParts.firstOrNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${fileAndLineParts[0]}:${fileAndLineParts[1]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import "dart:async";
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:arcane_framework/src/services/logging/logging_interface_extensions.dart";
|
||||||
import "package:arcane_helper_utils/arcane_helper_utils.dart";
|
import "package:arcane_helper_utils/arcane_helper_utils.dart";
|
||||||
import "package:flutter/foundation.dart";
|
|
||||||
|
|
||||||
part "logging_enums.dart";
|
part "logging_enums.dart";
|
||||||
part "logging_interface.dart";
|
part "logging_interface.dart";
|
||||||
@@ -12,7 +12,7 @@ part "logging_interface.dart";
|
|||||||
/// The `ArcaneLogger` provides a centralized way to log messages across
|
/// The `ArcaneLogger` provides a centralized way to log messages across
|
||||||
/// different parts of an application. It supports multiple logging interfaces,
|
/// different parts of an application. It supports multiple logging interfaces,
|
||||||
/// metadata, and platform-specific error handling.
|
/// metadata, and platform-specific error handling.
|
||||||
class ArcaneLogger {
|
class ArcaneLogger with FileAndLineNumber {
|
||||||
ArcaneLogger._internal();
|
ArcaneLogger._internal();
|
||||||
|
|
||||||
static final ArcaneLogger _instance = ArcaneLogger._internal();
|
static final ArcaneLogger _instance = ArcaneLogger._internal();
|
||||||
@@ -115,13 +115,61 @@ class ArcaneLogger {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
void log(
|
void log(
|
||||||
|
/// The message to be logged
|
||||||
String message, {
|
String message, {
|
||||||
|
/// The Dart class from which the `log` call was invoked. This is useful
|
||||||
|
/// in determining which part of the code called the log event. If the
|
||||||
|
/// [module] is not specified and [skipAutodetection] is set to [false],
|
||||||
|
/// [ArcaneLogger] will attempt to derive this value automatically. However,
|
||||||
|
/// this may fail in some cases and could potentially adversely impact
|
||||||
|
/// performance.
|
||||||
String? module,
|
String? module,
|
||||||
|
|
||||||
|
/// The method from which the `log` call was invoked. This is useful
|
||||||
|
/// in determining which part of the code called the log event. If the
|
||||||
|
/// [method] is not specified and [skipAutodetection] is set to [false],
|
||||||
|
/// [ArcaneLogger] will attempt to derive this value automatically. However,
|
||||||
|
/// this may fail in some cases and could potentially adversely impact
|
||||||
|
/// performance.
|
||||||
String? method,
|
String? method,
|
||||||
|
|
||||||
|
/// This value defines the severity of the log message. The default value is
|
||||||
|
/// [Level.debug].
|
||||||
Level level = Level.debug,
|
Level level = Level.debug,
|
||||||
|
|
||||||
|
/// A [StackTrace] can be passed into the `log` call for further processing
|
||||||
|
/// by the registered [LoggingInterface]s.
|
||||||
StackTrace? stackTrace,
|
StackTrace? stackTrace,
|
||||||
|
|
||||||
|
/// The provided [metadata] will be merged with any previously registered
|
||||||
|
/// persistent metadata. If the [module] and/or [method] are provided, these
|
||||||
|
/// values will be merged with the [metadata] as well, otherwise if one or
|
||||||
|
/// none of these values are provided and [skipAutodetection] is set to
|
||||||
|
/// [false] (default), the module, method, and/or [filenameAndLineNumber]
|
||||||
|
/// will be automatically determined and added to the [metadata] if the
|
||||||
|
/// values are not already present.
|
||||||
Map<String, String>? metadata,
|
Map<String, String>? metadata,
|
||||||
|
|
||||||
|
/// The [extra] parameter can be used to pass _any_ object into the
|
||||||
|
/// registered [LoggingInterface]s.
|
||||||
Object? extra,
|
Object? extra,
|
||||||
|
|
||||||
|
/// If set to [true], this parameter will skip automatically trying to
|
||||||
|
/// determine the current log message's [module], [method], and
|
||||||
|
/// [filenameAndLineNumber].
|
||||||
|
///
|
||||||
|
/// If set to [true] and the [filenameAndLineNumber] are desired, they
|
||||||
|
/// should be calculated by the [LoggingInterface] and added as as
|
||||||
|
/// [metadata].
|
||||||
|
///
|
||||||
|
/// If this value is [false] (default), the [module] and [method] will only
|
||||||
|
/// be added to the [metadata] if they are not otherwise provided. However,
|
||||||
|
/// the [filenameAndLineNumber] will automatically be added _unless_ it is
|
||||||
|
/// already in the [metadata] provided.
|
||||||
|
///
|
||||||
|
/// When set to [true], the automatic generation of these values _may_
|
||||||
|
/// impact performance.
|
||||||
|
bool skipAutodetection = false,
|
||||||
}) {
|
}) {
|
||||||
if (!I._initialized) {
|
if (!I._initialized) {
|
||||||
throw Exception("ArcaneLogger has not yet been initialized.");
|
throw Exception("ArcaneLogger has not yet been initialized.");
|
||||||
@@ -129,37 +177,41 @@ class ArcaneLogger {
|
|||||||
|
|
||||||
metadata ??= <String, String>{};
|
metadata ??= <String, String>{};
|
||||||
|
|
||||||
final String now = DateTime.now().toIso8601String();
|
metadata.putIfAbsent("timestamp", () => DateTime.now().toIso8601String());
|
||||||
metadata.putIfAbsent("timestamp", () => now);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final List<String> parts = StackTrace.current
|
|
||||||
.toString()
|
|
||||||
.split("\n")[2]
|
|
||||||
.split(RegExp("#2"))[1]
|
|
||||||
.trimLeft()
|
|
||||||
.split(".");
|
|
||||||
|
|
||||||
module ??= parts.first.replaceFirst("new ", "");
|
|
||||||
method ??= parts[1].split(" ").first.replaceAll("<anonymous", "");
|
|
||||||
|
|
||||||
final List<String> fileAndLineParts = StackTrace.current
|
|
||||||
.toString()
|
|
||||||
.split("\n")[2]
|
|
||||||
.split(RegExp("#2"))[1]
|
|
||||||
.trim()
|
|
||||||
.split("(package:")
|
|
||||||
.last
|
|
||||||
.split(":");
|
|
||||||
|
|
||||||
final String fileAndLine =
|
|
||||||
"${fileAndLineParts[0]}:${fileAndLineParts[1]}";
|
|
||||||
|
|
||||||
|
if (module.isNotEmptyOrNull) {
|
||||||
metadata.putIfAbsent("module", () => module!);
|
metadata.putIfAbsent("module", () => module!);
|
||||||
if (method.isNotNullOrEmpty)
|
} else if (!metadata.containsKey("module") &&
|
||||||
metadata.putIfAbsent("method", () => method!);
|
metadata["module"].isNullOrEmpty) {
|
||||||
metadata.putIfAbsent("filenameAndLineNumber", () => fileAndLine);
|
try {
|
||||||
} catch (_) {}
|
if (FileAndLineNumber.module.isNotNullOrEmpty) {
|
||||||
|
metadata.putIfAbsent("module", () => FileAndLineNumber.module!);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.isNotEmptyOrNull) {
|
||||||
|
metadata.putIfAbsent("method", () => method!);
|
||||||
|
} else if (!metadata.containsKey("method") &&
|
||||||
|
metadata["method"].isNullOrEmpty) {
|
||||||
|
try {
|
||||||
|
if (FileAndLineNumber.method.isNotNullOrEmpty) {
|
||||||
|
metadata.putIfAbsent("method", () => FileAndLineNumber.method!);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!metadata.containsKey("filenameAndLineNumber") &&
|
||||||
|
metadata["filenameAndLineNumber"].isNullOrEmpty) {
|
||||||
|
try {
|
||||||
|
if (FileAndLineNumber.fileAndLine.isNotNullOrEmpty) {
|
||||||
|
metadata.putIfAbsent(
|
||||||
|
"filenameAndLineNumber",
|
||||||
|
() => FileAndLineNumber.fileAndLine!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
metadata.addAll(additionalMetadata);
|
metadata.addAll(additionalMetadata);
|
||||||
|
|
||||||
@@ -211,6 +263,18 @@ class ArcaneLogger {
|
|||||||
return I;
|
return I;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unregisters a [LoggingInterface] from the [ArcaneLogger], if it was
|
||||||
|
/// previously registered.
|
||||||
|
Future<ArcaneLogger> unregisterInterface(
|
||||||
|
LoggingInterface interface,
|
||||||
|
) async {
|
||||||
|
if (!initialized) await _init();
|
||||||
|
|
||||||
|
I._interfaces.remove(interface);
|
||||||
|
|
||||||
|
return I;
|
||||||
|
}
|
||||||
|
|
||||||
/// Unregisters a `List` of [LoggingInterface] from the [ArcaneLogger], if
|
/// Unregisters a `List` of [LoggingInterface] from the [ArcaneLogger], if
|
||||||
/// they were previously registered.
|
/// they were previously registered.
|
||||||
Future<ArcaneLogger> unregisterInterfaces(
|
Future<ArcaneLogger> unregisterInterfaces(
|
||||||
@@ -287,7 +351,9 @@ class ArcaneLogger {
|
|||||||
/// Clears all persistent metadata.
|
/// Clears all persistent metadata.
|
||||||
void clearPersistentMetadata() => _additionalMetadata.clear();
|
void clearPersistentMetadata() => _additionalMetadata.clear();
|
||||||
|
|
||||||
@visibleForTesting
|
/// Resets the Arcane logging service by clearing all persistent metadata,
|
||||||
|
/// clearing all registered [LoggingInterface]s and marking the logging
|
||||||
|
/// service as no longer being initialized.
|
||||||
void reset() {
|
void reset() {
|
||||||
I._interfaces.clear();
|
I._interfaces.clear();
|
||||||
I._initialized = false;
|
I._initialized = false;
|
||||||
|
|||||||
@@ -25,3 +25,6 @@ dev_dependencies:
|
|||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
mockito: ^5.4.5
|
mockito: ^5.4.5
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
analyzer: 7.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user