562 lines
14 KiB
Dart
562 lines
14 KiB
Dart
import 'dart:async';
|
|
import 'dart:collection';
|
|
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:guru_utils/datetime/datetime_utils.dart';
|
|
import 'package:guru_utils/number/number_utils.dart';
|
|
import 'package:guru_utils/random/random_utils.dart';
|
|
import 'package:logger/logger.dart';
|
|
import 'package:persistent/log/persistent_log.dart';
|
|
|
|
import 'dart:developer' as developer;
|
|
|
|
/// Created by Haoyi on 4/13/21
|
|
///
|
|
///
|
|
|
|
class LogLevel {
|
|
static const verbose = 0;
|
|
static const debug = 1;
|
|
static const info = 2;
|
|
static const warning = 3;
|
|
static const error = 4;
|
|
static const wtf = 5;
|
|
static const nothing = -1;
|
|
|
|
static const List<Level> levels = [
|
|
Level.trace,
|
|
Level.debug,
|
|
Level.info,
|
|
Level.warning,
|
|
Level.error,
|
|
Level.fatal,
|
|
];
|
|
}
|
|
|
|
abstract class _TerminalColor {
|
|
String call(String msg) => msg;
|
|
}
|
|
|
|
class CmdColor extends _TerminalColor {
|
|
final String cmd;
|
|
|
|
CmdColor(this.cmd);
|
|
|
|
@override
|
|
String call(String msg) {
|
|
return "$cmd$msg${AnsiColor.ansiDefault}";
|
|
}
|
|
}
|
|
|
|
/// This class handles colorizing of terminal output.
|
|
class AnsiColor extends _TerminalColor {
|
|
/// ANSI Control Sequence Introducer, signals the terminal for new settings.
|
|
static const ansiEsc = '\x1B[';
|
|
|
|
/// Reset all colors and options for current SGRs to terminal defaults.
|
|
static const ansiDefault = '${ansiEsc}0m';
|
|
|
|
final int? fg;
|
|
final int? bg;
|
|
final bool color;
|
|
|
|
AnsiColor.none()
|
|
: fg = null,
|
|
bg = null,
|
|
color = false;
|
|
|
|
AnsiColor.fg(this.fg)
|
|
: bg = null,
|
|
color = true;
|
|
|
|
AnsiColor.bg(this.bg)
|
|
: fg = null,
|
|
color = true;
|
|
|
|
@override
|
|
String toString() {
|
|
if (fg != null) {
|
|
return '${ansiEsc}38;5;${fg}m';
|
|
} else if (bg != null) {
|
|
return '${ansiEsc}48;5;${bg}m';
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
@override
|
|
String call(String msg) {
|
|
if (!kIsWeb && color && Platform.isAndroid) {
|
|
return '${this}$msg$ansiDefault';
|
|
} else {
|
|
return msg;
|
|
}
|
|
}
|
|
|
|
AnsiColor toFg() => AnsiColor.fg(bg);
|
|
|
|
AnsiColor toBg() => AnsiColor.bg(fg);
|
|
|
|
/// Defaults the terminal's foreground color without altering the background.
|
|
String get resetForeground => color ? '${ansiEsc}39m' : '';
|
|
|
|
/// Defaults the terminal's background color without altering the foreground.
|
|
String get resetBackground => color ? '${ansiEsc}49m' : '';
|
|
|
|
static int grey(double level) => 232 + (level.clamp(0.0, 1.0) * 23).round();
|
|
}
|
|
|
|
class LogRecord {
|
|
final int sequence;
|
|
final DateTime time;
|
|
final Level level;
|
|
final String? tag;
|
|
final String msg;
|
|
|
|
const LogRecord(this.sequence, this.time, this.level, this.tag, this.msg);
|
|
}
|
|
|
|
class Log {
|
|
static final errorFgColor = AnsiColor.fg(1);
|
|
|
|
static String _appName = "GAME";
|
|
|
|
static final levelFgColors = [
|
|
AnsiColor.none(),
|
|
AnsiColor.fg(33),
|
|
AnsiColor.fg(40),
|
|
AnsiColor.fg(208),
|
|
AnsiColor.fg(1),
|
|
AnsiColor.fg(129),
|
|
];
|
|
|
|
static final tagCandidateColor = [
|
|
AnsiColor.fg(1),
|
|
AnsiColor.fg(2),
|
|
AnsiColor.fg(3),
|
|
AnsiColor.fg(4),
|
|
AnsiColor.fg(5),
|
|
AnsiColor.fg(6),
|
|
AnsiColor.fg(9),
|
|
AnsiColor.fg(10),
|
|
AnsiColor.fg(11),
|
|
AnsiColor.fg(12),
|
|
AnsiColor.fg(13),
|
|
AnsiColor.fg(14),
|
|
AnsiColor.fg(28),
|
|
AnsiColor.fg(29),
|
|
AnsiColor.fg(30),
|
|
AnsiColor.fg(31),
|
|
AnsiColor.fg(32),
|
|
AnsiColor.fg(33),
|
|
AnsiColor.fg(34),
|
|
AnsiColor.fg(35),
|
|
AnsiColor.fg(36),
|
|
AnsiColor.fg(37),
|
|
AnsiColor.fg(38),
|
|
AnsiColor.fg(39),
|
|
AnsiColor.fg(40),
|
|
AnsiColor.fg(41),
|
|
AnsiColor.fg(42),
|
|
AnsiColor.fg(43),
|
|
AnsiColor.fg(44),
|
|
AnsiColor.fg(45),
|
|
AnsiColor.fg(46),
|
|
AnsiColor.fg(47),
|
|
AnsiColor.fg(48),
|
|
AnsiColor.fg(49),
|
|
AnsiColor.fg(50),
|
|
AnsiColor.fg(51),
|
|
AnsiColor.fg(68),
|
|
AnsiColor.fg(69),
|
|
AnsiColor.fg(70),
|
|
AnsiColor.fg(71),
|
|
AnsiColor.fg(72),
|
|
AnsiColor.fg(73),
|
|
AnsiColor.fg(74),
|
|
AnsiColor.fg(75),
|
|
AnsiColor.fg(76),
|
|
AnsiColor.fg(77),
|
|
AnsiColor.fg(78),
|
|
AnsiColor.fg(79),
|
|
AnsiColor.fg(80),
|
|
AnsiColor.fg(81),
|
|
AnsiColor.fg(82),
|
|
AnsiColor.fg(83),
|
|
AnsiColor.fg(84),
|
|
AnsiColor.fg(85),
|
|
AnsiColor.fg(86),
|
|
AnsiColor.fg(87),
|
|
AnsiColor.fg(88),
|
|
AnsiColor.fg(89),
|
|
AnsiColor.fg(90),
|
|
AnsiColor.fg(91),
|
|
AnsiColor.fg(92),
|
|
AnsiColor.fg(93),
|
|
AnsiColor.fg(94),
|
|
AnsiColor.fg(95),
|
|
AnsiColor.fg(96),
|
|
AnsiColor.fg(97),
|
|
AnsiColor.fg(98),
|
|
AnsiColor.fg(99),
|
|
AnsiColor.fg(100),
|
|
AnsiColor.fg(101),
|
|
AnsiColor.fg(102),
|
|
AnsiColor.fg(103),
|
|
AnsiColor.fg(104),
|
|
AnsiColor.fg(105),
|
|
AnsiColor.fg(106),
|
|
AnsiColor.fg(107),
|
|
AnsiColor.fg(108),
|
|
AnsiColor.fg(109),
|
|
AnsiColor.fg(110),
|
|
AnsiColor.fg(111),
|
|
AnsiColor.fg(112),
|
|
AnsiColor.fg(113),
|
|
AnsiColor.fg(114),
|
|
AnsiColor.fg(115),
|
|
AnsiColor.fg(116),
|
|
AnsiColor.fg(117),
|
|
AnsiColor.fg(118),
|
|
AnsiColor.fg(119),
|
|
AnsiColor.fg(120),
|
|
AnsiColor.fg(121),
|
|
AnsiColor.fg(122),
|
|
AnsiColor.fg(123),
|
|
AnsiColor.fg(124),
|
|
AnsiColor.fg(125),
|
|
AnsiColor.fg(126),
|
|
AnsiColor.fg(127),
|
|
AnsiColor.fg(128),
|
|
AnsiColor.fg(129),
|
|
AnsiColor.fg(130),
|
|
AnsiColor.fg(131),
|
|
AnsiColor.fg(132),
|
|
AnsiColor.fg(133),
|
|
AnsiColor.fg(134),
|
|
AnsiColor.fg(135),
|
|
AnsiColor.fg(136),
|
|
AnsiColor.fg(137),
|
|
AnsiColor.fg(138),
|
|
AnsiColor.fg(139),
|
|
AnsiColor.fg(140),
|
|
AnsiColor.fg(141),
|
|
AnsiColor.fg(142),
|
|
AnsiColor.fg(143),
|
|
AnsiColor.fg(144),
|
|
AnsiColor.fg(145),
|
|
AnsiColor.fg(146),
|
|
AnsiColor.fg(147),
|
|
AnsiColor.fg(148),
|
|
AnsiColor.fg(149),
|
|
AnsiColor.fg(150),
|
|
AnsiColor.fg(151),
|
|
AnsiColor.fg(152),
|
|
AnsiColor.fg(153),
|
|
AnsiColor.fg(154),
|
|
AnsiColor.fg(155),
|
|
AnsiColor.fg(156),
|
|
AnsiColor.fg(157),
|
|
AnsiColor.fg(158),
|
|
AnsiColor.fg(159),
|
|
AnsiColor.fg(160),
|
|
AnsiColor.fg(161),
|
|
AnsiColor.fg(162),
|
|
AnsiColor.fg(163),
|
|
AnsiColor.fg(164),
|
|
AnsiColor.fg(165),
|
|
AnsiColor.fg(166),
|
|
AnsiColor.fg(167),
|
|
AnsiColor.fg(168),
|
|
AnsiColor.fg(169),
|
|
AnsiColor.fg(170),
|
|
AnsiColor.fg(171),
|
|
AnsiColor.fg(172),
|
|
AnsiColor.fg(173),
|
|
AnsiColor.fg(174),
|
|
AnsiColor.fg(175),
|
|
AnsiColor.fg(176),
|
|
AnsiColor.fg(177),
|
|
AnsiColor.fg(178),
|
|
AnsiColor.fg(179),
|
|
AnsiColor.fg(180),
|
|
AnsiColor.fg(181),
|
|
AnsiColor.fg(182),
|
|
AnsiColor.fg(183),
|
|
AnsiColor.fg(184),
|
|
AnsiColor.fg(185),
|
|
AnsiColor.fg(186),
|
|
AnsiColor.fg(187),
|
|
AnsiColor.fg(188),
|
|
AnsiColor.fg(189),
|
|
AnsiColor.fg(190),
|
|
AnsiColor.fg(191),
|
|
AnsiColor.fg(192),
|
|
AnsiColor.fg(193),
|
|
AnsiColor.fg(194),
|
|
AnsiColor.fg(195),
|
|
AnsiColor.fg(196),
|
|
AnsiColor.fg(197),
|
|
AnsiColor.fg(198),
|
|
AnsiColor.fg(199),
|
|
AnsiColor.fg(200),
|
|
AnsiColor.fg(201),
|
|
AnsiColor.fg(202),
|
|
AnsiColor.fg(203),
|
|
AnsiColor.fg(204),
|
|
AnsiColor.fg(205),
|
|
AnsiColor.fg(206),
|
|
AnsiColor.fg(207),
|
|
AnsiColor.fg(208),
|
|
AnsiColor.fg(209),
|
|
AnsiColor.fg(210),
|
|
AnsiColor.fg(211),
|
|
AnsiColor.fg(212),
|
|
AnsiColor.fg(213),
|
|
AnsiColor.fg(214),
|
|
AnsiColor.fg(215),
|
|
AnsiColor.fg(216),
|
|
AnsiColor.fg(217),
|
|
AnsiColor.fg(218),
|
|
AnsiColor.fg(219),
|
|
AnsiColor.fg(220),
|
|
AnsiColor.fg(221),
|
|
AnsiColor.fg(222),
|
|
AnsiColor.fg(223),
|
|
AnsiColor.fg(224),
|
|
AnsiColor.fg(225),
|
|
];
|
|
|
|
static final levelBgColors = [
|
|
AnsiColor.bg(0),
|
|
AnsiColor.bg(27),
|
|
AnsiColor.bg(22),
|
|
AnsiColor.bg(130),
|
|
AnsiColor.bg(1),
|
|
AnsiColor.bg(129),
|
|
];
|
|
|
|
static final levelTags = [
|
|
AnsiColor.bg(0)(" V "),
|
|
AnsiColor.bg(27)(" D "),
|
|
AnsiColor.bg(22)(" I "),
|
|
AnsiColor.bg(130)(" W "),
|
|
AnsiColor.bg(1)(" E "),
|
|
AnsiColor.bg(129)("WTF")
|
|
];
|
|
|
|
static final Map<String, _TerminalColor> tagColors = {};
|
|
|
|
static final Logger _logger = Logger(printer: PrettyPrinter(methodCount: 0));
|
|
static const persistentLogName = "guru_app";
|
|
static int recordCount = 0;
|
|
static final DoubleLinkedQueue<LogRecord> latestLogRecords = DoubleLinkedQueue();
|
|
static final StreamController<LogRecord> _logStream = StreamController.broadcast();
|
|
static bool listening = false;
|
|
|
|
static List<MapEntry<String, String>> dumpRecords({String? filterTag}) {
|
|
return latestLogRecords
|
|
.where((record) => filterTag == null || filterTag == _appName || record.tag == filterTag)
|
|
.map((record) => MapEntry(
|
|
"[${record.sequence}] ${record.tag == null ? "" : "#${record.tag}#"} ${DateTimeUtils.standardDateFormat.format(record.time)}",
|
|
record.msg))
|
|
.toList();
|
|
}
|
|
|
|
static bool isDebug = kDebugMode;
|
|
|
|
static int _persistentLevel = LogLevel.debug;
|
|
|
|
static Stream<LogRecord> get observableLogStream => _logStream.stream;
|
|
|
|
static Future init(String appName,
|
|
{int persistentLogFileSize = 1024 * 1024 * 10,
|
|
int persistentLogCount = 7,
|
|
int persistentLevel = LogLevel.debug}) async {
|
|
Log._appName = appName;
|
|
tagColors[_appName] = CmdColor("${AnsiColor.ansiEsc}33;1m");
|
|
_persistentLevel = persistentLevel;
|
|
if (persistentLevel != LogLevel.nothing) {
|
|
await PersistentLog.createLogger(
|
|
logName: persistentLogName,
|
|
fileSizeLimit: persistentLogFileSize,
|
|
fileCount: persistentLogCount);
|
|
}
|
|
}
|
|
|
|
static void setListen(bool listen) {
|
|
listening = listen;
|
|
}
|
|
|
|
static String _toColorTag(String? tag) {
|
|
if (tag == null) {
|
|
return "";
|
|
}
|
|
_TerminalColor? color = tagColors[tag];
|
|
if (color == null) {
|
|
color = tagCandidateColor[RandomUtils.nextInt(tagCandidateColor.length)];
|
|
tagColors[tag] = color;
|
|
}
|
|
return color(" #$tag#");
|
|
|
|
// "${AnsiColor.ansiEsc}33;1m$name${AnsiColor.ansiDefault}"; 加粗
|
|
// "${AnsiColor.ansiEsc}33;3m$name${AnsiColor.ansiDefault}"; 斜体
|
|
// "${AnsiColor.ansiEsc}33;4m$name${AnsiColor.ansiDefault}"; 下滑线
|
|
return "${AnsiColor.ansiEsc}33;1m$tag${AnsiColor.ansiDefault}";
|
|
}
|
|
|
|
static String _toColorName(String name) {
|
|
if (!kIsWeb && Platform.isAndroid) {
|
|
// "${AnsiColor.ansiEsc}33;1m$name${AnsiColor.ansiDefault}"; 加粗
|
|
// "${AnsiColor.ansiEsc}33;3m$name${AnsiColor.ansiDefault}"; 斜体
|
|
// "${AnsiColor.ansiEsc}33;4m$name${AnsiColor.ansiDefault}"; 下滑线
|
|
return "${AnsiColor.ansiEsc}33;1m$name${AnsiColor.ansiDefault}";
|
|
}
|
|
return name;
|
|
}
|
|
|
|
static String _toDateTime(DateTime dateTime) {
|
|
final humanDt =
|
|
"${NumberUtils.twoDigits(dateTime.hour)}:${NumberUtils.twoDigits(dateTime.minute)}:${NumberUtils.twoDigits(dateTime.second)}.${NumberUtils.threeDigits(dateTime.millisecondsSinceEpoch % 1000)}";
|
|
if (!kIsWeb && Platform.isAndroid) {
|
|
return "${AnsiColor.ansiEsc}36;3m<$humanDt>${AnsiColor.ansiDefault}";
|
|
}
|
|
return "<$humanDt>";
|
|
}
|
|
|
|
static void _log(int level, String msgData,
|
|
{String? tag,
|
|
dynamic error,
|
|
StackTrace? stackTrace,
|
|
bool syncFirebase = false,
|
|
bool persistent = false}) {
|
|
final now = DateTime.now();
|
|
final msg = msgData.substring(0, min(4096, msgData.length));
|
|
recordCount++;
|
|
// latestLogRecords.addFirst(MapEntry("[$recordCount] ${DateTimeUtils.humanTime}", msg));
|
|
final logRecord =
|
|
LogRecord(recordCount, now, LogLevel.levels[level], tag, "$msg${error != null ? '!![$error]!!' : ''}");
|
|
latestLogRecords.addFirst(logRecord);
|
|
if (latestLogRecords.length > 2000) {
|
|
latestLogRecords.removeLast();
|
|
}
|
|
|
|
if (stackTrace != null) {
|
|
_logger.log(LogLevel.levels[level], "[$_appName] ${tag == null ? "" : "#$tag#"} $msg",
|
|
error: error, stackTrace: stackTrace);
|
|
} else {
|
|
final color = levelFgColors[level];
|
|
String output;
|
|
if (error != null) {
|
|
output = "${color(msg)}[${errorFgColor(error.toString())}]";
|
|
} else {
|
|
output = color(msg);
|
|
}
|
|
|
|
if (isDebug) {
|
|
// ignore: avoid_print
|
|
print(
|
|
"${_toColorName("[$_appName]")} ${_toDateTime(now)} ${levelTags[level]}${_toColorTag(tag)} $output");
|
|
}
|
|
}
|
|
|
|
if (!kIsWeb && (level >= _persistentLevel || persistent)) {
|
|
PersistentLog.log(
|
|
logName: persistentLogName, message: "$msg ${error != null ? error.toString() : ''}");
|
|
}
|
|
|
|
if (syncFirebase) {
|
|
// Analytics.instance.logFirebase("$msg ${error != null ? error.toString() : ''}");
|
|
// if (error != null) {
|
|
// if (syncCrashlytics) {
|
|
// Analytics.instance
|
|
// .logException(M2BlocksException(error, cause: error), stacktrace: stackTrace);
|
|
// }
|
|
// } else {
|
|
// Analytics.instance.logFirebase(msg);
|
|
// }
|
|
}
|
|
|
|
if (listening) {
|
|
_logStream.add(logRecord);
|
|
}
|
|
}
|
|
|
|
static void v(String msg,
|
|
{String? tag,
|
|
dynamic error,
|
|
StackTrace? stackTrace,
|
|
bool syncFirebase = false,
|
|
bool syncCrashlytics = false,
|
|
bool persistent = false}) {
|
|
_log(LogLevel.verbose, msg,
|
|
tag: tag,
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
syncFirebase: syncFirebase,
|
|
persistent: persistent);
|
|
}
|
|
|
|
static void d(String msg,
|
|
{String? tag,
|
|
dynamic error,
|
|
StackTrace? stackTrace,
|
|
bool syncFirebase = false,
|
|
bool syncCrashlytics = false,
|
|
bool persistent = false}) {
|
|
_log(LogLevel.debug, msg,
|
|
tag: tag,
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
syncFirebase: syncFirebase,
|
|
persistent: persistent);
|
|
}
|
|
|
|
static void i(String msg,
|
|
{String? tag,
|
|
dynamic error,
|
|
StackTrace? stackTrace,
|
|
bool syncFirebase = false,
|
|
bool syncCrashlytics = false,
|
|
bool persistent = false}) {
|
|
_log(LogLevel.info, msg,
|
|
tag: tag,
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
syncFirebase: syncFirebase,
|
|
persistent: persistent);
|
|
}
|
|
|
|
static void w(String msg,
|
|
{String? tag,
|
|
dynamic error,
|
|
StackTrace? stackTrace,
|
|
bool syncFirebase = false,
|
|
bool syncCrashlytics = false,
|
|
bool persistent = false}) {
|
|
_log(LogLevel.warning, msg,
|
|
tag: tag,
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
syncFirebase: syncFirebase,
|
|
persistent: persistent);
|
|
}
|
|
|
|
static void e(String msg,
|
|
{String? tag,
|
|
dynamic error,
|
|
StackTrace? stackTrace,
|
|
bool syncFirebase = false,
|
|
bool syncCrashlytics = false,
|
|
bool persistent = false}) {
|
|
_log(LogLevel.error, msg,
|
|
tag: tag,
|
|
error: error,
|
|
stackTrace: stackTrace,
|
|
syncFirebase: syncFirebase,
|
|
persistent: persistent);
|
|
}
|
|
}
|