guru_sdk/guru_app/packages/guru_utils/lib/log/log.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);
}
}