added logs

This commit is contained in:
Mike Schwörer 2024-05-26 00:20:25 +02:00
parent 51e89ce901
commit dae5182f90
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
20 changed files with 588 additions and 110 deletions

View File

@ -6,6 +6,7 @@ import 'package:http/http.dart' as http;
import 'package:simplecloudnotifier/models/api_error.dart'; import 'package:simplecloudnotifier/models/api_error.dart';
import 'package:simplecloudnotifier/models/key_token_auth.dart'; import 'package:simplecloudnotifier/models/key_token_auth.dart';
import 'package:simplecloudnotifier/models/user.dart'; import 'package:simplecloudnotifier/models/user.dart';
import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/globals.dart'; import 'package:simplecloudnotifier/state/globals.dart';
import 'package:simplecloudnotifier/state/request_log.dart'; import 'package:simplecloudnotifier/state/request_log.dart';
import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/models/channel.dart';
@ -67,7 +68,8 @@ class APIClient {
responseHeaders = response.headers; responseHeaders = response.headers;
} catch (exc, trace) { } catch (exc, trace) {
RequestLog.addRequestException(name, t0, method, uri, req.body, req.headers, exc, trace); RequestLog.addRequestException(name, t0, method, uri, req.body, req.headers, exc, trace);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context); showPlatformToast(child: Text('Request "${name}" failed'), context: ToastProvider.context);
ApplicationLog.error('Request "${name}" failed: ' + exc.toString(), trace: trace);
rethrow; rethrow;
} }
@ -78,7 +80,9 @@ class APIClient {
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr); RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context); showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
throw Exception(apierr.message); throw Exception(apierr.message);
} catch (_) {} } catch (exc, trace) {
ApplicationLog.warn('Failed to decode api response as error-object', additional: exc.toString() + "\nBody:\n" + responseBody, trace: trace);
}
RequestLog.addRequestErrorStatuscode(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders); RequestLog.addRequestErrorStatuscode(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context); showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
@ -99,6 +103,7 @@ class APIClient {
} catch (exc, trace) { } catch (exc, trace) {
RequestLog.addRequestDecodeError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, exc, trace); RequestLog.addRequestDecodeError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, exc, trace);
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context); showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
ApplicationLog.error('Failed to decode response: ' + exc.toString(), additional: "\nBody:\n" + responseBody, trace: trace);
rethrow; rethrow;
} }
} }

View File

@ -17,29 +17,31 @@ void main() async {
Hive.registerAdapter(SCNRequestAdapter()); Hive.registerAdapter(SCNRequestAdapter());
Hive.registerAdapter(SCNLogAdapter()); Hive.registerAdapter(SCNLogAdapter());
Hive.registerAdapter(SCNLogLevelAdapter());
try { try {
await Hive.openBox<SCNRequest>('scn-requests'); await Hive.openBox<SCNRequest>('scn-requests');
await Hive.openBox<SCNLog>('scn-logs'); } catch (exc, trace) {
} catch (e) {
print(e);
Hive.deleteBoxFromDisk('scn-requests'); Hive.deleteBoxFromDisk('scn-requests');
Hive.deleteBoxFromDisk('scn-logs');
await Hive.openBox<SCNRequest>('scn-requests'); await Hive.openBox<SCNRequest>('scn-requests');
await Hive.openBox<SCNLog>('scn-logs'); ApplicationLog.error('Failed to open Hive-Box: scn-requests: ' + exc.toString(), trace: trace);
} }
try {
await Hive.openBox<SCNLog>('scn-logs');
} catch (exc, trace) {
Hive.deleteBoxFromDisk('scn-logs');
await Hive.openBox<SCNLog>('scn-logs');
ApplicationLog.error('Failed to open Hive-Box: scn-logs: ' + exc.toString(), trace: trace);
}
ApplicationLog.debug('Application started');
runApp( runApp(
MultiProvider( MultiProvider(
providers: [ providers: [
ChangeNotifierProvider( ChangeNotifierProvider(create: (context) => UserAccount(), lazy: false),
create: (context) => UserAccount(), ChangeNotifierProvider(create: (context) => AppTheme(), lazy: false),
lazy: false,
),
ChangeNotifierProvider(
create: (context) => AppTheme(),
lazy: false,
),
], ],
child: const SCNApp(), child: const SCNApp(),
), ),

View File

@ -35,13 +35,13 @@ class Message {
senderUserID: json['sender_user_id'] as String, senderUserID: json['sender_user_id'] as String,
channelInternalName: json['channel_internal_name'] as String, channelInternalName: json['channel_internal_name'] as String,
channelID: json['channel_id'] as String, channelID: json['channel_id'] as String,
senderName: json['sender_name'] as String, senderName: json['sender_name'] as String?,
senderIP: json['sender_ip'] as String, senderIP: json['sender_ip'] as String,
timestamp: json['timestamp'] as String, timestamp: json['timestamp'] as String,
title: json['title'] as String, title: json['title'] as String,
content: json['content'] as String, content: json['content'] as String?,
priority: json['priority'] as int, priority: json['priority'] as int,
userMessageID: json['usr_message_id'] as String, userMessageID: json['usr_message_id'] as String?,
usedKeyID: json['used_key_id'] as String, usedKeyID: json['used_key_id'] as String,
trimmed: json['trimmed'] as bool, trimmed: json['trimmed'] as bool,
); );

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:simplecloudnotifier/components/layout/app_bar.dart'; import 'package:simplecloudnotifier/components/layout/app_bar.dart';
import 'package:simplecloudnotifier/pages/channel_list/root.dart'; import 'package:simplecloudnotifier/pages/channel_list/channel_list.dart';
import 'package:simplecloudnotifier/pages/send/root.dart'; import 'package:simplecloudnotifier/pages/send/root.dart';
import 'package:simplecloudnotifier/components/bottom_fab/fab_bottom_app_bar.dart'; import 'package:simplecloudnotifier/components/bottom_fab/fab_bottom_app_bar.dart';
import 'package:simplecloudnotifier/pages/account/root.dart'; import 'package:simplecloudnotifier/pages/account/root.dart';

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/api/api_client.dart';
import 'package:simplecloudnotifier/models/key_token_auth.dart'; import 'package:simplecloudnotifier/models/key_token_auth.dart';
import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/user_account.dart'; import 'package:simplecloudnotifier/state/user_account.dart';
class AccountLoginPage extends StatefulWidget { class AccountLoginPage extends StatefulWidget {
@ -75,7 +76,7 @@ class _AccountLoginPageState extends State<AccountLoginPage> {
if (verified) { if (verified) {
msgr.showSnackBar( msgr.showSnackBar(
const SnackBar( const SnackBar(
content: Text('Data ok'), content: Text('Data ok'), //TODO use toast?
), ),
); );
prov.setToken(KeyTokenAuth(userId: uid, token: tok)); prov.setToken(KeyTokenAuth(userId: uid, token: tok));
@ -84,12 +85,12 @@ class _AccountLoginPageState extends State<AccountLoginPage> {
} else { } else {
msgr.showSnackBar( msgr.showSnackBar(
const SnackBar( const SnackBar(
content: Text('Failed to verify token'), content: Text('Failed to verify token'), //TODO use toast?
), ),
); );
} }
} catch (e) { } catch (exc, trace) {
//TODO ApplicationLog.error('Failed to verify token: ' + exc.toString(), trace: trace);
} }
} }
} }

View File

@ -3,6 +3,7 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:simplecloudnotifier/api/api_client.dart'; import 'package:simplecloudnotifier/api/api_client.dart';
import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/models/channel.dart';
import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/user_account.dart'; import 'package:simplecloudnotifier/state/user_account.dart';
import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart'; import 'package:simplecloudnotifier/pages/channel_list/channel_list_item.dart';
@ -44,8 +45,9 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
final items = await APIClient.getChannelList(acc.auth!, ChannelSelector.all); final items = await APIClient.getChannelList(acc.auth!, ChannelSelector.all);
_pagingController.appendLastPage(items); _pagingController.appendLastPage(items);
} catch (error) { } catch (exc, trace) {
_pagingController.error = error; _pagingController.error = exc.toString();
ApplicationLog.error('Failed to list channels: ' + exc.toString(), trace: trace);
} }
} }

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:intl/intl.dart';
import 'package:simplecloudnotifier/state/application_log.dart';
class DebugLogsPage extends StatefulWidget { class DebugLogsPage extends StatefulWidget {
@override @override
@ -6,8 +9,97 @@ class DebugLogsPage extends StatefulWidget {
} }
class _DebugLogsPageState extends State<DebugLogsPage> { class _DebugLogsPageState extends State<DebugLogsPage> {
Box<SCNLog> logBox = Hive.box<SCNLog>('scn-logs');
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container(/* Add your UI components here */); return Container(
child: ValueListenableBuilder(
valueListenable: logBox.listenable(),
builder: (context, Box<SCNLog> box, _) {
return ListView.builder(
itemCount: logBox.length,
itemBuilder: (context, listIndex) {
final log = logBox.getAt(logBox.length - listIndex - 1)!;
switch (log.level) {
case SCNLogLevel.debug:
return buildItem(context, log, Theme.of(context).hintColor);
case SCNLogLevel.info:
return buildItem(context, log, Colors.blueAccent);
case SCNLogLevel.warning:
return buildItem(context, log, Colors.orangeAccent);
case SCNLogLevel.error:
return buildItem(context, log, Colors.redAccent);
case SCNLogLevel.fatal:
return buildItem(context, log, Colors.black);
}
},
);
},
),
);
}
Widget buildItem(BuildContext context, SCNLog log, Color tagColor) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 2.0),
child: Card.filled(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.fromLTRB(12, 1, 12, 1),
decoration: BoxDecoration(
color: tagColor,
borderRadius: BorderRadius.only(topLeft: Radius.circular(8)),
),
child: Text(
log.level.name.toUpperCase(),
style: TextStyle(fontWeight: FontWeight.bold, color: Theme.of(context).cardColor, fontSize: 14),
),
),
Expanded(child: SizedBox()),
Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 8, 0),
child: Text(_dateFormat.format(log.timestamp), style: TextStyle(fontSize: 12)),
),
],
),
SizedBox(height: 4),
if (log.message.isNotEmpty)
Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: Text(log.message, style: TextStyle(fontWeight: FontWeight.bold)),
),
if (log.additional.isNotEmpty)
Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: SelectableText(
log.additional,
style: TextStyle(fontSize: 12),
minLines: 1,
maxLines: 10,
),
),
if (log.trace.isNotEmpty)
Padding(
padding: const EdgeInsets.fromLTRB(8, 0, 8, 0),
child: SelectableText(
log.trace,
style: TextStyle(fontSize: 12),
minLines: 1,
maxLines: 10,
),
),
SizedBox(height: 8),
],
),
),
);
} }
} }

View File

@ -1,4 +1,10 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:simplecloudnotifier/pages/debug/debug_persistence_hive.dart';
import 'package:simplecloudnotifier/pages/debug/debug_persistence_sharedprefs.dart';
import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/request_log.dart';
class DebugPersistencePage extends StatefulWidget { class DebugPersistencePage extends StatefulWidget {
@override @override
@ -6,8 +12,78 @@ class DebugPersistencePage extends StatefulWidget {
} }
class _DebugPersistencePageState extends State<DebugPersistencePage> { class _DebugPersistencePageState extends State<DebugPersistencePage> {
SharedPreferences? prefs = null;
@override
void initState() {
super.initState();
SharedPreferences.getInstance().then((value) => setState(() => prefs = value));
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container(/* Add your UI components here */); return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card.outlined(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute<DebugSharedPrefPage>(builder: (context) => DebugSharedPrefPage(sharedPref: prefs!)));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${prefs?.getKeys().length.toString()}', textAlign: TextAlign.end)),
],
),
),
),
),
Card.outlined(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute<DebugHiveBoxPage>(builder: (context) => DebugHiveBoxPage(boxName: 'scn-requests', box: Hive.box<SCNRequest>('scn-requests'))));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Hive [scn-requests]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${Hive.box<SCNRequest>('scn-requests').length.toString()}', textAlign: TextAlign.end)),
],
),
),
),
),
Card.outlined(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute<DebugHiveBoxPage>(builder: (context) => DebugHiveBoxPage(boxName: 'scn-requests', box: Hive.box<SCNLog>('scn-logs'))));
},
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width: 30, child: Text('')),
Expanded(child: Text('Hive [scn-logs]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)),
SizedBox(width: 30, child: Text('${Hive.box<SCNLog>('scn-logs').length.toString()}', textAlign: TextAlign.end)),
],
),
),
),
),
],
),
);
} }
} }

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
import 'package:simplecloudnotifier/pages/debug/debug_persistence_hiveentry.dart';
import 'package:simplecloudnotifier/state/interfaces.dart';
class DebugHiveBoxPage extends StatelessWidget {
final String boxName;
final Box<FieldDebuggable> box;
DebugHiveBoxPage({required this.boxName, required this.box});
@override
Widget build(BuildContext context) {
return SCNScaffold(
title: 'Hive: ' + boxName,
showSearch: false,
showDebug: false,
child: ListView.separated(
itemCount: box.length,
itemBuilder: (context, listIndex) {
return GestureDetector(
onTap: () {
Navigator.push(context, MaterialPageRoute<DebugHiveEntryPage>(builder: (context) => DebugHiveEntryPage(value: box.getAt(listIndex)!)));
},
child: ListTile(
title: Text(box.getAt(listIndex).toString(), style: TextStyle(fontWeight: FontWeight.bold)),
),
);
},
separatorBuilder: (context, index) => Divider(),
),
);
}
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
import 'package:simplecloudnotifier/state/interfaces.dart';
class DebugHiveEntryPage extends StatelessWidget {
final FieldDebuggable value;
final List<(String, String)> fields;
DebugHiveEntryPage({required this.value}) : fields = value.debugFieldList();
@override
Widget build(BuildContext context) {
return SCNScaffold(
title: 'HiveEntry',
showSearch: false,
showDebug: false,
child: ListView.separated(
itemCount: fields.length,
itemBuilder: (context, listIndex) {
return ListTile(
title: Text(fields[listIndex].$1, style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(fields[listIndex].$2, style: TextStyle(fontFamily: "monospace")),
);
},
separatorBuilder: (context, index) => Divider(),
),
);
}
}

View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
class DebugSharedPrefPage extends StatelessWidget {
final SharedPreferences sharedPref;
final List<String> keys;
DebugSharedPrefPage({required this.sharedPref}) : keys = sharedPref.getKeys().toList();
@override
Widget build(BuildContext context) {
return SCNScaffold(
title: 'SharedPreferences',
showSearch: false,
showDebug: false,
child: ListView.separated(
itemCount: sharedPref.getKeys().length,
itemBuilder: (context, listIndex) {
return ListTile(
title: Text(keys[listIndex], style: TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(sharedPref.get(keys[listIndex]).toString()),
);
},
separatorBuilder: (context, index) => Divider(),
),
);
}
}

View File

@ -5,6 +5,7 @@ import 'package:simplecloudnotifier/api/api_client.dart';
import 'package:simplecloudnotifier/models/channel.dart'; import 'package:simplecloudnotifier/models/channel.dart';
import 'package:simplecloudnotifier/models/message.dart'; import 'package:simplecloudnotifier/models/message.dart';
import 'package:simplecloudnotifier/pages/message_view/message_view.dart'; import 'package:simplecloudnotifier/pages/message_view/message_view.dart';
import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/user_account.dart'; import 'package:simplecloudnotifier/state/user_account.dart';
import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart'; import 'package:simplecloudnotifier/pages/message_list/message_list_item.dart';
@ -57,10 +58,9 @@ class _MessageListPageState extends State<MessageListPage> {
} else { } else {
_pagingController.appendPage(newItems, npt); _pagingController.appendPage(newItems, npt);
} }
} catch (error) { } catch (exc, trace) {
print("API-Error: "); //TODO remove me, proper error handling _pagingController.error = exc.toString();
print(error); //TODO remove me, proper error handling ApplicationLog.error('Failed to list messages: ' + exc.toString(), trace: trace);
_pagingController.error = error;
} }
} }

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:simplecloudnotifier/state/user_account.dart'; import 'package:simplecloudnotifier/state/user_account.dart';
@ -151,8 +152,8 @@ class _SendRootPageState extends State<SendRootPage> {
} else { } else {
// TODO ("Cannot open URL"); // TODO ("Cannot open URL");
} }
} catch (e) { } catch (exc, trace) {
// TODO ('Cannot open URL'); ApplicationLog.error('Failed to open URL: ' + exc.toString(), additional: 'URL: ${url}', trace: trace);
} }
} }
} }

View File

@ -1,29 +1,128 @@
import 'package:hive_flutter/hive_flutter.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:simplecloudnotifier/state/interfaces.dart';
import 'package:xid/xid.dart';
part 'application_log.g.dart'; part 'application_log.g.dart';
class ApplicationLog {} class ApplicationLog {
static void debug(String message, {String? additional, StackTrace? trace}) {
print('[DEBUG] ${message}: ${additional ?? ''}');
enum SCNLogLevel { debug, info, warning, error, fatal } Hive.box<SCNLog>('scn-logs').add(SCNLog(
id: Xid().toString(),
timestamp: DateTime.now(),
level: SCNLogLevel.debug,
message: message,
additional: additional ?? '',
trace: trace?.toString() ?? '',
));
}
static void info(String message, {String? additional, StackTrace? trace}) {
print('[INFO] ${message}: ${additional ?? ''}');
Hive.box<SCNLog>('scn-logs').add(SCNLog(
id: Xid().toString(),
timestamp: DateTime.now(),
level: SCNLogLevel.info,
message: message,
additional: additional ?? '',
trace: trace?.toString() ?? '',
));
}
static void warn(String message, {String? additional, StackTrace? trace}) {
print('[WARN] ${message}: ${additional ?? ''}');
Hive.box<SCNLog>('scn-logs').add(SCNLog(
id: Xid().toString(),
timestamp: DateTime.now(),
level: SCNLogLevel.warning,
message: message,
additional: additional ?? '',
trace: trace?.toString() ?? '',
));
}
static void error(String message, {String? additional, StackTrace? trace}) {
print('[ERROR] ${message}: ${additional ?? ''}');
Hive.box<SCNLog>('scn-logs').add(SCNLog(
id: Xid().toString(),
timestamp: DateTime.now(),
level: SCNLogLevel.error,
message: message,
additional: additional ?? '',
trace: trace?.toString() ?? '',
));
}
static void fatal(String message, {String? additional, StackTrace? trace}) {
print('[FATAL] ${message}: ${additional ?? ''}');
Hive.box<SCNLog>('scn-logs').add(SCNLog(
id: Xid().toString(),
timestamp: DateTime.now(),
level: SCNLogLevel.fatal,
message: message,
additional: additional ?? '',
trace: trace?.toString() ?? '',
));
}
}
@HiveType(typeId: 103)
enum SCNLogLevel {
@HiveField(0)
debug,
@HiveField(1)
info,
@HiveField(2)
warning,
@HiveField(3)
error,
@HiveField(4)
fatal
}
@HiveType(typeId: 101) @HiveType(typeId: 101)
class SCNLog extends HiveObject { class SCNLog extends HiveObject implements FieldDebuggable {
@HiveField(0) @HiveField(0)
final String id;
@HiveField(10)
final DateTime timestamp; final DateTime timestamp;
@HiveField(1) @HiveField(11)
final SCNLogLevel level; final SCNLogLevel level;
@HiveField(2) @HiveField(12)
final String message; final String message;
@HiveField(3) @HiveField(13)
final String additional; final String additional;
@HiveField(4) @HiveField(14)
final String trace; final String trace;
SCNLog( SCNLog({
this.timestamp, required this.id,
this.level, required this.timestamp,
this.message, required this.level,
this.additional, required this.message,
this.trace, required this.additional,
); required this.trace,
});
@override
String toString() {
return 'SCNLog[${this.id}]';
}
List<(String, String)> debugFieldList() {
return [
('id', this.id),
('timestamp', this.timestamp.toIso8601String()),
('level', this.level.name),
('message', this.message),
('additional', this.additional),
('trace', this.trace),
];
}
} }

View File

@ -17,27 +17,30 @@ class SCNLogAdapter extends TypeAdapter<SCNLog> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return SCNLog( return SCNLog(
fields[0] as DateTime, id: fields[0] as String,
fields[1] as SCNLogLevel, timestamp: fields[10] as DateTime,
fields[2] as String, level: fields[11] as SCNLogLevel,
fields[3] as String, message: fields[12] as String,
fields[4] as String, additional: fields[13] as String,
trace: fields[14] as String,
); );
} }
@override @override
void write(BinaryWriter writer, SCNLog obj) { void write(BinaryWriter writer, SCNLog obj) {
writer writer
..writeByte(5) ..writeByte(6)
..writeByte(0) ..writeByte(0)
..write(obj.id)
..writeByte(10)
..write(obj.timestamp) ..write(obj.timestamp)
..writeByte(1) ..writeByte(11)
..write(obj.level) ..write(obj.level)
..writeByte(2) ..writeByte(12)
..write(obj.message) ..write(obj.message)
..writeByte(3) ..writeByte(13)
..write(obj.additional) ..write(obj.additional)
..writeByte(4) ..writeByte(14)
..write(obj.trace); ..write(obj.trace);
} }
@ -51,3 +54,57 @@ class SCNLogAdapter extends TypeAdapter<SCNLog> {
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
typeId == other.typeId; typeId == other.typeId;
} }
class SCNLogLevelAdapter extends TypeAdapter<SCNLogLevel> {
@override
final int typeId = 103;
@override
SCNLogLevel read(BinaryReader reader) {
switch (reader.readByte()) {
case 0:
return SCNLogLevel.debug;
case 1:
return SCNLogLevel.info;
case 2:
return SCNLogLevel.warning;
case 3:
return SCNLogLevel.error;
case 4:
return SCNLogLevel.fatal;
default:
return SCNLogLevel.debug;
}
}
@override
void write(BinaryWriter writer, SCNLogLevel obj) {
switch (obj) {
case SCNLogLevel.debug:
writer.writeByte(0);
break;
case SCNLogLevel.info:
writer.writeByte(1);
break;
case SCNLogLevel.warning:
writer.writeByte(2);
break;
case SCNLogLevel.error:
writer.writeByte(3);
break;
case SCNLogLevel.fatal:
writer.writeByte(4);
break;
}
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is SCNLogLevelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@ -0,0 +1,3 @@
abstract class FieldDebuggable {
List<(String, String)> debugFieldList();
}

View File

@ -1,11 +1,14 @@
import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart';
import 'package:simplecloudnotifier/models/api_error.dart'; import 'package:simplecloudnotifier/models/api_error.dart';
import 'package:simplecloudnotifier/state/interfaces.dart';
import 'package:xid/xid.dart';
part 'request_log.g.dart'; part 'request_log.g.dart';
class RequestLog { class RequestLog {
static void addRequestException(String name, DateTime tStart, String method, Uri uri, String reqbody, Map<String, String> reqheaders, dynamic e, StackTrace trace) { static void addRequestException(String name, DateTime tStart, String method, Uri uri, String reqbody, Map<String, String> reqheaders, dynamic e, StackTrace trace) {
Hive.box<SCNRequest>('scn-requests').add(SCNRequest( Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
id: Xid().toString(),
timestampStart: tStart, timestampStart: tStart,
timestampEnd: DateTime.now(), timestampEnd: DateTime.now(),
name: name, name: name,
@ -24,6 +27,7 @@ class RequestLog {
static void addRequestAPIError(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders, APIError apierr) { static void addRequestAPIError(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders, APIError apierr) {
Hive.box<SCNRequest>('scn-requests').add(SCNRequest( Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
id: Xid().toString(),
timestampStart: t0, timestampStart: t0,
timestampEnd: DateTime.now(), timestampEnd: DateTime.now(),
name: name, name: name,
@ -42,6 +46,7 @@ class RequestLog {
static void addRequestErrorStatuscode(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders) { static void addRequestErrorStatuscode(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders) {
Hive.box<SCNRequest>('scn-requests').add(SCNRequest( Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
id: Xid().toString(),
timestampStart: t0, timestampStart: t0,
timestampEnd: DateTime.now(), timestampEnd: DateTime.now(),
name: name, name: name,
@ -60,6 +65,7 @@ class RequestLog {
static void addRequestSuccess(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders) { static void addRequestSuccess(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders) {
Hive.box<SCNRequest>('scn-requests').add(SCNRequest( Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
id: Xid().toString(),
timestampStart: t0, timestampStart: t0,
timestampEnd: DateTime.now(), timestampEnd: DateTime.now(),
name: name, name: name,
@ -78,6 +84,7 @@ class RequestLog {
static void addRequestDecodeError(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders, Object exc, StackTrace trace) { static void addRequestDecodeError(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders, Object exc, StackTrace trace) {
Hive.box<SCNRequest>('scn-requests').add(SCNRequest( Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
id: Xid().toString(),
timestampStart: t0, timestampStart: t0,
timestampEnd: DateTime.now(), timestampEnd: DateTime.now(),
name: name, name: name,
@ -96,37 +103,41 @@ class RequestLog {
} }
@HiveType(typeId: 100) @HiveType(typeId: 100)
class SCNRequest extends HiveObject { class SCNRequest extends HiveObject implements FieldDebuggable {
@HiveField(0) @HiveField(0)
final String id;
@HiveField(10)
final DateTime timestampStart; final DateTime timestampStart;
@HiveField(1) @HiveField(11)
final DateTime timestampEnd; final DateTime timestampEnd;
@HiveField(2) @HiveField(12)
final String name; final String name;
@HiveField(3) @HiveField(13)
final String type; final String type; // SUCCESS | EXCEPTION | API_ERROR | ERROR_STATUSCODE | DECODE_ERROR
@HiveField(4) @HiveField(14)
final String error; final String error;
@HiveField(5) @HiveField(15)
final String stackTrace; final String stackTrace;
@HiveField(6) @HiveField(21)
final String method; final String method;
@HiveField(7) @HiveField(22)
final String url; final String url;
@HiveField(8) @HiveField(23)
final Map<String, String> requestHeaders; final Map<String, String> requestHeaders;
@HiveField(12) @HiveField(24)
final String requestBody; final String requestBody;
@HiveField(9) @HiveField(31)
final int responseStatusCode; final int responseStatusCode;
@HiveField(10) @HiveField(32)
final Map<String, String> responseHeaders; final Map<String, String> responseHeaders;
@HiveField(11) @HiveField(33)
final String responseBody; final String responseBody;
SCNRequest({ SCNRequest({
required this.id,
required this.timestampStart, required this.timestampStart,
required this.timestampEnd, required this.timestampEnd,
required this.name, required this.name,
@ -141,4 +152,28 @@ class SCNRequest extends HiveObject {
required this.error, required this.error,
required this.stackTrace, required this.stackTrace,
}); });
@override
String toString() {
return 'SCNRequest[${this.id}]';
}
List<(String, String)> debugFieldList() {
return [
('id', this.id),
('timestampStart', this.timestampStart.toIso8601String()),
('timestampEnd', this.timestampEnd.toIso8601String()),
('name', this.name),
('method', this.method),
('url', this.url),
for (var (idx, item) in this.requestHeaders.entries.indexed) ('requestHeaders[$idx]', '${item.key}=${item.value}'),
('requestBody', this.requestBody),
('responseStatusCode', this.responseStatusCode.toString()),
for (var (idx, item) in this.responseHeaders.entries.indexed) ('responseHeaders[$idx]', '${item.key}=${item.value}'),
('responseBody', this.responseBody),
('type', this.type),
('error', this.error),
('stackTrace', this.stackTrace),
];
}
} }

View File

@ -17,51 +17,54 @@ class SCNRequestAdapter extends TypeAdapter<SCNRequest> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return SCNRequest( return SCNRequest(
timestampStart: fields[0] as DateTime, id: fields[0] as String,
timestampEnd: fields[1] as DateTime, timestampStart: fields[10] as DateTime,
name: fields[2] as String, timestampEnd: fields[11] as DateTime,
method: fields[6] as String, name: fields[12] as String,
url: fields[7] as String, method: fields[21] as String,
requestHeaders: (fields[8] as Map).cast<String, String>(), url: fields[22] as String,
requestBody: fields[12] as String, requestHeaders: (fields[23] as Map).cast<String, String>(),
responseStatusCode: fields[9] as int, requestBody: fields[24] as String,
responseHeaders: (fields[10] as Map).cast<String, String>(), responseStatusCode: fields[31] as int,
responseBody: fields[11] as String, responseHeaders: (fields[32] as Map).cast<String, String>(),
type: fields[3] as String, responseBody: fields[33] as String,
error: fields[4] as String, type: fields[13] as String,
stackTrace: fields[5] as String, error: fields[14] as String,
stackTrace: fields[15] as String,
); );
} }
@override @override
void write(BinaryWriter writer, SCNRequest obj) { void write(BinaryWriter writer, SCNRequest obj) {
writer writer
..writeByte(13) ..writeByte(14)
..writeByte(0) ..writeByte(0)
..write(obj.timestampStart) ..write(obj.id)
..writeByte(1)
..write(obj.timestampEnd)
..writeByte(2)
..write(obj.name)
..writeByte(3)
..write(obj.type)
..writeByte(4)
..write(obj.error)
..writeByte(5)
..write(obj.stackTrace)
..writeByte(6)
..write(obj.method)
..writeByte(7)
..write(obj.url)
..writeByte(8)
..write(obj.requestHeaders)
..writeByte(12)
..write(obj.requestBody)
..writeByte(9)
..write(obj.responseStatusCode)
..writeByte(10) ..writeByte(10)
..write(obj.responseHeaders) ..write(obj.timestampStart)
..writeByte(11) ..writeByte(11)
..write(obj.timestampEnd)
..writeByte(12)
..write(obj.name)
..writeByte(13)
..write(obj.type)
..writeByte(14)
..write(obj.error)
..writeByte(15)
..write(obj.stackTrace)
..writeByte(21)
..write(obj.method)
..writeByte(22)
..write(obj.url)
..writeByte(23)
..write(obj.requestHeaders)
..writeByte(24)
..write(obj.requestBody)
..writeByte(31)
..write(obj.responseStatusCode)
..writeByte(32)
..write(obj.responseHeaders)
..writeByte(33)
..write(obj.responseBody); ..write(obj.responseBody);
} }

View File

@ -280,7 +280,7 @@ packages:
source: hosted source: hosted
version: "2.3.1" version: "2.3.1"
hive: hive:
dependency: "direct main" dependency: transitive
description: description:
name: hive name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
@ -884,6 +884,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
xid:
dependency: "direct main"
description:
name: xid
sha256: "58b61c7c1234810afa500cde7556d7c407ce72154e0d5a8a5618690f03848dbd"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

View File

@ -10,6 +10,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
font_awesome_flutter: '>= 4.7.0' font_awesome_flutter: '>= 4.7.0'
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
http: ^1.2.0 http: ^1.2.0
@ -20,10 +21,10 @@ dependencies:
infinite_scroll_pagination: ^4.0.0 infinite_scroll_pagination: ^4.0.0
intl: ^0.19.0 intl: ^0.19.0
path_provider: ^2.1.3 path_provider: ^2.1.3
hive: ^2.2.3
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0
package_info_plus: ^8.0.0 package_info_plus: ^8.0.0
fl_toast: ^3.2.0 fl_toast: ^3.2.0
xid: ^1.2.1
dependency_overrides: dependency_overrides: