Skip to content

Commit 2418e03

Browse files
feat: reintroduce level attribute to Timer.stop
1 parent 32efc58 commit 2418e03

File tree

10 files changed

+87
-70
lines changed

10 files changed

+87
-70
lines changed

docs/assets/demo.gif

265 KB
Loading

envrc.local

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
if use flake; then
2+
use flake
3+
fi

examples/file_example.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,4 @@ Future<void> main() async {
2929

3030
timer.stop('Aborting...');
3131
context.fatal('Failed to upload!', {const Str('reason', 'Timeout')});
32-
33-
// close handler.
34-
// await handler.close();
3532
}

examples/file_rotation_periodic_example.dart

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
library;
22

3+
import 'dart:math' show Random;
4+
35
import 'package:strlog/formatters.dart' show TextFormatter;
46
import 'package:strlog/global_logger.dart' as log;
57
import 'package:strlog/strlog.dart';
@@ -10,12 +12,12 @@ final handler = FileHandler(
1012
LogFileRotationPolicy.periodic(Duration(seconds: 30)), // each 30 seconds
1113
path: './file_rotation_periodic_example.log',
1214
formatter: formatter.call);
13-
final logger = Logger.detached()
15+
final _logger = Logger.detached()
1416
..handler = handler
1517
..level = Level.all;
1618

1719
Future<void> main() async {
18-
log.set(logger);
20+
log.set(_logger);
1921

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

3032
// Emulate exponential backoff retry
3133
final maxAttempts = 5;
34+
var cooldown = Duration(seconds: 0);
3235
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
36+
await Future<void>.delayed(cooldown);
37+
3338
try {
3439
final attemptTimer = context.withFields({
3540
Int('attempt', attempt),
36-
}).startTimer('Uploading attempt', level: Level.debug);
41+
}).startTimer('Attempt to upload...', level: Level.debug);
3742

38-
final waitFor = Duration(seconds: (2.5 * attempt).toInt());
39-
await Future<void>.delayed(waitFor);
43+
await Future<void>.delayed(Duration(seconds: Random().nextInt(5)));
44+
cooldown = Duration(seconds: attempt);
4045

4146
// resolve on the last attempt.
4247
if (attempt != maxAttempts) {
43-
attemptTimer.stop('Failed attempt');
44-
context.warn('Failed to upload, retrying...',
45-
{Int('attempt', attempt), Dur('waited_for', waitFor)});
48+
attemptTimer.stop('Failed to upload!',
49+
level: Level.warn,
50+
fields: {Int('attempt', attempt), Dur('next_cooldown', cooldown)});
4651

47-
throw Exception('failed');
52+
throw Exception('Upload failed');
4853
}
4954

50-
attemptTimer.stop('Succeeded');
55+
attemptTimer.stop('Upload succeeded!');
5156

5257
break; // Success, exit loop
5358
} catch (e) {

examples/pretty_console_example.dart

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
/// This examples show how to setup prettified console logger.
22
library;
33

4+
import 'dart:math' show Random;
5+
46
import 'package:ansicolor/ansicolor.dart' show AnsiPen;
57
import 'package:strlog/formatters.dart' show TextFormatter;
68
import 'package:strlog/handlers.dart' show ConsoleHandler;
7-
import 'package:strlog/strlog.dart' show DefaultLog, Level, Logger, Str, Group;
9+
import 'package:strlog/strlog.dart';
10+
import 'package:strlog/global_logger.dart' as log;
811

912
class _Ansi {
1013
static final boldBlack = AnsiPen()..black(bold: true);
@@ -50,25 +53,53 @@ final _logger = Logger.detached()
5053
..handler = handler;
5154

5255
Future<void> main() async {
53-
var context = _logger.withFields({
54-
const Str('username', 'roman-vanesyan'),
55-
});
56-
57-
context.info('Received upload request');
56+
log.set(_logger);
5857

59-
// Create a new logging context
60-
context = context.withFields({
58+
final context = log.withFields({
59+
const Str('username', 'roman-vanesyan'),
6160
const Group('file', [
6261
Str('name', 'avatar.png'),
6362
Str('mime', 'image/png'),
6463
])
6564
});
6665

67-
final timer = context.startTimer('Uploading!');
66+
final uploadingTimer = context.startTimer('Uploading!', level: Level.info);
67+
68+
// Emulate exponential backoff retry
69+
final maxAttempts = 5;
70+
var cooldown = Duration(seconds: 0);
71+
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
72+
await Future<void>.delayed(cooldown);
73+
74+
try {
75+
final attemptTimer = context.withFields({
76+
Int('attempt', attempt),
77+
}).startTimer('Attempt to upload...', level: Level.debug);
6878

69-
// Emulate uploading, wait for 1 sec.
70-
await Future<void>.delayed(const Duration(seconds: 1));
79+
await Future<void>.delayed(Duration(seconds: Random().nextInt(5)));
80+
cooldown = Duration(seconds: attempt);
81+
82+
// resolve on the last attempt.
83+
if (attempt != maxAttempts) {
84+
attemptTimer.stop('Failed to upload!',
85+
level: Level.warn,
86+
fields: {Int('attempt', attempt), Dur('next_cooldown', cooldown)});
87+
88+
throw Exception('Upload failed');
89+
}
90+
91+
attemptTimer.stop('Upload succeeded!');
92+
93+
break; // Success, exit loop
94+
} catch (e) {
95+
if (attempt == maxAttempts) {
96+
uploadingTimer.stop('Aborting...');
97+
context.error('Failed to upload!', {const Str('reason', 'Timeout')});
98+
99+
rethrow; // Last attempt failed
100+
}
101+
}
102+
}
71103

72-
timer.stop('Aborting...');
73-
context.error('Failed to upload!', {const Str('reason', 'Timeout')});
104+
uploadingTimer.stop('Uploaded!');
74105
}

lib/src/noop_logger.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import 'package:strlog/src/timer.dart';
99
final class NoopTimer implements Timer {
1010
@override
1111
@pragma('vm:prefer-inline')
12-
void stop(String message, [Iterable<Field>? fields]) {}
12+
void stop(String message, {Level? level, Iterable<Field>? fields}) {}
1313
}
1414

1515
final class NoopLogger with LoggerBase {

lib/src/timer.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import 'package:strlog/src/field.dart' show Field;
2+
import 'package:strlog/src/level.dart';
23
import 'package:strlog/src/interface.dart';
34

45
/// [Timer] is used to measure time between [Interface.startTimer] and [Timer.stop]
56
/// calls.
67
abstract interface class Timer {
7-
/// Stops tracing; immediately emits a record with a [message] and
8-
/// measured time.
9-
///
10-
/// If [stop] is called more than once a [TracerStoppedError]
11-
/// will be raised.
12-
void stop(String message, [Iterable<Field>? fields]);
8+
/// Stops timer and outputs a log record containing a [message] and the measured time between
9+
/// the [Interface.startTimer] call and this [stop] call.
10+
///
11+
/// An optional [level] can be specified to control the output log level, and
12+
/// additional [fields] can be added to provide extra context in the log record.
13+
void stop(String message, {Level? level, Iterable<Field>? fields});
1314
}

lib/src/timer_impl.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,15 @@ class TimerImpl implements Timer {
2929
}
3030

3131
@override
32-
void stop(String message, [Iterable<Field>? fields]) {
32+
void stop(String message, {Level? level, Iterable<Field>? fields}) {
3333
if (_stopwatch.isRunning) {
3434
_stopwatch.stop();
3535
stoppedAt = startedAt.add(_stopwatch.elapsed);
3636
_context.withFields({
3737
DTM('stopped_at', stoppedAt),
3838
Dur('duration', _stopwatch.elapsed),
3939
...?fields
40-
}).log(_level, message);
40+
}).log(level ?? _level, message);
4141
}
4242
}
4343

test/all.dart

Lines changed: 0 additions & 34 deletions
This file was deleted.

test/interface_test.dart

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,14 +691,28 @@ void main() {
691691
..level = Level.all;
692692

693693
const fields = [Str('test1', 'test1'), Str('test2', 'test2')];
694-
logger.startTimer('start').stop('stop', fields);
694+
logger.startTimer('start').stop('stop', fields: fields);
695695

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

702+
test('accepts custom level on stop', () async {
703+
final handler = MemoryHandler();
704+
final logger = Logger.detached()
705+
..handler = handler
706+
..level = Level.all;
707+
708+
logger.startTimer('start').stop('stop', level: Level.warn);
709+
710+
await later(() {
711+
final record = handler.records.elementAt(1);
712+
expect(record.level, Level.warn);
713+
});
714+
});
715+
702716
test('allows custom level method extensions', () async {
703717
final handler = MemoryHandler();
704718
final logger = Logger.detached()

0 commit comments

Comments
 (0)