Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions lib/src/action_api/action_executor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import "package:duit_kernel/duit_kernel.dart";

/// The [ActionExecutor] is an abstract class responsible for executing actions.
///
/// It relies on a [UIDriver] to perform actions and an [DebugLogger] to log messages.
/// It relies on a [UIDriver] to perform actions and an DebugLogger to log messages.
/// This class serves as a base for concrete implementations that define how actions
/// should be executed.
///
/// Subclasses must implement the [executeAction] method to handle different types
/// of [ServerAction]s.
abstract class ActionExecutor {
final UIDriver driver;
@Deprecated("Use [LoggingCapabilityDelegate] instead")
final DebugLogger? logger;

ActionExecutor({
Expand Down Expand Up @@ -58,10 +59,10 @@ final class DefaultActionExecutor extends ActionExecutor {

return null;
} catch (e, s) {
logger?.error(
driver.logError(
"[Error while executing transport action]",
error: e,
stackTrace: s,
e,
s,
);
}

Expand All @@ -71,10 +72,10 @@ final class DefaultActionExecutor extends ActionExecutor {
try {
return action.event;
} catch (e, s) {
logger?.error(
driver.logError(
"[Error while executing local action]",
error: e,
stackTrace: s,
e,
s,
);
}
break;
Expand All @@ -97,10 +98,10 @@ final class DefaultActionExecutor extends ActionExecutor {

return null;
} catch (e, s) {
logger?.error(
driver.logError(
"[Error while executing script action]",
error: e,
stackTrace: s,
e,
s,
);
}
break;
Expand Down
13 changes: 6 additions & 7 deletions lib/src/action_api/event_resolver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "package:flutter/material.dart" show BuildContext;

abstract class EventResolver {
final UIDriver driver;
@Deprecated("Use [LoggingCapabilityDelegate] instead")
final DebugLogger? logger;

EventResolver({
Expand Down Expand Up @@ -32,8 +33,6 @@ final class DefaultEventResolver extends EventResolver {
}

try {
final driver = this.driver;

switch (event) {
case UpdateEvent():
event.updates.forEach((key, value) async {
Expand All @@ -46,7 +45,7 @@ final class DefaultEventResolver extends EventResolver {
"ExternalEventHandler instance is not set",
);
if (driver.externalEventHandler != null) {
logger?.error("ExternalEventHandler instance is not set");
driver.logError("ExternalEventHandler instance is not set");
throw StateError("ExternalEventHandler instance is not set");
}
Comment on lines 47 to 50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical logic error: Inverted null check.

The condition checks if (driver.externalEventHandler != null) but then logs and throws an error stating the handler is NOT set. This logic is inverted—the error should only be thrown when the handler IS null.

🐛 Proposed fix
-        if (driver.externalEventHandler != null) {
+        if (driver.externalEventHandler == null) {
           driver.logError("ExternalEventHandler instance is not set");
           throw StateError("ExternalEventHandler instance is not set");
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (driver.externalEventHandler != null) {
logger?.error("ExternalEventHandler instance is not set");
driver.logError("ExternalEventHandler instance is not set");
throw StateError("ExternalEventHandler instance is not set");
}
if (driver.externalEventHandler == null) {
driver.logError("ExternalEventHandler instance is not set");
throw StateError("ExternalEventHandler instance is not set");
}
🤖 Prompt for AI Agents
In @lib/src/action_api/event_resolver.dart around lines 47 - 50, The null check
is inverted: currently it checks if driver.externalEventHandler != null and then
logs and throws about it being unset; change the condition to if
(driver.externalEventHandler == null) so the error path only runs when the
handler is missing, update the log via driver.logError(...) and the thrown
StateError(...) remain but will now execute correctly when
driver.externalEventHandler is null; locate this logic around the
externalEventHandler check in event_resolver.dart to apply the fix.

await driver.externalEventHandler?.handleNavigation(
Expand All @@ -57,7 +56,7 @@ final class DefaultEventResolver extends EventResolver {
break;
case OpenUrlEvent():
if (driver.externalEventHandler != null) {
logger?.error("ExternalEventHandler instance is not set");
driver.logError("ExternalEventHandler instance is not set");
throw StateError("ExternalEventHandler instance is not set");
}
Comment on lines 58 to 61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical logic error: Inverted null check (duplicate issue).

Same inverted logic as in the NavigationEvent case. The condition should check if the handler IS null before logging and throwing an error.

🐛 Proposed fix
-        if (driver.externalEventHandler != null) {
+        if (driver.externalEventHandler == null) {
           driver.logError("ExternalEventHandler instance is not set");
           throw StateError("ExternalEventHandler instance is not set");
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (driver.externalEventHandler != null) {
logger?.error("ExternalEventHandler instance is not set");
driver.logError("ExternalEventHandler instance is not set");
throw StateError("ExternalEventHandler instance is not set");
}
if (driver.externalEventHandler == null) {
driver.logError("ExternalEventHandler instance is not set");
throw StateError("ExternalEventHandler instance is not set");
}
🤖 Prompt for AI Agents
In @lib/src/action_api/event_resolver.dart around lines 58 - 61, The null-check
for the external event handler is inverted: in event_resolver.dart the block
currently checks if (driver.externalEventHandler != null) before calling
driver.logError and throwing StateError; change the condition to if
(driver.externalEventHandler == null) so you only log and throw when the handler
is missing (mirror the fix applied for the NavigationEvent case); update the
condition around driver.externalEventHandler, keeping the driver.logError(...)
and throw StateError(...) calls intact.

await driver.externalEventHandler?.handleOpenUrl(event.url);
Expand Down Expand Up @@ -106,10 +105,10 @@ final class DefaultEventResolver extends EventResolver {
break;
}
} catch (e, s) {
logger?.error(
driver.logError(
"Error while resolving ${event.type} event",
error: e,
stackTrace: s,
e,
s,
);
}
}
Expand Down
4 changes: 4 additions & 0 deletions lib/src/capabilities/index.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ export "actions_capability.dart";
export "focus_capability.dart";
export "ui_controller_capability.dart";
export "viewmodel_capability.dart";
export "transport_capability.dart";
export "scripting_capability.dart";
export "logging/index.dart";
export "native_module_capability.dart";
2 changes: 2 additions & 0 deletions lib/src/capabilities/logging/index.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export "logging_capability.dart";
export "io.dart" if (dart.library.js_interop) "web.dart";
82 changes: 82 additions & 0 deletions lib/src/capabilities/logging/io.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import "package:duit_kernel/src/capabilities/logging/logging_capability.dart";
import "package:flutter/foundation.dart";

/// A class that provides logging capabilities for the Duit UI system.
///
/// This class implements the [LoggingCapabilityDelegate] mixin and provides
/// concrete implementations for logging messages at different levels.
final class LoggingManager with LoggingCapabilityDelegate {
const LoggingManager();

@override
void logCritical(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] CRITICAL: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}

@override
void logDebug(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] DEBUG: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}

@override
void logError(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] ERROR: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}

@override
void logInfo(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] INFO: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}

@override
void logVerbose(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] VERBOSE: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}

@override
void logWarning(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] WARNING: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
Comment on lines +11 to +81
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract common logging logic to reduce duplication.

All six logging methods follow an identical pattern, differing only in the log level label. This creates significant code duplication (~66 lines) that could be reduced to ~20 lines by extracting a common helper method.

♻️ Proposed refactor
 final class LoggingManager with LoggingCapabilityDelegate {
   const LoggingManager();
 
+  void _log(String level, message, [Object? exception, StackTrace? stackTrace]) {
+    final buff = StringBuffer("[DUIT FRAMEWORK] $level: $message \nTime: ${DateTime.now().toUtc()}");
+    if (exception != null) {
+      buff.write("\nException: ${exception.toString()}");
+    }
+    if (stackTrace != null) {
+      buff.write("\nStackTrace: ${stackTrace.toString()}");
+    }
+    debugPrint(buff.toString());
+  }
+
   @override
-  void logCritical(message, [Object? exception, StackTrace? stackTrace]) {
-    final buff = StringBuffer("[DUIT FRAMEWORK] CRITICAL: $message \nTime: ${DateTime.now().toUtc()}");
-    if (exception != null) {
-      buff.write("\nException: ${exception.toString()}");
-    }
-    if (stackTrace != null) {
-      buff.write("\nStackTrace: ${stackTrace.toString()}");
-    }
-    debugPrint(buff.toString());
-  }
+  void logCritical(message, [Object? exception, StackTrace? stackTrace]) =>
+      _log("CRITICAL", message, exception, stackTrace);
 
   @override
-  void logDebug(message, [Object? exception, StackTrace? stackTrace]) {
-    final buff = StringBuffer("[DUIT FRAMEWORK] DEBUG: $message \nTime: ${DateTime.now().toUtc()}");
-    if (exception != null) {
-      buff.write("\nException: ${exception.toString()}");
-    }
-    if (stackTrace != null) {
-      buff.write("\nStackTrace: ${stackTrace.toString()}");
-    }
-    debugPrint(buff.toString());
-  }
+  void logDebug(message, [Object? exception, StackTrace? stackTrace]) =>
+      _log("DEBUG", message, exception, stackTrace);
 
   @override
-  void logError(message, [Object? exception, StackTrace? stackTrace]) {
-    final buff = StringBuffer("[DUIT FRAMEWORK] ERROR: $message \nTime: ${DateTime.now().toUtc()}");
-    if (exception != null) {
-      buff.write("\nException: ${exception.toString()}");
-    }
-    if (stackTrace != null) {
-      buff.write("\nStackTrace: ${stackTrace.toString()}");
-    }
-    debugPrint(buff.toString());
-  }
+  void logError(message, [Object? exception, StackTrace? stackTrace]) =>
+      _log("ERROR", message, exception, stackTrace);
 
   @override
-  void logInfo(message, [Object? exception, StackTrace? stackTrace]) {
-    final buff = StringBuffer("[DUIT FRAMEWORK] INFO: $message \nTime: ${DateTime.now().toUtc()}");
-    if (exception != null) {
-      buff.write("\nException: ${exception.toString()}");
-    }
-    if (stackTrace != null) {
-      buff.write("\nStackTrace: ${stackTrace.toString()}");
-    }
-    debugPrint(buff.toString());
-  }
+  void logInfo(message, [Object? exception, StackTrace? stackTrace]) =>
+      _log("INFO", message, exception, stackTrace);
 
   @override
-  void logVerbose(message, [Object? exception, StackTrace? stackTrace]) {
-    final buff = StringBuffer("[DUIT FRAMEWORK] VERBOSE: $message \nTime: ${DateTime.now().toUtc()}");
-    if (exception != null) {
-      buff.write("\nException: ${exception.toString()}");
-    }
-    if (stackTrace != null) {
-      buff.write("\nStackTrace: ${stackTrace.toString()}");
-    }
-    debugPrint(buff.toString());
-  }
+  void logVerbose(message, [Object? exception, StackTrace? stackTrace]) =>
+      _log("VERBOSE", message, exception, stackTrace);
 
   @override
-  void logWarning(message, [Object? exception, StackTrace? stackTrace]) {
-    final buff = StringBuffer("[DUIT FRAMEWORK] WARNING: $message \nTime: ${DateTime.now().toUtc()}");
-    if (exception != null) {
-      buff.write("\nException: ${exception.toString()}");
-    }
-    if (stackTrace != null) {
-      buff.write("\nStackTrace: ${stackTrace.toString()}");
-    }
-    debugPrint(buff.toString());
-  }
+  void logWarning(message, [Object? exception, StackTrace? stackTrace]) =>
+      _log("WARNING", message, exception, stackTrace);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@override
void logCritical(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] CRITICAL: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
@override
void logDebug(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] DEBUG: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
@override
void logError(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] ERROR: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
@override
void logInfo(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] INFO: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
@override
void logVerbose(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] VERBOSE: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
@override
void logWarning(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] WARNING: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
final class LoggingManager with LoggingCapabilityDelegate {
const LoggingManager();
void _log(String level, message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] $level: $message \nTime: ${DateTime.now().toUtc()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
debugPrint(buff.toString());
}
@override
void logCritical(message, [Object? exception, StackTrace? stackTrace]) =>
_log("CRITICAL", message, exception, stackTrace);
@override
void logDebug(message, [Object? exception, StackTrace? stackTrace]) =>
_log("DEBUG", message, exception, stackTrace);
@override
void logError(message, [Object? exception, StackTrace? stackTrace]) =>
_log("ERROR", message, exception, stackTrace);
@override
void logInfo(message, [Object? exception, StackTrace? stackTrace]) =>
_log("INFO", message, exception, stackTrace);
@override
void logVerbose(message, [Object? exception, StackTrace? stackTrace]) =>
_log("VERBOSE", message, exception, stackTrace);
@override
void logWarning(message, [Object? exception, StackTrace? stackTrace]) =>
_log("WARNING", message, exception, stackTrace);
}
🤖 Prompt for AI Agents
In @lib/src/capabilities/logging/io.dart around lines 7 - 77, All six methods
(logCritical, logDebug, logError, logInfo, logVerbose, logWarning) duplicate the
same message-building and debugPrint logic; extract that into a single private
helper (e.g. _log(String level, message, [Object? exception, StackTrace?
stackTrace])) that builds the StringBuffer with the "[DUIT FRAMEWORK] <LEVEL>:
$message", timestamp, optional exception and stackTrace, and calls debugPrint,
then replace each public logX method to simply call that helper with the
appropriate level label.

}
99 changes: 99 additions & 0 deletions lib/src/capabilities/logging/logging_capability.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import "package:duit_kernel/src/misc/error.dart";
import "package:meta/meta.dart";

/// A mixin that provides logging capabilities compatible with Talker API.
///
/// This mixin defines methods for logging messages at different levels,
/// handling exceptions, and logging custom log types. All methods must be
/// overridden by implementing classes.
mixin LoggingCapabilityDelegate {
/// Logs an informational message.
///
/// Use this method to log general informational messages about the
/// application's state or flow.
@mustBeOverridden
void logInfo(
message, [
Object? exception,
StackTrace? stackTrace,
]) =>
throw const MissingCapabilityMethodImplementation(
"info",
"LoggingCapabilityDelegate",
);
Comment on lines +15 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add type annotation to the message parameter.

The message parameter lacks a type annotation, making it implicitly dynamic. This reduces type safety and could lead to runtime issues if non-stringifiable objects are passed. Consider using Object? or String to make the contract explicit.

♻️ Suggested fix
  @mustBeOverridden
  void logInfo(
-   message, [
+   Object? message, [
    Object? exception,
    StackTrace? stackTrace,
  ]) =>

Apply the same change to all logging methods (logDebug, logWarning, logError, logCritical, logVerbose).

🤖 Prompt for AI Agents
In @lib/src/capabilities/logging/logging_capability.dart around lines 15 - 23,
The parameter "message" in logInfo is untyped (dynamic); change its declaration
to a typed parameter (e.g., Object? or String) in the logInfo method to make the
contract explicit and then apply the same type annotation to the other logging
methods (logDebug, logWarning, logError, logCritical, logVerbose) in the
LoggingCapabilityDelegate so all signatures are consistent while keeping the
thrown MissingCapabilityMethodImplementation unchanged.


/// Logs a debug message.
///
/// Use this method to log detailed debugging information that is typically
/// only useful during development.
@mustBeOverridden
void logDebug(
message, [
Object? exception,
StackTrace? stackTrace,
]) =>
throw const MissingCapabilityMethodImplementation(
"debug",
"LoggingCapabilityDelegate",
);

/// Logs a warning message.
///
/// Use this method to log warnings about potential issues or unexpected
/// conditions that don't prevent the application from functioning.
@mustBeOverridden
void logWarning(
message, [
Object? exception,
StackTrace? stackTrace,
]) =>
throw const MissingCapabilityMethodImplementation(
"warning",
"LoggingCapabilityDelegate",
);

/// Logs an error message.
///
/// Use this method to log error messages that indicate problems or failures
/// in the application.
@mustBeOverridden
void logError(
message, [
Object? exception,
StackTrace? stackTrace,
]) =>
throw const MissingCapabilityMethodImplementation(
"error",
"LoggingCapabilityDelegate",
);

/// Logs a critical error message.
///
/// Use this method to log critical errors that require immediate attention
/// and may indicate serious problems in the application.
@mustBeOverridden
void logCritical(
message, [
Object? exception,
StackTrace? stackTrace,
]) =>
throw const MissingCapabilityMethodImplementation(
"critical",
"LoggingCapabilityDelegate",
);

/// Logs a verbose message.
///
/// Use this method to log very detailed information that is typically only
/// useful for deep debugging or troubleshooting.
@mustBeOverridden
void logVerbose(
message, [
Object? exception,
StackTrace? stackTrace,
]) =>
throw const MissingCapabilityMethodImplementation(
"verbose",
"LoggingCapabilityDelegate",
);
}
Comment on lines +9 to +99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using abstract methods instead of throwing implementations.

The mixin uses concrete methods that throw MissingCapabilityMethodImplementation. While this provides runtime checking, Dart allows abstract methods in mixins (since 2.14). Abstract methods would provide compile-time checking and clearer intent. However, if runtime flexibility is needed (e.g., partially implementing the mixin), the current approach is acceptable.

Alternative approach using abstract methods
mixin LoggingCapabilityDelegate {
  /// Logs an informational message.
  void logInfo(Object? message, [Object? exception, StackTrace? stackTrace]);
  
  /// Logs a debug message.
  void logDebug(Object? message, [Object? exception, StackTrace? stackTrace]);
  
  // ... etc for all methods
}

This would enforce implementation at compile-time rather than runtime.

🤖 Prompt for AI Agents
In @lib/src/capabilities/logging/logging_capability.dart around lines 9 - 99,
The current LoggingCapabilityDelegate mixin defines concrete methods (logInfo,
logDebug, logWarning, logError, logCritical, logVerbose) that throw
MissingCapabilityMethodImplementation at runtime; change each to an abstract
method declaration (e.g., void logInfo(Object? message, [Object? exception,
StackTrace? stackTrace]);) by removing the implementation body, the throw, and
the @mustBeOverridden annotation so the compiler enforces implementations for
logInfo, logDebug, logWarning, logError, logCritical, and logVerbose at compile
time.

84 changes: 84 additions & 0 deletions lib/src/capabilities/logging/web.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import "dart:js_interop";

import "package:duit_kernel/src/capabilities/logging/logging_capability.dart";
import "package:web/web.dart";

/// A class that provides logging capabilities for the Duit UI system.
///
/// This class implements the [LoggingCapabilityDelegate] mixin and provides
/// concrete implementations for logging messages at different levels.
final class LoggingManager with LoggingCapabilityDelegate {
const LoggingManager();

Comment on lines +10 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Inconsistent spacing after class declaration.

There's an extra blank line after the const constructor at line 8. While minor, consistent formatting improves readability.

🤖 Prompt for AI Agents
In @lib/src/capabilities/logging/web.dart around lines 6 - 8, Remove the stray
blank line after the const constructor in the LoggingManager class declaration;
locate the final class declaration "final class LoggingManager with
LoggingCapabilityDelegate" and its "const LoggingManager();" constructor and
delete the extra empty line that follows so the constructor and subsequent code
are consistently spaced.

@override
void logCritical(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] CRITICAL: $message \nTime: ${DateTime.now().toIso8601String()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
console.error(buff.toString().toJS);
}
Comment on lines +14 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

logCritical and logError both use console.error.

Both logCritical and logError use console.error(), making them functionally identical in the browser console. Consider using a more prominent output method for critical errors, such as also triggering a visual alert or using a distinct prefix that stands out more clearly.

Alternatively, if browser console limitations mean they must use the same method, document this behavior to set expectations for users debugging in the browser console.

Also applies to: 34-43


@override
void logDebug(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] DEBUG: $message \nTime: ${DateTime.now().toIso8601String()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
console.debug(buff.toString().toJS);
}

@override
void logError(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] ERROR: $message \nTime: ${DateTime.now().toIso8601String()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
console.error(buff.toString().toJS);
}

@override
void logInfo(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] INFO: $message \nTime: ${DateTime.now().toIso8601String()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
console.info(buff.toString().toJS);
}

@override
void logVerbose(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] VERBOSE: $message \nTime: ${DateTime.now().toIso8601String()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
console.log(buff.toString().toJS);
}

@override
void logWarning(message, [Object? exception, StackTrace? stackTrace]) {
final buff = StringBuffer("[DUIT FRAMEWORK] WARNING: $message \nTime: ${DateTime.now().toIso8601String()}");
if (exception != null) {
buff.write("\nException: ${exception.toString()}");
}
if (stackTrace != null) {
buff.write("\nStackTrace: ${stackTrace.toString()}");
}
console.warn(buff.toString().toJS);
}
Comment on lines +14 to +83
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

High code duplication across logging methods.

All six logging methods follow an identical pattern, differing only in the log level prefix and console method. Consider extracting the common logic to a helper method to improve maintainability.

♻️ Refactored approach
void _log(
  String level,
  void Function(JSString) consoleFn,
  Object? message, [
  Object? exception,
  StackTrace? stackTrace,
]) {
  final buff = StringBuffer("[DUIT FRAMEWORK] $level: $message \nTime: ${DateTime.now().toIso8601String()}");
  if (exception != null) {
    buff.write("\nException: $exception");
  }
  if (stackTrace != null) {
    buff.write("\nStackTrace: $stackTrace");
  }
  consoleFn(buff.toString().toJS);
}

@override
void logInfo(message, [Object? exception, StackTrace? stackTrace]) {
  _log("INFO", console.info, message, exception, stackTrace);
}

@override
void logError(message, [Object? exception, StackTrace? stackTrace]) {
  _log("ERROR", console.error, message, exception, stackTrace);
}
// ... etc

Note: The .toString() calls on exception and stackTrace are also redundant since string interpolation handles this automatically.

🤖 Prompt for AI Agents
In @lib/src/capabilities/logging/web.dart around lines 10 - 79, All six logging
methods (logCritical, logDebug, logError, logInfo, logVerbose, logWarning)
duplicate the same string-building and console call pattern; extract the shared
logic into a private helper (e.g., _log) that accepts the level label, the
console function
(console.error/console.debug/console.info/console.log/console.warn), the
message, optional exception and stackTrace, builds the StringBuffer once,
appends exception/stackTrace when non-null (no explicit .toString needed) and
invokes consoleFn(buff.toString().toJS); then replace each public method to call
_log with the appropriate level and console function.

}
Loading
Loading