diff --git a/lib/app/state.dart b/lib/app/state.dart index f40f9672..b2a8c1b9 100755 --- a/lib/app/state.dart +++ b/lib/app/state.dart @@ -1,14 +1,16 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:developer' as developer; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:logging/logging.dart'; import '../../core/rpc.dart'; import '../../core/state.dart'; import 'models.dart'; +final log = Logger('app.state'); + final attachedDevicesProvider = StateNotifierProvider>( (ref) => AttachedDeviceNotifier(ref.watch(rpcProvider))); @@ -32,8 +34,7 @@ class AttachedDeviceNotifier extends StateNotifier> { if (_usbState != scan['state']) { var usbResult = await _rpc.command('get', ['usb']); - developer.log('USB state change', - name: 'controller', error: jsonEncode(usbResult)); + log.info('USB state change', jsonEncode(usbResult)); _usbState = usbResult['data']['state']; diff --git a/lib/core/rpc.dart b/lib/core/rpc.dart index 4a1f5c66..913bc4de 100644 --- a/lib/core/rpc.dart +++ b/lib/core/rpc.dart @@ -1,12 +1,14 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:developer' as developer; +import 'package:logging/logging.dart'; import 'package:async/async.dart'; import 'models.dart'; +final log = Logger('rpc'); + class Signaler { final _send = StreamController(); final _recv = StreamController(); @@ -40,6 +42,21 @@ class _Request { }; } +Level _forPythonName(String name) { + switch (name) { + case 'DEBUG': + return Level.CONFIG; + case 'INFO': + return Level.INFO; + case 'WARNING': + return Level.WARNING; + case 'ERROR': + return Level.SEVERE; + default: + return Level.INFO; + } +} + class RpcSession { final Process _process; final StreamController<_Request> _requests = StreamController(); @@ -50,8 +67,18 @@ class RpcSession { .transform(const Utf8Decoder()) .transform(const LineSplitter()) .map((event) => RpcResponse.fromJson(jsonDecode(event)))) { - stderr.addStream(_process.stderr); - developer.log('Launched ykman subprocess...', name: 'rpc'); + _process.stderr + .transform(const Utf8Decoder()) + .transform(const LineSplitter()) + .map((event) => jsonDecode(event)) + .listen((event) { + Logger('rpc.${event['name']}').log( + _forPythonName(event['level']), + event['message'], + //time: DateTime.fromMillisecondsSinceEpoch(event['time'] * 1000), + ); + }); + log.info('Launched ykman subprocess...'); _pump(); } @@ -68,8 +95,24 @@ class RpcSession { return request.completer.future; } + setLogLevel(Level level) { + String pyLevel; + if (level.value <= Level.CONFIG.value) { + pyLevel = 'debug'; + } else if (level.value <= Level.INFO.value) { + pyLevel = 'info'; + } else if (level.value <= Level.WARNING.value) { + pyLevel = 'warning'; + } else if (level.value <= Level.SEVERE.value) { + pyLevel = 'error'; + } else { + pyLevel = 'critical'; + } + command('logging', [], params: {'level': pyLevel}); + } + void _send(Map data) { - developer.log('SEND', name: 'rpc', error: jsonEncode(data)); + log.fine('SEND', jsonEncode(data)); _process.stdin.writeln(jsonEncode(data)); _process.stdin.flush(); } @@ -85,7 +128,7 @@ class RpcSession { bool completed = false; while (!completed) { final response = await _responses.next; - developer.log('RECV', name: 'rpc', error: jsonEncode(response)); + log.fine('RECV', jsonEncode(response)); response.map( signal: (signal) { request.signal?._recv.sink.add(signal); diff --git a/lib/main.dart b/lib/main.dart index ff98d8e6..48c17323 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,10 @@ import 'dart:io'; - -import 'package:flutter/material.dart'; import 'dart:developer' as developer; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:logging/logging.dart'; import 'app.dart'; import 'core/rpc.dart'; @@ -13,6 +13,8 @@ import 'core/state.dart'; import 'app/main_page.dart'; import 'error_page.dart'; +final log = Logger('main'); + void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -35,14 +37,16 @@ void main() async { prefProvider.overrideWithValue(await SharedPreferences.getInstance()) ]; - developer.log('Starting subprocess: $exe', name: 'main'); + log.info('Starting subprocess: $exe'); try { var rpc = await RpcSession.launch(exe!); - developer.log('ykman process started', name: 'main'); + // Enable logging TODO: Make this configurable + _initLogging(Level.INFO, rpc); + log.info('ykman process started', exe); overrides.add(rpcProvider.overrideWithValue(rpc)); page = const MainPage(); } catch (e) { - developer.log('ykman process failed: $e', name: 'main'); + log.warning('ykman process failed: $e'); page = ErrorPage(error: e.toString()); } @@ -52,6 +56,22 @@ void main() async { )); } +void _initLogging(Level level, RpcSession rpc) { + //TODO: Add support for logging to stderr and file + Logger.root.onRecord.listen((record) { + developer.log( + '${record.level} ${record.message}', + error: record.error, + name: record.loggerName, + time: record.time, + level: record.level.value, + ); + }); + + Logger.root.level = level; + rpc.setLogLevel(level); +} + //TODO: Remove below this class MyApp extends StatelessWidget { diff --git a/lib/oath/state.dart b/lib/oath/state.dart index a36d76fe..0b0e8027 100755 --- a/lib/oath/state.dart +++ b/lib/oath/state.dart @@ -1,15 +1,17 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:developer' as developer; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:logging/logging.dart'; import '../core/state.dart'; import 'models.dart'; +final log = Logger('oath.state'); + final _sessionProvider = Provider.autoDispose.family>( (ref, devicePath) => RpcNodeSession( @@ -34,8 +36,7 @@ class OathStateNotifier extends StateNotifier { refresh() async { var result = await _session.command('get'); - developer.log('application status', - name: 'oath', error: jsonEncode(result)); + log.config('application status', jsonEncode(result)); if (mounted) { state = OathState.fromJson(result['data']); } @@ -47,7 +48,7 @@ class OathStateNotifier extends StateNotifier { var key = result['key']; await _session.command('validate', params: {'key': key}); if (mounted) { - developer.log('UNLOCKED'); + log.config('applet unlocked'); state = state?.copyWith(locked: false); } return true; @@ -117,7 +118,7 @@ class CredentialListNotifier extends StateNotifier?> { await _session.command('code', target: ['accounts', credential.id]); code = OathCode.fromJson(result); } - developer.log('Calculate', name: 'oath', error: jsonEncode(code)); + log.config('Calculate', jsonEncode(code)); if (mounted) { final creds = state!.toList(); final i = creds.indexWhere((e) => e.credential.id == credential.id); @@ -143,9 +144,9 @@ class CredentialListNotifier extends StateNotifier?> { refresh() async { if (_locked) return; - developer.log('refreshing credentials...', name: 'oath'); + log.config('refreshing credentials...'); var result = await _session.command('calculate_all', target: ['accounts']); - developer.log('Entries', name: 'oath', error: jsonEncode(result)); + log.config('Entries', jsonEncode(result)); if (mounted) { var current = state?.toList() ?? []; diff --git a/pubspec.lock b/pubspec.lock index 6c486c24..da197b5f 100755 --- a/pubspec.lock +++ b/pubspec.lock @@ -304,7 +304,7 @@ packages: source: hosted version: "1.0.1" logging: - dependency: transitive + dependency: "direct main" description: name: logging url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index e43c8dfa..d2338317 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: # cupertino_icons: ^1.0.2 async: ^2.8.2 + logging: ^1.0.2 shared_preferences: ^2.0.8 flutter_riverpod: ^1.0.0 json_annotation: ^4.3.0 diff --git a/yubikey-manager b/yubikey-manager index 739f3122..318b0981 160000 --- a/yubikey-manager +++ b/yubikey-manager @@ -1 +1 @@ -Subproject commit 739f3122a43adfcd2549374bc4cd11e88a483a8d +Subproject commit 318b0981d545950a45a8c9b9e17de9332e1f64b5