search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

File sink

The ring buffers live in memory and reset on every launch. TelescopeFileSink keeps a timeline on disk: a rotating set of JSONL files holding the log, event and exception records, readable through the VM service, the telescope:files command, or any tool that reads the directory.

The package looks up no directory. The host passes one, so it chooses where the files live (an app support directory, a temp directory, a path inside a test sandbox).

Start and stop

import 'package:fluttersdk_telescope/telescope.dart';

TelescopeRedaction.redactor = maskSecrets; // required, see "Fail closed"
TelescopePlugin.install();
await TelescopeFileSink.start(
  directory: '${supportDir.path}/telescope',
  maxFileBytes: 1048576, // default: 1 MiB per file
  maxFiles: 8,           // default: keep 8 timeline files
);
Member Meaning
TelescopeFileSink.start({required directory, maxFileBytes = 1048576, maxFiles = 8}) Creates the directory, opens the first file, subscribes to the store, and returns the running sink. A sink already running is stopped first. Throws a FileSystemException when the directory or first file cannot be created; the sink then neither listens nor becomes current.
TelescopeFileSink.current The running sink, or null.
files() [{name, bytes}] for every timeline file in the directory, earlier launches included, newest first.
read(name, {offset = 0, maxBytes = 65536}) Whole lines of one file as {lines, next}. Null when refused.
flush() Writes the batched lines now. Throws a FileSystemException when the write fails.
stop() Flushes, closes the file, and clears current. Safe to call twice.
droppedUnredacted Count of records refused because redacted was false.
writeFailures Count of background batch writes that failed.
pendingLines Lines accepted and not yet handed to the disk queue.

maxFileBytes must be at least 1 and maxFiles at least 1. A single line larger than maxFileBytes still gets a file of its own.

What is written

Every record from onLogRecord, onEventRecord and onExceptionRecord becomes one line:

{"kind": "log", "level": "INFO", "message": "...", "atUs": 81234567, "redacted": true}

kind is log, event or exception; the rest is the record's toJson(). Lines are batched and written at 64 lines or 500 ms, whichever comes first. A new file starts before a line would pass maxFileBytes. Files are named timeline--.jsonl, where the stamp is the UTC launch time (yyyyMMddTHHmmssZ) and n counts rotations within the launch. After each rotation the sink deletes the oldest timeline files in the directory beyond maxFiles, earlier launches included. It only ever touches names its own pattern produces.

The stamp has second precision, so a sink started again within the same second (a restart, or start() while one runs) names the file of the sink it replaced. The new sink appends to that file and counts its existing bytes toward maxFileBytes; it never truncates it.

Fail closed

The sink writes only redacted records. A record whose redacted flag is false, which means it was buffered while no TelescopeRedaction.redactor was registered, is never written and is counted in droppedUnredacted. Register the redactor before the sink starts (see Redaction); with none registered the sink stays empty and the counter climbs.

Write failures

A batch write that no caller awaits (the 64-line or 500 ms flush) and that fails does not raise into the zone, where an exception watcher would record it and the sink would accept that record in turn. The sink counts the failure in writeFailures, stops writing, and is no longer current. Start it again once the cause is fixed. A failure inside an awaited flush() is thrown to the caller instead.

Reading it back

files() and read() expose the timeline files in the directory and nothing else. read compares name against the files files() lists before it ever builds a path, so a path, a .. segment, a link, or any other file in the directory reaches nothing: the answer is null.

read returns whole lines within maxBytes of offset, without their newline, and next, the offset to pass to continue. A line longer than maxBytes is returned whole. At the end of a file lines is empty and next equals offset. A negative offset or a maxBytes below 1 is refused. An offset inside a multi-byte character decodes the cut bytes as U+FFFD rather than throwing. Both calls flush first, so a read sees every accepted line.

VM extensions

Extension Params Answer
ext.telescope.files none {files: [{name, bytes}]}, newest first
ext.telescope.file name, offset (default 0), maxBytes (default 65536) {lines, next}

Both answer {error: "No timeline file sink is running."} when TelescopeFileSink.current is null. ext.telescope.file answers one fixed {error} message for every refusal (unknown name, bad offset, bad maxBytes), so the answer leaks nothing about the file system. The extensions have no MCP tool; reach them through the CLI or the VM service directly.

CLI

./bin/fsa telescope:files                       # list: " " per line
./bin/fsa telescope:files --name=timeline-20261010T091500Z-0.jsonl
./bin/fsa telescope:files --name= --offset=
Option Default Meaning
--name= none Print the lines of this file, as listed, instead of the list.
--offset= 0 Byte offset to start from: 0 or a previous next.

Printing a file ends with next offset: , shown at -v only, so pass -v to page through a long file. An empty directory prints No timeline files. and exits 0. When no sink runs the command prints the extension's error message, not a stack, and exits 1.