-
Notifications
You must be signed in to change notification settings - Fork 7
feat: Adds serverpod logging package #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
fb59ef6
feat: adds serverpod logging package
Crazelu 0976162
chore: update typo in README
Crazelu be034d6
feat: adds safety to add and remove methods for MultiLogWriter
Crazelu 8882575
chore: formats code and sets up CI for serverpod_logging package
Crazelu 9500bcc
chore: update ci workflow for serverpod_logging package
Crazelu 15f6d84
chore: update analysis options for serverpod_logging
Crazelu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # https://dart.dev/guides/libraries/private-files | ||
| # Created by `dart pub` | ||
| .dart_tool/ | ||
|
|
||
| # Avoid committing pubspec.lock for library packages; see | ||
| # https://dart.dev/guides/libraries/private-files#pubspeclock. | ||
| pubspec.lock |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ## 0.0.1 | ||
|
|
||
| - Initial version. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
|  | ||
|
|
||
| # Serverpod Logging | ||
|
|
||
| This package contains tools for bridging logging between Serverpod framework and tooling. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| include: package:serverpod_lints/cli.yaml | ||
|
|
||
| analyzer: | ||
| language: | ||
| strict-raw-types: false | ||
| errors: | ||
| inference_failure_on_instance_creation: ignore | ||
| inference_failure_on_function_invocation: ignore | ||
| inference_failure_on_untyped_parameter: ignore | ||
| prefer_final_parameters: ignore | ||
|
|
||
| linter: | ||
| rules: | ||
| prefer_relative_imports: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export 'src/global_log.dart'; | ||
| export 'src/log.dart'; | ||
| export 'src/log_types.dart'; | ||
| export 'src/spinner_log_writer.dart'; | ||
| export 'src/test_log_writer.dart'; | ||
| export 'src/text_log_writer.dart'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import 'log.dart'; | ||
| import 'log_types.dart'; | ||
|
|
||
| /// Global writer chain that backs [log]. Callers configure the chain by | ||
| /// adding writers with [MultiLogWriter.add] and removing them with | ||
| /// [MultiLogWriter.remove]; identity is stable for the process lifetime, | ||
| /// so the chain is shared across any number of [Log] consumers and | ||
| /// framework bootstraps. | ||
| /// | ||
| /// ```dart | ||
| /// logWriter.add(MyLogWriter()); | ||
| /// log.info('Server started'); | ||
| /// ``` | ||
| final MultiLogWriter logWriter = MultiLogWriter([]); | ||
|
|
||
| /// Global [Log] that forwards to [logWriter]. Identity is stable: the | ||
| /// instance is constructed at library init and never reassigned. Entry | ||
| /// points configure logging by mutating [logWriter], not by replacing | ||
| /// [log]. | ||
| final Log log = Log(logWriter, logLevel: LogLevel.info); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| import 'dart:async'; | ||
|
|
||
| import 'log_types.dart'; | ||
|
|
||
| /// Symbol used to store the current [LogScope] in Zone values. | ||
| const Symbol _logScopeKey = #_logScope; | ||
|
|
||
| final _stopwatch = Stopwatch()..start(); | ||
| int _scopeCounter = 0; | ||
| String _newScopeId(String label) => | ||
| '${label.hashCode}_${_stopwatch.elapsedTicks}_${++_scopeCounter}'; | ||
|
|
||
| /// A factory function that creates a [LogEntry]. | ||
| typedef LogEntryFactory = FutureOr<LogEntry> Function(); | ||
|
|
||
| /// A logger that delegates to a [LogWriter] and resolves the current | ||
| /// [LogScope] from the Zone. | ||
| /// | ||
| /// Each call appends onto a rolling `_latest` Future, so writes | ||
| /// serialize in invocation order. [flush] awaits that tail; [close] | ||
| /// does the same and blocks further dispatches. | ||
| /// | ||
| /// ```dart | ||
| /// log.info('Server started'); | ||
| /// await log.progress('Migration', () async { | ||
| /// log.info('Step 1'); | ||
| /// }); | ||
| /// ``` | ||
| class Log { | ||
| final LogWriter _writer; | ||
| Future<void> _latest = Future.value(); | ||
| bool _closed = false; | ||
|
|
||
| /// The minimum severity that will be forwarded to the writer. Calls below | ||
| /// this level short-circuit without invoking the [LogEntryFactory]. May | ||
| /// be changed at runtime (e.g. when a verbose flag is parsed). | ||
| LogLevel logLevel; | ||
|
|
||
| /// Creates a [Log] that forwards to [_writer]. Messages below [logLevel] | ||
| /// are dropped before the [LogEntryFactory] runs. | ||
| Log(this._writer, {this.logLevel = LogLevel.info}); | ||
|
|
||
| /// The current scope from the Zone, or a synthetic root if none. | ||
| LogScope get currentScope => | ||
| Zone.current[_logScopeKey] as LogScope? ?? _fallbackScope; | ||
|
|
||
| static final _fallbackScope = LogScope.root('unknown'); | ||
|
|
||
| /// Checks level, evaluates the factory eagerly, and appends the write | ||
| /// to the chain. Writer errors are swallowed - logging is best-effort. | ||
| void call(LogLevel level, LogEntryFactory factory) { | ||
| if (level.index < logLevel.index || _closed) return; | ||
| _latest = _latest.then((_) async { | ||
| try { | ||
| await _writer.log(await factory()); | ||
| } catch (_) {} | ||
| }); | ||
| } | ||
|
|
||
| /// Awaits in-flight writes without blocking further dispatches. | ||
| Future<void> flush() => _latest; | ||
|
|
||
| /// Awaits in-flight writes and blocks further dispatches. | ||
| Future<void> close() async { | ||
| _closed = true; | ||
| await flush(); | ||
| } | ||
| } | ||
|
|
||
| /// Convenience methods for common log levels. | ||
| extension LogConvenience on Log { | ||
| /// Logs [message] at [LogLevel.debug]. | ||
| void debug(String message, {Map<String, Object?>? metadata}) => this( | ||
| LogLevel.debug, | ||
| () => LogEntry( | ||
| time: DateTime.now(), | ||
| level: LogLevel.debug, | ||
| message: message, | ||
| scope: currentScope, | ||
| metadata: metadata, | ||
| ), | ||
| ); | ||
|
|
||
| /// Logs [message] at [LogLevel.info]. | ||
| void info(String message, {Map<String, Object?>? metadata}) => this( | ||
| LogLevel.info, | ||
| () => LogEntry( | ||
| time: DateTime.now(), | ||
| level: LogLevel.info, | ||
| message: message, | ||
| scope: currentScope, | ||
| metadata: metadata, | ||
| ), | ||
| ); | ||
|
|
||
| /// Logs [message] at [LogLevel.warning]. | ||
| void warning(String message, {Map<String, Object?>? metadata}) => this( | ||
| LogLevel.warning, | ||
| () => LogEntry( | ||
| time: DateTime.now(), | ||
| level: LogLevel.warning, | ||
| message: message, | ||
| scope: currentScope, | ||
| metadata: metadata, | ||
| ), | ||
| ); | ||
|
|
||
| /// Logs [message] at [LogLevel.error], optionally attaching an [error] | ||
| /// value and [stackTrace]. | ||
| void error( | ||
| String message, { | ||
| Object? error, | ||
| StackTrace? stackTrace, | ||
| Map<String, Object?>? metadata, | ||
| }) => this( | ||
| LogLevel.error, | ||
| () => LogEntry( | ||
| time: DateTime.now(), | ||
| level: LogLevel.error, | ||
| message: message, | ||
| scope: currentScope, | ||
| error: error, | ||
| stackTrace: stackTrace, | ||
| metadata: metadata, | ||
| ), | ||
| ); | ||
|
|
||
| /// Whether debug-level messages are currently forwarded to the writer. | ||
| /// Use to gate expensive message construction: | ||
| /// `if (log.isDebugEnabled) log.debug(formatBigObject(x));`. | ||
| bool get isDebugEnabled => logLevel.index <= LogLevel.debug.index; | ||
| } | ||
|
|
||
| /// Scope management: progress operations and manual scope control. | ||
| extension LogScoping on Log { | ||
| /// Runs [runner] inside a new scope. The scope is automatically opened | ||
| /// before the runner and closed after it completes (or fails). | ||
| /// | ||
| /// Log calls inside the runner are automatically scoped via the Zone. | ||
| /// | ||
| /// Success signal: | ||
| /// - If [runner] throws, the scope closes with `success: false`. | ||
| /// - Else if [isSuccess] is provided, its return value is used. | ||
| /// - Else if [T] is `bool`, the returned value is used directly. | ||
| /// - Otherwise, the scope closes with `success: true`. | ||
| Future<T> progress<T>( | ||
| String label, | ||
| FutureOr<T> Function() runner, { | ||
| Map<String, Object?>? metadata, | ||
| bool Function(T result)? isSuccess, | ||
| }) async { | ||
| final scope = currentScope.child( | ||
| id: _newScopeId(label), | ||
| label: label, | ||
| metadata: metadata, | ||
| ); | ||
| await _writer.openScope(scope); | ||
| final stopwatch = Stopwatch()..start(); | ||
| try { | ||
| final result = await runZoned(runner, zoneValues: {_logScopeKey: scope}); | ||
| await _writer.closeScope( | ||
| scope, | ||
| success: isSuccess?.call(result) ?? (result is bool ? result : true), | ||
| duration: stopwatch.elapsed, | ||
| ); | ||
| return result; | ||
| } catch (e, st) { | ||
| await _writer.closeScope( | ||
| scope, | ||
| success: false, | ||
| duration: stopwatch.elapsed, | ||
| error: e, | ||
| stackTrace: st, | ||
| ); | ||
| rethrow; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.