Skip to content

feat: Adds serverpod logging package#115

Merged
Crazelu merged 6 commits into
serverpod:mainfrom
Crazelu:feat/logging-package
Jun 4, 2026
Merged

feat: Adds serverpod logging package#115
Crazelu merged 6 commits into
serverpod:mainfrom
Crazelu:feat/logging-package

Conversation

@Crazelu
Copy link
Copy Markdown
Contributor

@Crazelu Crazelu commented Jun 3, 2026

This PR adds a serverpod_logging package that contains tools for bridging logging between Serverpod framework and CLI tooling.

Summary by CodeRabbit

  • New Features

    • Introduced the serverpod_logging package: global logger, severity levels, scoped progress tracking, interactive spinners, and text output; test helpers for assertions.
  • Documentation

    • Added package README and initial CHANGELOG (v0.0.1).
  • Tests

    • Added tests covering scoped progress behavior and error handling.
  • Chores

    • Added package manifest, analysis config, and .gitignore for the new package.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 3, 2026

Review Change Stack

Warning

Review limit reached

@Crazelu, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 52 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 414c96f4-8896-4efc-80df-0a4278413195

📥 Commits

Reviewing files that changed from the base of the PR and between 8882575 and 15f6d84.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • packages/serverpod_logging/analysis_options.yaml
  • packages/serverpod_logging/lib/serverpod_logging.dart
  • packages/serverpod_logging/pubspec.yaml
📝 Walkthrough

Walkthrough

This PR adds a new serverpod_logging package: core logging types and writers, an async-safe Log orchestration with Zone-scoped progress(), interactive spinner and text writers, a test writer, global singletons, package metadata/config, and unit tests for progress behavior.

Changes

Serverpod Logging Package

Layer / File(s) Summary
Logging data model and writer abstraction
lib/src/log_types.dart
LogLevel enum, LogScope hierarchical context, LogEntry record, LogWriter abstract interface, and MultiLogWriter fanout dispatcher.
Log class and convenience & scoping extensions
lib/src/log.dart
Log resolves active scope from Zone (root fallback), filters by logLevel, serializes writes via a chained Future, provides flush/close, LogConvenience shortcuts (debug/info/warning/error), and LogScoping.progress() to run async operations inside child scopes with duration/success/error capture.
Interactive terminal spinner writer
lib/src/spinner_log_writer.dart
SpinnerLogWriter renders braille-frame spinner lines for interactive terminals, manages ActiveScope stopwatches, periodic redraw timer, ANSI clearing/redraw, and emits plain start/end messages for non-interactive outputs.
Formatted text output writer
lib/src/text_log_writer.dart
TextLogWriter extends SpinnerLogWriter to format level-prefixed lines with optional ANSI colors, route error-level output to stderr (including error/stackTrace), and format spinner/completion messages with elapsed durations and icons.
Test assertion writer
lib/src/test_log_writer.dart
TestLogWriter records LogEntry entries, opened scopes, and ClosedScope records to in-memory lists for assertions.
Global logging singletons
lib/src/global_log.dart
Exports logWriter (MultiLogWriter) and a stable log instance configured with default level info; package consumers mutate logWriter to add/remove writers.
Public API barrel and package metadata
lib/serverpod_logging.dart, pubspec.yaml, .gitignore, CHANGELOG.md, README.md, analysis_options.yaml
Barrel export of public API, package manifest (0.0.1) and dev deps, gitignore, changelog, README, and analysis options added.
Log.progress scoping tests
test/log_progress_test.dart
Tests verifying progress() closes scopes with correct success for boolean and generic results, respects isSuccess predicate, and rethrows while recording errors on exceptions.
CI matrices update
.github/workflows/ci.yml
Adds serverpod_logging to dart_format and dart_analyze_test job package matrices.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • christerswahn
  • vlidholt
  • nielsenko
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Adds serverpod logging package' accurately describes the main change—the introduction of a new logging package for Serverpod with all supporting infrastructure, tests, and CI configuration.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Crazelu Crazelu marked this pull request as ready for review June 3, 2026 20:28
@Crazelu Crazelu requested a review from nielsenko June 3, 2026 20:28
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
packages/serverpod_logging/lib/src/spinner_log_writer.dart (1)

113-123: ⚡ Quick win

Override close() to cancel the periodic timer.

_timer is only stopped when scopeStack empties via closeScope. If the writer is disposed while scopes remain open (e.g., abnormal shutdown or a progress that never closes), the Timer.periodic keeps firing and can keep the event loop alive. LogWriter.close() exists for exactly this resource-release purpose but isn't overridden here.

