initial console/file logging framework

This commit is contained in:
austinried 2023-05-14 09:29:27 +09:00
parent 1ae29c5ade
commit 63ff9772e5
9 changed files with 327 additions and 68 deletions

View File

@ -22,6 +22,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -53,6 +55,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -62,32 +66,12 @@
],
"cs": [
"actionsCancel",
"actionsDelete",
"actionsDownload",
"actionsDownloadCancel",
"actionsDownloadDelete",
"actionsOk",
"controlsShuffle",
"resourcesAlbumCount",
"resourcesArtistCount",
"resourcesFilterAlbum",
"resourcesFilterArtist",
"resourcesFilterOwner",
"resourcesFilterYear",
"resourcesPlaylistCount",
"resourcesSongCount",
"resourcesSongListDeleteAllContent",
"resourcesSongListDeleteAllTitle",
"resourcesSortByAlbum",
"resourcesSortByAlbumCount",
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsLicenses",
"settingsAboutActionsProjectHomepage",
"settingsAboutActionsSupport",
"settingsAboutName",
"settingsAboutVersion",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsMusicName",
"settingsMusicOptionsScrobbleDescriptionOff",
"settingsMusicOptionsScrobbleDescriptionOn",
@ -96,12 +80,7 @@
"settingsNetworkOptionsMinBufferTitle",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
"settingsNetworkOptionsStreamFormat",
"settingsNetworkOptionsStreamFormatServerDefault",
"settingsResetActionsClearImageCache",
"settingsResetName",
"settingsServersFieldsName"
"settingsNetworkOptionsOfflineModeOn"
],
"da": [
@ -133,6 +112,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsMusicOptionsScrobbleDescriptionOff",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
@ -163,6 +144,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -187,6 +170,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -218,6 +203,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -226,6 +213,11 @@
"settingsServersFieldsName"
],
"gl": [
"settingsAboutShareLogs",
"settingsAboutChooseLog"
],
"it": [
"actionsCancel",
"actionsDelete",
@ -249,6 +241,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -300,6 +294,8 @@
"settingsAboutActionsLicenses",
"settingsAboutActionsSupport",
"settingsAboutName",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsAboutVersion",
"settingsMusicOptionsScrobbleDescriptionOff",
"settingsMusicOptionsScrobbleDescriptionOn",
@ -356,6 +352,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -387,6 +385,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -418,6 +418,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -427,32 +429,17 @@
],
"pt": [
"actionsCancel",
"actionsDelete",
"actionsDownload",
"actionsDownloadCancel",
"actionsDownloadDelete",
"actionsOk",
"controlsShuffle",
"resourcesAlbumCount",
"resourcesArtistCount",
"resourcesFilterAlbum",
"resourcesFilterArtist",
"resourcesFilterOwner",
"resourcesFilterYear",
"resourcesPlaylistCount",
"resourcesSongCount",
"resourcesSongListDeleteAllContent",
"resourcesSongListDeleteAllTitle",
"resourcesSortByAlbum",
"resourcesSortByAlbumCount",
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
"settingsNetworkOptionsStreamFormat",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsStreamFormatServerDefault",
"settingsServersFieldsName"
],
@ -480,6 +467,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -511,6 +500,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -542,6 +533,8 @@
"resourcesSortByTitle",
"resourcesSortByUpdated",
"settingsAboutActionsSupport",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",
@ -556,6 +549,8 @@
"resourcesArtistCount",
"resourcesPlaylistCount",
"resourcesSongCount",
"settingsAboutShareLogs",
"settingsAboutChooseLog",
"settingsNetworkOptionsOfflineMode",
"settingsNetworkOptionsOfflineModeOff",
"settingsNetworkOptionsOfflineModeOn",

View File

@ -1,11 +1,16 @@
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:path/path.dart' as p;
import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../log.dart';
import '../../models/support.dart';
import '../../services/settings_service.dart';
import '../../state/init.dart';
@ -162,6 +167,54 @@ class _About extends HookConsumerWidget {
mode: LaunchMode.externalApplication,
),
),
const SizedBox(height: 12),
const _ShareLogsButton(),
],
);
}
}
class _ShareLogsButton extends StatelessWidget {
const _ShareLogsButton();
@override
Widget build(BuildContext context) {
final l = AppLocalizations.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
OutlinedButton.icon(
icon: const Icon(Icons.share),
label: Text(l.settingsAboutShareLogs),
onPressed: () async {
final files = await logFiles();
if (files.isEmpty) return;
// ignore: use_build_context_synchronously
final value = await showDialog<String>(
context: context,
builder: (context) => MultipleChoiceDialog<String>(
title: l.settingsAboutChooseLog,
current: files.first.path,
options: files
.map((e) => MultiChoiceOption.string(
title: p.basename(e.path),
option: e.path,
))
.toIList(),
),
);
if (value == null) return;
Share.shareXFiles(
[XFile(value, mimeType: 'text/plain')],
subject: 'Logs from subtracks: ${String.fromCharCodes(
List.generate(8, (_) => Random().nextInt(26) + 65),
)}',
);
},
),
],
);
}

