refactor: rename internal Fai* design system + fai_ helpers to chain
Some checks failed
Security / Security check (push) Failing after 2s
Some checks failed
Security / Security check (push) Failing after 2s
The Studio design system, widgets and helpers carried a Fai* / fai_ prefix (FaiSpace, FaiColors, FaiTheme, FaiLog, 17 fai_*.dart files, the faiBinary* l10n keys). Studio is the Ch∆In product, so rename them to Chain* / chain_ — carefully preserving English fail/failure/failed. Also fix stale references: the 'fai' binary in l10n strings -> 'chain', FAI_* env vars (FAI_BIN/DATA_DIR/MODULES_DIR/TODAY/BOOTSTRAP_TOKEN) -> CHAIN_*, fai_platform -> fai_chain, fai_hub -> chain_hub. Vendor security-hook tooling (FAI_BANNED_TERMS_FILE) + the .fai bundle ext left. flutter analyze + test: clean (20 passed). Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
parent
68d23ab7dd
commit
891acd2ba2
52 changed files with 1225 additions and 1225 deletions
128
lib/data/chain_log.dart
Normal file
128
lib/data/chain_log.dart
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Central error log for Studio. Every error surfaced to the
|
||||
// operator (SnackBar, error sheet, recoverable dialog) is also
|
||||
// appended here so `chain doctor` and "Open log" can show what
|
||||
// went wrong without the operator having to reproduce the
|
||||
// failure.
|
||||
//
|
||||
// Path: `~/.chain/logs/studio-errors.log` (cross-platform via
|
||||
// HOME / USERPROFILE). One line per event, JSON-shaped so
|
||||
// `chain doctor` can parse it; humans can still read it because
|
||||
// the JSON is single-line and short.
|
||||
//
|
||||
// Best-effort by design: a failed write to the log file must
|
||||
// never break the UI layer that asked us to log. We swallow
|
||||
// IO errors silently — the worst case is the operator loses
|
||||
// the trail of one failure, not the failure itself.
|
||||
//
|
||||
// Rotation: when the file passes 256 KiB we move it to
|
||||
// `studio-errors.log.1` and start fresh. One rotation level is
|
||||
// enough — anything older is noise, and a Studio session
|
||||
// rarely produces more than a few KiB of error text.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class ChainLog {
|
||||
ChainLog._();
|
||||
|
||||
static final ChainLog instance = ChainLog._();
|
||||
|
||||
// Serialise concurrent writes — Future-based mutex so a burst
|
||||
// of errors (rare but happens during failed install retries)
|
||||
// doesn't interleave half-lines on disk.
|
||||
Future<void> _tail = Future.value();
|
||||
|
||||
static const int _rotateBytes = 256 * 1024;
|
||||
|
||||
/// Test seam. Set this in `setUp` to redirect the singleton at
|
||||
/// a per-test temp file; reset to `null` in `tearDown`. Production
|
||||
/// code never touches it — the default getter computes from HOME.
|
||||
@visibleForTesting
|
||||
static String? testPathOverride;
|
||||
|
||||
String get _path {
|
||||
final override = testPathOverride;
|
||||
if (override != null) return override;
|
||||
final home =
|
||||
Platform.environment['HOME'] ??
|
||||
Platform.environment['USERPROFILE'] ??
|
||||
'.';
|
||||
return p.join(home, '.fai', 'logs', 'studio-errors.log');
|
||||
}
|
||||
|
||||
/// Absolute path of the studio-errors log. Public so the
|
||||
/// Doctor page and the in-app log viewer can read it without
|
||||
/// hard-coding the layout twice.
|
||||
String get path => _path;
|
||||
|
||||
/// Append a single error event. The [source] is a short tag
|
||||
/// (`flows.run`, `daemon.logs`, …) that lets a reader group
|
||||
/// related failures. [error] can be any thrown object — gRPC
|
||||
/// codes, plain strings, exceptions; we serialise it via
|
||||
/// [error.toString()] so the on-disk record matches what the
|
||||
/// operator saw on screen.
|
||||
Future<void> error(String source, Object error, {Object? context}) {
|
||||
final entry = <String, Object?>{
|
||||
'ts': DateTime.now().toUtc().toIso8601String(),
|
||||
'level': 'error',
|
||||
'source': source,
|
||||
'error': error.toString(),
|
||||
if (context != null) 'context': context.toString(),
|
||||
};
|
||||
return _append(entry);
|
||||
}
|
||||
|
||||
/// Read the last [maxLines] lines back. Used by `chain doctor`
|
||||
/// + Settings → "Recent errors" view. Returns oldest first so
|
||||
/// the caller can render in chronological order.
|
||||
Future<List<String>> tail({int maxLines = 50}) async {
|
||||
try {
|
||||
final f = File(_path);
|
||||
if (!await f.exists()) return const <String>[];
|
||||
final raw = await f.readAsString();
|
||||
final lines = raw.split('\n').where((l) => l.isNotEmpty).toList();
|
||||
if (lines.length <= maxLines) return lines;
|
||||
return lines.sublist(lines.length - maxLines);
|
||||
} catch (_) {
|
||||
return const <String>[];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _append(Map<String, Object?> entry) {
|
||||
final next = _tail.then((_) async {
|
||||
try {
|
||||
final f = File(_path);
|
||||
final dir = Directory(p.dirname(_path));
|
||||
if (!await dir.exists()) {
|
||||
await dir.create(recursive: true);
|
||||
}
|
||||
// Rotate before write so a hot-loop never balloons past
|
||||
// the cap. Single-level rotation: keep the previous file
|
||||
// as `.1`, anything older drops on the floor.
|
||||
if (await f.exists()) {
|
||||
final size = await f.length();
|
||||
if (size >= _rotateBytes) {
|
||||
final rotated = File('$_path.1');
|
||||
if (await rotated.exists()) {
|
||||
await rotated.delete();
|
||||
}
|
||||
await f.rename(rotated.path);
|
||||
}
|
||||
}
|
||||
await f.writeAsString(
|
||||
'${jsonEncode(entry)}\n',
|
||||
mode: FileMode.append,
|
||||
flush: false,
|
||||
);
|
||||
} catch (_) {
|
||||
// Best-effort. A failed log write must not crash the UI.
|
||||
}
|
||||
});
|
||||
_tail = next.catchError((_) {});
|
||||
return next;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue