Skip to content

Commit

Permalink
feat: reintroduce level attribute to Timer.stop
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-vanesyan committed Nov 3, 2024
1 parent 32efc58 commit 2418e03
Show file tree
Hide file tree
Showing 10 changed files with 87 additions and 70 deletions.
Binary file modified docs/assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions envrc.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
if use flake; then
use flake
fi
3 changes: 0 additions & 3 deletions examples/file_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,4 @@ Future<void> main() async {

timer.stop('Aborting...');
context.fatal('Failed to upload!', {const Str('reason', 'Timeout')});

// close handler.
// await handler.close();
}
25 changes: 15 additions & 10 deletions examples/file_rotation_periodic_example.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
library;

import 'dart:math' show Random;

import 'package:strlog/formatters.dart' show TextFormatter;
import 'package:strlog/global_logger.dart' as log;
import 'package:strlog/strlog.dart';
Expand All @@ -10,12 +12,12 @@ final handler = FileHandler(
LogFileRotationPolicy.periodic(Duration(seconds: 30)), // each 30 seconds
path: './file_rotation_periodic_example.log',
formatter: formatter.call);
final logger = Logger.detached()
final _logger = Logger.detached()
..handler = handler
..level = Level.all;

Future<void> main() async {
log.set(logger);
log.set(_logger);

final context = log.withFields({
const Str('username', 'roman-vanesyan'),
Expand All @@ -29,25 +31,28 @@ Future<void> main() async {

// Emulate exponential backoff retry
final maxAttempts = 5;
var cooldown = Duration(seconds: 0);
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
await Future<void>.delayed(cooldown);

try {
final attemptTimer = context.withFields({
Int('attempt', attempt),
}).startTimer('Uploading attempt', level: Level.debug);
}).startTimer('Attempt to upload...', level: Level.debug);

final waitFor = Duration(seconds: (2.5 * attempt).toInt());
await Future<void>.delayed(waitFor);
await Future<void>.delayed(Duration(seconds: Random().nextInt(5)));
cooldown = Duration(seconds: attempt);

// resolve on the last attempt.
if (attempt != maxAttempts) {
attemptTimer.stop('Failed attempt');
context.warn('Failed to upload, retrying...',
{Int('attempt', attempt), Dur('waited_for', waitFor)});
attemptTimer.stop('Failed to upload!',
level: Level.warn,
fields: {Int('attempt', attempt), Dur('next_cooldown', cooldown)});

throw Exception('failed');
throw Exception('Upload failed');
}

attemptTimer.stop('Succeeded');
attemptTimer.stop('Upload succeeded!');

break; // Success, exit loop
} catch (e) {
Expand Down
57 changes: 44 additions & 13 deletions examples/pretty_console_example.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
/// This examples show how to setup prettified console logger.
library;

import 'dart:math' show Random;

import 'package:ansicolor/ansicolor.dart' show AnsiPen;
import 'package:strlog/formatters.dart' show TextFormatter;
import 'package:strlog/handlers.dart' show ConsoleHandler;
import 'package:strlog/strlog.dart' show DefaultLog, Level, Logger, Str, Group;
import 'package:strlog/strlog.dart';
import 'package:strlog/global_logger.dart' as log;

class _Ansi {
static final boldBlack = AnsiPen()..black(bold: true);
Expand Down Expand Up @@ -50,25 +53,53 @@ final _logger = Logger.detached()
..handler = handler;

Future<void> main() async {
var context = _logger.withFields({
const Str('username', 'roman-vanesyan'),
});

context.info('Received upload request');
log.set(_logger);

// Create a new logging context
context = context.withFields({
final context = log.withFields({
const Str('username', 'roman-vanesyan'),
const Group('file', [
Str('name', 'avatar.png'),
Str('mime', 'image/png'),
])
});

final timer = context.startTimer('Uploading!');
final uploadingTimer = context.startTimer('Uploading!', level: Level.info);

// Emulate exponential backoff retry
final maxAttempts = 5;
var cooldown = Duration(seconds: 0);
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
await Future<void>.delayed(cooldown);

try {
final attemptTimer = context.withFields({
Int('attempt', attempt),
}).startTimer('Attempt to upload...', level: Level.debug);

// Emulate uploading, wait for 1 sec.
await Future<void>.delayed(const Duration(seconds: 1));
await Future<void>.delayed(Duration(seconds: Random().nextInt(5)));
cooldown = Duration(seconds: attempt);

// resolve on the last attempt.
if (attempt != maxAttempts) {
attemptTimer.stop('Failed to upload!',
level: Level.warn,
fields: {Int('attempt', attempt), Dur('next_cooldown', cooldown)});

throw Exception('Upload failed');
}

attemptTimer.stop('Upload succeeded!');

break; // Success, exit loop
} catch (e) {
if (attempt == maxAttempts) {
uploadingTimer.stop('Aborting...');
context.error('Failed to upload!', {const Str('reason', 'Timeout')});

rethrow; // Last attempt failed
}
}
}

timer.stop('Aborting...');
context.error('Failed to upload!', {const Str('reason', 'Timeout')});
uploadingTimer.stop('Uploaded!');
}
2 changes: 1 addition & 1 deletion lib/src/noop_logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:strlog/src/timer.dart';
final class NoopTimer implements Timer {
@override
@pragma('vm:prefer-inline')
void stop(String message, [Iterable<Field>? fields]) {}
void stop(String message, {Level? level, Iterable<Field>? fields}) {}
}

final class NoopLogger with LoggerBase {
Expand Down
13 changes: 7 additions & 6 deletions lib/src/timer.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import 'package:strlog/src/field.dart' show Field;
import 'package:strlog/src/level.dart';
import 'package:strlog/src/interface.dart';

/// [Timer] is used to measure time between [Interface.startTimer] and [Timer.stop]
/// calls.
abstract interface class Timer {
/// Stops tracing; immediately emits a record with a [message] and
/// measured time.
///
/// If [stop] is called more than once a [TracerStoppedError]
/// will be raised.
void stop(String message, [Iterable<Field>? fields]);
/// Stops timer and outputs a log record containing a [message] and the measured time between
/// the [Interface.startTimer] call and this [stop] call.
///
/// An optional [level] can be specified to control the output log level, and
/// additional [fields] can be added to provide extra context in the log record.
void stop(String message, {Level? level, Iterable<Field>? fields});
}
4 changes: 2 additions & 2 deletions lib/src/timer_impl.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ class TimerImpl implements Timer {
}

@override
void stop(String message, [Iterable<Field>? fields]) {
void stop(String message, {Level? level, Iterable<Field>? fields}) {
if (_stopwatch.isRunning) {
_stopwatch.stop();
stoppedAt = startedAt.add(_stopwatch.elapsed);
_context.withFields({
DTM('stopped_at', stoppedAt),
Dur('duration', _stopwatch.elapsed),
...?fields
}).log(_level, message);
}).log(level ?? _level, message);
}
}

Expand Down
34 changes: 0 additions & 34 deletions test/all.dart

This file was deleted.

16 changes: 15 additions & 1 deletion test/interface_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -691,14 +691,28 @@ void main() {
..level = Level.all;

const fields = [Str('test1', 'test1'), Str('test2', 'test2')];
logger.startTimer('start').stop('stop', fields);
logger.startTimer('start').stop('stop', fields: fields);

await later(() {
final record = handler.records.elementAt(1);
expect(List<Field>.from(record.fields!), containsAll(fields));
});
});

test('accepts custom level on stop', () async {
final handler = MemoryHandler();
final logger = Logger.detached()
..handler = handler
..level = Level.all;

logger.startTimer('start').stop('stop', level: Level.warn);

await later(() {
final record = handler.records.elementAt(1);
expect(record.level, Level.warn);
});
});

test('allows custom level method extensions', () async {
final handler = MemoryHandler();
final logger = Logger.detached()
Expand Down

0 comments on commit 2418e03

Please sign in to comment.