View File

@ -1,7 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:http/http.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../log.dart';
part 'client.g.dart';
const Map<String, String> subtracksHeaders = {
@ -14,11 +15,26 @@ class SubtracksHttpClient extends BaseClient {
@override
Future<StreamedResponse> send(BaseRequest request) {
request.headers.addAll(subtracksHeaders);
if (kDebugMode) print('${request.method} ${request.url}');
log.info('${request.method} ${_redactUri(request.url)}');
return request.send();
}
}
String _redactUri(Uri uri) {
var redacted = uri.toString();
redacted = _redactParam(redacted, 'u');
redacted = _redactParam(redacted, 'p');
redacted = _redactParam(redacted, 's');
redacted = _redactParam(redacted, 't');
return redacted.toString();
}
RegExp _queryReplace(String key) => RegExp('$key=([^&|\\n|\\t\\s]+)');
String _redactParam(String url, String key) =>
url.replaceFirst(_queryReplace(key), '$key=REDACTED');
@Riverpod(keepAlive: true)
BaseClient httpClient(HttpClientRef ref) {
return SubtracksHttpClient();

View File

@ -173,6 +173,10 @@
"@settingsAboutActionsSupport": {},
"settingsAboutName": "About",
"@settingsAboutName": {},
"settingsAboutShareLogs": "Share logs",
"@settingsAboutShareLogs": {},
"settingsAboutChooseLog": "Choose logs file",
"@settingsAboutChooseLog": {},
"settingsAboutVersion": "version {version}",
"@settingsAboutVersion": {
"placeholders": {

169
lib/log.dart Normal file
View File

@ -0,0 +1,169 @@
// import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
class AnsiColor {
/// 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 '';
}
}
String call(String msg) {
if (color) {
// ignore: unnecessary_brace_in_string_interps
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();
}
final levelColors = {
Level.FINEST: AnsiColor.fg(AnsiColor.grey(0.5)),
Level.FINER: AnsiColor.fg(AnsiColor.grey(0.5)),
Level.FINE: AnsiColor.fg(AnsiColor.grey(0.5)),
Level.CONFIG: AnsiColor.fg(81),
Level.INFO: AnsiColor.fg(12),
Level.WARNING: AnsiColor.fg(208),
Level.SEVERE: AnsiColor.fg(196),
Level.SHOUT: AnsiColor.fg(199),
};
class LogData {
final String? message;
final Object? data;
const LogData(this.message, this.data);
}
String _format(
LogRecord event, {
bool color = false,
bool time = true,
bool level = true,
}) {
var message = '';
if (time) message += '${event.time.toIso8601String()} ';
if (level) message += '${event.level.name} ';
final object = event.object;
if (object is LogData) {
message += '${object.message}';
message += '\n${object.data}';
} else if (object != null) {
message += 'Object';
message += '\n$object';
} else {
message += event.message;
}
if (event.error != null) {
message += '\n${event.error}';
}
if (event.stackTrace != null) {
message += '\n${event.stackTrace}';
}
return color
? message.split('\n').map((e) => levelColors[event.level]!(e)).join('\n')
: message;
}
Future<void> _printFile(String event, String dir) async {
final now = DateTime.now();
final file = File(p.join(dir, '${now.year}-${now.month}-${now.day}.txt'));
if (!event.endsWith('\n')) {
event += '\n';
}
await file.writeAsString(event, mode: FileMode.writeOnlyAppend, flush: true);
}
Future<Directory> logDirectory() async {
return Directory(
p.join((await getApplicationDocumentsDirectory()).path, 'logs'),
);
}
Future<List<File>> logFiles() async {
final dir = await logDirectory();
return dir.listSync().whereType<File>().toList()
..sort(
(a, b) => b.statSync().modified.compareTo(a.statSync().modified),
);
}
final log = Logger('default');
Future<void> initLogging() async {
final dir = (await logDirectory())..create();
final files = await logFiles();
if (files.length > 7) {
for (var file in files.slice(7)) {
await file.delete();
}
}
Logger.root.level = kDebugMode ? Level.ALL : Level.INFO;
Logger.root.onRecord.asyncMap((event) async {
if (kDebugMode) {
print(_format(event, color: true, time: false, level: false));
} else {
await _printFile(
_format(event, color: false, time: true, level: true),
dir.path,
);
}
}).listen((_) {}, cancelOnError: false);
log.info('start');
}

View File

@ -4,6 +4,7 @@ import 'package:stack_trace/stack_trace.dart' as stack_trace;
import 'package:worker_manager/worker_manager.dart';
import 'app/app.dart';
import 'log.dart';
void main() async {
// TOOD: probably remove before live
@ -18,5 +19,8 @@ void main() async {
await Executor().warmUp();
WidgetsFlutterBinding.ensureInitialized();
await initLogging();
runApp(const ProviderScope(child: MyApp()));
}

View File

@ -4,7 +4,6 @@ import 'dart:math';
import 'package:audio_service/audio_service.dart';
import 'package:collection/collection.dart';
import 'package:drift/drift.dart' show Value;
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:just_audio/just_audio.dart';
import 'package:pool/pool.dart';
@ -14,6 +13,7 @@ import 'package:synchronized/synchronized.dart';
import '../cache/image_cache.dart';
import '../database/database.dart';
import '../log.dart';
import '../models/music.dart';
import '../models/query.dart';
import '../models/support.dart';
@ -137,7 +137,7 @@ class AudioControl extends BaseAudioHandler with QueueHandler, SeekHandler {
_player.processingStateStream.listen((event) async {
if (event == ProcessingState.completed) {
if (_audioSource.length > 0) {
yell('completed');
log.fine('completed');
await stop();
await seek(Duration.zero);
}
@ -386,7 +386,7 @@ class AudioControl extends BaseAudioHandler with QueueHandler, SeekHandler {
mediaItem.add(slice.current!.mediaItem);
queue.add(list.map((e) => e.mediaItem).toList());
yell('addAll');
log.fine('addAll');
await _audioSource.addAll(list.map((e) => e.audioSource).toList());
await _player.seek(Duration.zero, index: list.indexOf(slice.current!));
}
@ -410,7 +410,7 @@ class AudioControl extends BaseAudioHandler with QueueHandler, SeekHandler {
final sourceNeedsPrev = sourceIndex == 0;
if (sourceNeedsNext && slice.next != null) {
yell('add');
log.fine('add');
await _audioSource.add(slice.next!.audioSource);
}
if (sourceNeedsPrev && slice.prev != null) {
@ -497,7 +497,7 @@ class AudioControl extends BaseAudioHandler with QueueHandler, SeekHandler {
}
Future<void> _insertFirstAudioSource(AudioSource source) {
yell('insert');
log.fine('insert');
final wait = _audioSource.insert(0, source);
_currentIndexIgnore.add(1);
return wait;
@ -505,20 +505,20 @@ class AudioControl extends BaseAudioHandler with QueueHandler, SeekHandler {
Future<void> _pruneAudioSources(int keepIndex) async {
if (keepIndex > 0) {
yell('removeRange 0');
log.fine('removeRange 0');
final wait = _audioSource.removeRange(0, keepIndex);
_currentIndexIgnore.add(0);
await wait;
}
if (_audioSource.length > 1) {
yell('removeRange 1');
log.fine('removeRange 1');
await _audioSource.removeRange(1, _audioSource.length);
}
}
Future<void> _clearAudioSource([bool clearMetadata = false]) async {
// await _player.stop();
yell('_clearAudioSource');
log.fine('_clearAudioSource');
await _audioSource.clear();
if (clearMetadata) {
@ -697,11 +697,3 @@ class AudioControl extends BaseAudioHandler with QueueHandler, SeekHandler {
}
}
}
void yell(String msg) {
if (kDebugMode) {
print('=================================================================<');
print(msg);
print('=================================================================>');
}
}

View File

@ -290,6 +290,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.6.3"
cross_file:
dependency: transitive
description:
name: cross_file
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
url: "https://pub.dev"
source: hosted
version: "0.3.3+4"
crypto:
dependency: "direct main"
description:
@ -679,7 +687,7 @@ packages:
source: hosted
version: "2.0.1"
logging:
dependency: transitive
dependency: "direct main"
description:
name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
@ -834,10 +842,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
url: "https://pub.dev"
source: hosted
version: "2.1.5"
version: "2.1.6"
pedantic:
dependency: transitive
description:
@ -950,6 +958,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.27.7"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: "322a1ec9d9fe07e2e2252c098ce93d12dbd06133cc4c00ffe6a4ef505c295c17"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
shelf:
dependency: transitive
description:
@ -1319,10 +1343,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
version: "4.1.4"
worker_manager:
dependency: "direct main"
description:

View File

@ -57,6 +57,8 @@ dependencies:
connectivity_plus: ^3.0.4
package_info_plus: ^3.1.1
url_launcher: ^6.1.10
logging: ^1.1.1
share_plus: ^7.0.0
# https://github.com/dart-lang/intl/issues/522#issuecomment-1469961807
dependency_overrides: