lognal 1.0.0
lognal: ^1.0.0 copied to clipboard
A terminal-style log viewer drawn on a canvas. Mirror the runtime's logging, read log files, and inspect typed values that expand and collapse.
lognal for Flutter #
lognal is a log viewer widget that looks and behaves like a terminal. It draws log output on a canvas instead of building a widget for every line, so a fast stream of messages and a history of tens of thousands of entries cost the same frame.
This is the pub.flutter-io.cn package. The npm package lives in packages/js, and the documentation for both is at lognal.cdget.com.
What it does #
- Shows what the application already prints.
debugPrintis hooked and put back,printis caught by running your app in a zone, andFlutterError.onErrorputs a failed build in the log with its stack. - Reads log files. One entry per line, from a file on disk or the bytes a browser handed you. The encoding is detected from the byte order mark and from the bytes themselves.
- Displays values by type. Lists, maps, sets and errors expand and collapse, a class that writes
toJson()opens into its properties, and a collection is drawn as a table. - Accepts commands. Connect a handler, and the viewer shows an input line and prints the replies.
- Handles Korean and other CJK text. Wide characters stay on the grid, Korean wraps at spaces, and the input line is a real text field, so an input method composes into it and Enter reaches the command only once the composition is finished.
The viewer has a toolbar (follow new logs, clear, scroll to top and bottom, line wrapping, entry selection, themes, hidden messages, text filter and level filter), a status bar, a search bar that highlights every match without hiding an entry, timestamps, links that open after a confirmation, a menu on each entry for copying and expanding it, a mode that selects whole entries the way a file manager selects files, six color palettes that follow the system by default, and its own scrollbars. Every part can be turned off, and the whole palette is a value you can replace.
Quick start #
flutter pub add lognal
import 'package:flutter/material.dart';
import 'package:lognal/lognal.dart';
final LogStore store = LogStore();
void main() {
// Everything the app prints from here on reaches the viewer, and the terminal.
hookDebugPrint(store);
hookFlutterErrors(store);
runZonedWithLognal(store, () => runApp(const MyApp()));
}
// Wherever the log belongs on screen. Give it a height.
class LogPanel extends StatelessWidget {
const LogPanel({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(height: 400, child: LogViewer(store: store));
}
}
Writing to it from your own code:
final LognalConsole log = LognalConsole(store);
log.info('Connected to %s in %fms', <Object?>['database', 12.5]);
log.warn('Retrying the connection');
log.error(error, const <Object?>[], stackTrace);
log.table(<Map<String, Object?>>[
<String, Object?>{'name': '김철수', 'orders': 12},
]);
Reading a log file:
final ReadTextResult result = await readTextStream(
localTextFile('/var/log/app.log').openRead(),
store,
);
Following one as it grows:
final FollowHandle follow = followTextFile(localTextFile('/var/log/app.log'), store);
await follow.ready;
// … later
follow.stop();
Options #
Everything is on LogViewerOptions, and a LogViewerController holds the state when you want to drive the viewer from your own code:
LogViewer(
store: store,
options: LogViewerOptions(
theme: 'midnight',
locale: 'ko',
timestampFormat: TimestampFormat.datetime,
core: const CoreOptions(maxEntries: 50000, wrap: WrapMode.char),
input: InputOptions(onSubmit: runCommand),
onOpenLink: (String url) => launchUrl(Uri.parse(url)),
),
);
onOpenLink is yours to answer because opening a URL needs a plugin, and which plugin is your application's decision. Without it, links are still drawn and still confirmed; the answer simply goes nowhere.
What differs from the JavaScript package #
The two are the same library, and the core is translated file for file. Three things are different because the runtimes are:
| Case | What Flutter does |
|---|---|
| Opening an object | Dart cannot read the fields of an arbitrary value without reflection, so a class that writes toJson() opens into its properties, one that writes toString() shows what it says, and the rest show their type. |
| A legacy CJK file | UTF-8, UTF-16, Latin-1 and Windows-1252 decode here. euc-kr, shift_jis, big5 and gbk are tables of tens of thousands of characters that a browser already has and Dart does not, so registerTextDecoder takes one from you. |
| A monospace font on the web | Flutter draws with the fonts an application bundles and cannot reach the ones the system has, so a web build needs one of its own: bundle a monospace font and name it in FontSettings. Every other platform resolves its own by name. |
Development #
flutter pub get
flutter test
dart analyze
dart format lib test example/lib
cd example && flutter run # the gallery
See CONTRIBUTING.md for the project layout and the workflow.
License #
MIT © CDGet. The character width and composition tables are generated from the Unicode Character Database, whose license is included beside each generated file.