♻️ Proposed cleanup hook
   void _stopTimer() {
     _timer?.cancel();
     _timer = null;
   }
+
+  `@override`
+  Future<void> close() async {
+    _stopTimer();
+    await super.close();
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serverpod_logging/lib/src/spinner_log_writer.dart` around lines 113
- 123, The class starts a periodic Timer via _startTimer and only cancels it in
_stopTimer when scopeStack is emptied via closeScope; override the
LogWriter.close() method to ensure any remaining timer is cancelled on writer
disposal by calling _stopTimer() (and any other cleanup like draining scopeStack
if needed) so the periodic Timer (_timer) cannot keep firing after the writer is
closed; update the class to implement close() that calls _stopTimer() and then
calls super.close() or existing disposal logic.
packages/serverpod_logging/lib/src/log.dart (1)

70-132: 💤 Low value

Consider adding a fatal() convenience method for completeness.

LogLevel.fatal exists but has no corresponding convenience method in LogConvenience. If fatal-level logging is expected to be used, consider adding:

void fatal(
  String message, {
  Object? error,
  StackTrace? stackTrace,
  Map<String, Object?>? metadata,
}) => this(
  LogLevel.fatal,
  () => LogEntry(
    time: DateTime.now(),
    level: LogLevel.fatal,
    message: message,
    scope: currentScope,
    error: error,
    stackTrace: stackTrace,
    metadata: metadata,
  ),
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serverpod_logging/lib/src/log.dart` around lines 70 - 132, Add a
fatal() convenience method to the LogConvenience extension that mirrors the
error() method but uses LogLevel.fatal; implement it on the extension
(LogConvenience) so it calls this(LogLevel.fatal, () => LogEntry(...)) with
time: DateTime.now(), level: LogLevel.fatal, message, scope: currentScope and
optional error, stackTrace, and metadata parameters, and also ensure
isDebugEnabled remains unchanged.
packages/serverpod_logging/lib/src/log_types.dart (1)

52-57: 💤 Low value

Multiple root scopes share the same id: 'root'.

If LogScope.root() is called multiple times (e.g., in tests or parallel processes), all root scopes will have identical id values, which may complicate log correlation or debugging. Consider whether this is intentional or if a unique identifier should be generated.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serverpod_logging/lib/src/log_types.dart` around lines 52 - 57, The
LogScope.root factory is assigning a constant id 'root' causing multiple root
scopes to share the same identifier; change LogScope.root to generate a unique
id (e.g., using a UUID, a monotonic counter, or incorporate
DateTime.now().microsecondsSinceEpoch) instead of the literal 'root' so each
root scope (created via LogScope.root) has a distinct id while keeping label and
startTime as-is; update any related tests or consumers of LogScope.id to accept
the generated unique id.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/serverpod_logging/lib/src/log_types.dart`:
- Around line 143-177: The iteration methods (log, openScope, closeScope, close)
currently iterate directly over the mutable _writers list while add/remove can
mutate it, causing ConcurrentModificationError; fix by taking a snapshot (e.g.,
final writers = List.of(_writers)) at the start of each of LogChain.log,
LogChain.openScope, LogChain.closeScope, and LogChain.close and iterate over
that snapshot so concurrent add/remove calls won’t modify the iterated
collection; ensure add and remove keep their current behavior and no other logic
changes are required.

In `@packages/serverpod_logging/lib/src/log.dart`:
- Around line 51-58: The problem is that call(LogLevel level, LogEntryFactory
factory) defers invoking the factory until the chained future executes, so
scope-dependent values (e.g., currentScope used by LogConvenience.info()) are
captured too late; fix by eagerly evaluating and capturing scope-related data
before queuing: invoke the factory (or at least capture currentScope and any
other mutable context) immediately inside call() and store the resulting
LogEntry (or a closure with captured values) to be passed to _writer.log inside
the _latest.then(...) block, keeping the existing _latest chaining and error
handling with _writer.log(await factory()) replaced by
_writer.log(capturedEntry) or similar so scope is resolved at call time.

In `@packages/serverpod_logging/test/log_progress_test.dart`:
- Around line 5-95: The tests should be reorganized to nest action groups under
each "Given" group: for each existing group block (the three groups around
Log.progress), create an inner group('when ...') that contains the current test
cases, move the test descriptions to start with "then ..." and keep the existing
setUp() and assertions intact; specifically update the tests that call
log.progress (including usages of log.progress<T>, TestLogWriter, and checks of
writer.closedScopes.success/error) so they are inside their respective when
groups, preserving behavior and assertions but renaming the test descriptions to
match "then ..." convention.

---

Nitpick comments:
In `@packages/serverpod_logging/lib/src/log_types.dart`:
- Around line 52-57: The LogScope.root factory is assigning a constant id 'root'
causing multiple root scopes to share the same identifier; change LogScope.root
to generate a unique id (e.g., using a UUID, a monotonic counter, or incorporate
DateTime.now().microsecondsSinceEpoch) instead of the literal 'root' so each
root scope (created via LogScope.root) has a distinct id while keeping label and
startTime as-is; update any related tests or consumers of LogScope.id to accept
the generated unique id.

In `@packages/serverpod_logging/lib/src/log.dart`:
- Around line 70-132: Add a fatal() convenience method to the LogConvenience
extension that mirrors the error() method but uses LogLevel.fatal; implement it
on the extension (LogConvenience) so it calls this(LogLevel.fatal, () =>
LogEntry(...)) with time: DateTime.now(), level: LogLevel.fatal, message, scope:
currentScope and optional error, stackTrace, and metadata parameters, and also
ensure isDebugEnabled remains unchanged.

In `@packages/serverpod_logging/lib/src/spinner_log_writer.dart`:
- Around line 113-123: The class starts a periodic Timer via _startTimer and
only cancels it in _stopTimer when scopeStack is emptied via closeScope;
override the LogWriter.close() method to ensure any remaining timer is cancelled
on writer disposal by calling _stopTimer() (and any other cleanup like draining
scopeStack if needed) so the periodic Timer (_timer) cannot keep firing after
the writer is closed; update the class to implement close() that calls
_stopTimer() and then calls super.close() or existing disposal logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 203e04fc-e698-4487-8233-c3cf401339e2

📥 Commits

Reviewing files that changed from the base of the PR and between fc83eca and 0976162.

📒 Files selected for processing (13)
  • packages/serverpod_logging/.gitignore
  • packages/serverpod_logging/CHANGELOG.md
  • packages/serverpod_logging/README.md
  • packages/serverpod_logging/analysis_options.yaml
  • packages/serverpod_logging/lib/serverpod_logging.dart
  • packages/serverpod_logging/lib/src/global_log.dart
  • packages/serverpod_logging/lib/src/log.dart
  • packages/serverpod_logging/lib/src/log_types.dart
  • packages/serverpod_logging/lib/src/spinner_log_writer.dart
  • packages/serverpod_logging/lib/src/test_log_writer.dart
  • packages/serverpod_logging/lib/src/text_log_writer.dart
  • packages/serverpod_logging/pubspec.yaml
  • packages/serverpod_logging/test/log_progress_test.dart

Comment thread packages/serverpod_logging/lib/src/log_types.dart Outdated
Comment thread packages/serverpod_logging/lib/src/log.dart
Comment thread packages/serverpod_logging/test/log_progress_test.dart
Copy link
Copy Markdown
Contributor

@nielsenko nielsenko left a comment

Choose a reason for hiding this comment

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

Approved. Replied on @CodeRabbit comments

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/serverpod_logging/lib/src/log_types.dart (1)

139-139: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

add method discards the new list — writers are never actually added.

The arrow expression creates [..._writers, writer] but never assigns it to _writers. Since the return type is void, the result is discarded and the method has no effect.

Based on learnings, the copy-on-write pattern requires assignment: _writers = [..._writers, writer].

🐛 Proposed fix
-  void add(LogWriter writer) => [..._writers, writer];
+  void add(LogWriter writer) => _writers = [..._writers, writer];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serverpod_logging/lib/src/log_types.dart` at line 139, The add
method currently constructs a new list with [..._writers, writer] but discards
it, so _writers is never updated; modify the add(LogWriter writer)
implementation to assign the new list back to the backing field (i.e., set
_writers = [..._writers, writer]) so the writer is actually added, preserving
the copy-on-write behavior used by the class.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@packages/serverpod_logging/lib/src/log_types.dart`:
- Line 139: The add method currently constructs a new list with [..._writers,
writer] but discards it, so _writers is never updated; modify the add(LogWriter
writer) implementation to assign the new list back to the backing field (i.e.,
set _writers = [..._writers, writer]) so the writer is actually added,
preserving the copy-on-write behavior used by the class.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9bcd0b1f-8b38-4b43-9a0a-3fe8fcff4ffe

📥 Commits

Reviewing files that changed from the base of the PR and between 0976162 and 8882575.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • packages/serverpod_logging/lib/src/log.dart
  • packages/serverpod_logging/lib/src/log_types.dart
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/ci.yml
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/serverpod_logging/lib/src/log.dart

@Crazelu Crazelu merged commit 10816b0 into serverpod:main Jun 4, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants