Cursor and filters
Follow the log and event buffers without re-reading them or skipping a record: the filters and the cursor of ext.crumbs.console and ext.crumbs.events, the matching CLI flags, and the followCursor loop.
On this page
The parameters belong to the VM Service extensions and the CLI. The MCP descriptors of crumbs_tail and crumbs_events declare only limit (and level on crumbs_tail), but dowel's MCP server forwards a call's arguments unchanged, so since, logger and type work over MCP too and the answer carries the same cursor.
Extension parameters#
All values are strings on the wire, as with every VM Service extension.
| Extension | Parameter | Meaning |
|---|---|---|
ext.crumbs.console |
level |
Minimum package:logging level name. An unknown name matches nothing. |
ext.crumbs.console |
logger |
Exact logger name. |
ext.crumbs.events |
type |
Prefix of eventType. |
| both | since |
Microseconds, exclusive, compared with the record's atUs. |
| both | limit |
Applied after the filters. A zero or negative value returns nothing. |
A since that is not an integer is refused with invalidParams (since must be an integer number of microseconds.) rather than read as "from the start", so a typo cannot return the whole buffer as if it
were new.
Which records limit keeps#
The filters run first, then limit:
- With
since,limitkeeps the OLDEST N records after it. Paging bycursorthen skips nothing. A page cut inside a run of records sharing oneatUskeeps the whole run, so it may exceedlimitby those ties. - Without
since,limitkeeps the NEWEST N records that matched.
Records come back oldest first either way.
The cursor#
Both answers carry cursor: the largest atUs among the returned records, else the since you passed,
else null. Pass it back as since and the next answer holds only newer records.
{
"messages": [
{"level": "INFO", "message": "synced", "loggerName": "Sync", "atUs": 81234567, "redacted": true}
],
"cursor": 81234567
}
ext.crumbs.console answers its records under messages, ext.crumbs.events under events.
Every other read extension keeps its own envelope and takes limit only.
CLI#
crumbs:tail and crumbs:events expose the same parameters as flags:
| Flag | crumbs:tail |
crumbs:events |
Meaning |
|---|---|---|---|
--limit=<n> |
yes (50) | yes (50) | Records to print. |
--level=<name> |
yes | no | Minimum log level. |
--since=<atUs> |
yes | yes | Only records after this atUs. |
--logger=<name> |
yes | no | Exact logger name. |
--type=<prefix> |
no | yes | Event types starting with this prefix. |
--json |
yes | yes | One JSON object per line, the payload as JSON. |
--follow |
yes | yes | Keep polling until interrupted. |
--follow re-reads once a second from the cursor the previous read returned. After the first read it
drops --limit, since the cursor already bounds the read. A null cursor from an empty poll keeps the
previous one, so a quiet buffer never rewinds. Without --json an empty read prints the usual
empty-buffer hint; with --json or --follow it prints nothing.
./bin/dowel crumbs:tail --logger=Sync --json --follow
./bin/dowel crumbs:events --type=Auth --since=81234567
followCursor#
The loop behind --follow is a pure Dart function, exported from package:crumbs/cli.dart
so a command that runs under dart run can build on it:
import 'package:crumbs/cli.dart' show followCursor;
/// [fetch] calls the extension with `since`; [handle] prints the answer. Both are the caller's.
Future<void> follow(
Future<Map<String, dynamic>> Function(int? since) fetch,
void Function(Map<String, dynamic> response) handle,
) =>
followCursor(
interval: const Duration(seconds: 1),
read: (int? since) async {
final response = await fetch(since);
handle(response);
return response['cursor'] as int?; // the next cursor
},
);
| Parameter | Default | Meaning |
|---|---|---|
read |
required | Called with the cursor the previous round returned; returns the next cursor. A null keeps the last one. |
since |
null | The cursor of the first round. |
interval |
1 second | Wait between rounds. |
delay |
real clock | Replaceable wait, so a test runs the loop without a clock. |
shouldStop |
never | Asked after each read; the loop ends when it returns true. By default it ends only when the process does. |
Related Documentation#
- Tool reference: the
crumbs_tailandcrumbs_eventsanswers the cursor belongs to. - Buffers: the
atUsclock and howlimitandminLevelbehave in the store. - File sink: a timeline on disk for reads that must outlive the app.