Skip to content

Commit

Permalink
feat: logger (#524)
Browse files Browse the repository at this point in the history
## Description
Change of logging approach

## Additional Notes
- Refactor logging to have one logger
- Use logger interface from
[NostrDart](https://github.com/ice-blockchain/nostr-dart/blob/master/lib/src/nostr_dart_logger.dart)
to control logs from app
- Use `TalkerDioLogger` as Interceptor for `ionIdentityClient` to
control logs from app
- Integrate [Talker](https://pub.dev/packages/talker) package for logs
- Add Debug menu, shake 👋 your phone or use hotkeys in simulators(🤖
cmd+m, 🍏 ctrl+cmd+z)
- Refactor feature flags (logs can be on/off by feature flags)
- Now you can export logs and share them

## Type of Change
- [ ] Bug fix
- [x] New feature
- [x] Breaking change
- [x] Refactoring
- [ ] Documentation
- [ ] Chore

## Screenshots
<img
src="https://github.com/user-attachments/assets/cd08b7cd-b799-4e11-beea-005ccf5a992b"
width="240"/>
<img
src="https://github.com/user-attachments/assets/f013d02f-6193-4b1d-9e48-7cf158ce3169"
width="240"/>
<img
src="https://github.com/user-attachments/assets/b81732be-9c2f-4028-8ebf-c888813ebaf4"
width="240"/>
  • Loading branch information
ice-endymion authored Jan 9, 2025
1 parent e487ee7 commit ada521e
Show file tree
Hide file tree
Showing 34 changed files with 421 additions and 274 deletions.
14 changes: 13 additions & 1 deletion android/app/src/main/kotlin/io/ion/app/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,27 @@ package io.ion.app
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import com.banuba.sdk.pe.PhotoCreationActivity
import android.view.KeyEvent
import com.banuba.sdk.pe.BanubaPhotoEditor
import com.banuba.sdk.pe.PhotoCreationActivity
import com.banuba.sdk.pe.data.PhotoEditorConfig
import dev.fluttercommunity.shake_gesture_android.ShakeGesturePlugin
import io.flutter.embedding.android.FlutterActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import java.io.File

class MainActivity : FlutterActivity() {
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_MENU) {
this.flutterEngine?.plugins?.get(ShakeGesturePlugin::class.java).let { plugin ->
if (plugin is ShakeGesturePlugin)
plugin.onShake()
}
}

return super.onKeyDown(keyCode, event)
}

companion object {
// For Photo Editor
Expand Down
12 changes: 9 additions & 3 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ PODS:
- SDWebImage (5.20.0):
- SDWebImage/Core (= 5.20.0)
- SDWebImage/Core (5.20.0)
- shake_gesture_ios (0.0.1):
- Flutter
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
Expand Down Expand Up @@ -241,6 +243,7 @@ DEPENDENCIES:
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
- qr_code_scanner (from `.symlinks/plugins/qr_code_scanner/ios`)
- quill_native_bridge (from `.symlinks/plugins/quill_native_bridge/ios`)
- shake_gesture_ios (from `.symlinks/plugins/shake_gesture_ios/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
Expand Down Expand Up @@ -333,6 +336,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/qr_code_scanner/ios"
quill_native_bridge:
:path: ".symlinks/plugins/quill_native_bridge/ios"
shake_gesture_ios:
:path: ".symlinks/plugins/shake_gesture_ios/ios"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
Expand Down Expand Up @@ -400,9 +405,10 @@ SPEC CHECKSUMS:
qr_code_scanner: d77f94ecc9abf96d9b9b8fc04ef13f611e5a147a
quill_native_bridge: fd2819cf6da02fb6cbf9de37835f96e798e145eb
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
shake_gesture_ios: 64f1f579f314c58445761992a123111b3d7b3492
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
sqlite3: 7559e33dae4c78538df563795af3a86fc887ee71
sqlite3_flutter_libs: f0b59f6bb2a18597d0796558725007e5a7428397
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class DiscoverCreators extends HookConsumerWidget {
final contentCreators = entitiesPagedData?.data.items?.whereType<UserMetadataEntity>();

final hideCreatorsWithoutPicture = ref
.watch(featureFlagsProvider.notifier)
.read(featureFlagsProvider.notifier)
.get(HideCreatorsWithoutPicture.hideCreatorsWithoutPicture);

final filteredCreators = hideCreatorsWithoutPicture
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ class ForceUpdate extends _$ForceUpdate {
}

Future<void> _checkAndUpdateConfig() async {
await ref.read(envProvider.future);

final refetchIntervalInMilliseconds =
ref.read(envProvider.notifier).get<int>(EnvVariable.VERSIONS_CONFIG_REFETCH_INTERVAL);

Expand Down
9 changes: 9 additions & 0 deletions lib/app/features/core/model/feature_flags.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ final class FeedFeatureFlag extends FeatureFlag {
static const showMentionsSuggestions = FeedFeatureFlag._(key: 'showMentionsSuggestions');
}

final class LoggerFeatureFlag extends FeatureFlag {
const LoggerFeatureFlag._({required super.key});

static const logApp = FeedFeatureFlag._(key: 'logApp');
static const logRouters = FeedFeatureFlag._(key: 'logRouters');
static const logNostrDart = FeedFeatureFlag._(key: 'logNostrDart');
static const logIonIdentityClient = FeedFeatureFlag._(key: 'logIonIdentityClient');
}

///
/// TODO: remove this once before production release
/// It hides creators without picture from the discover creators page
Expand Down
11 changes: 7 additions & 4 deletions lib/app/features/core/providers/dio_provider.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

import 'package:dio/dio.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/services/logger/config.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:ion/app/services/logger/logger.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'dio_provider.c.g.dart';

@riverpod
Dio dio(Ref ref) {
final dio = Dio();
if (LoggerConfig.dioLogsEnabled) {
dio.interceptors.add(PrettyDioLogger());

final logger = Logger.talkerDioLogger;

if (logger != null) {
dio.interceptors.add(logger);
}

return dio;
}
37 changes: 10 additions & 27 deletions lib/app/features/core/providers/env_provider.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// ignore_for_file: constant_identifier_names

import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:ion/generated/assets.gen.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'env_provider.c.g.dart';
Expand All @@ -21,35 +20,19 @@ enum EnvVariable {
@Riverpod(keepAlive: true)
class Env extends _$Env {
@override
Future<void> build() async {
await dotenv.load(fileName: Assets.aApp);
final notDefined = _getNotDefined();
if (notDefined.isNotEmpty) {
throw Exception('Invalid ENV value for $notDefined');
}
}

List<EnvVariable> _getNotDefined() {
return EnvVariable.values
.where((EnvVariable element) => dotenv.maybeGet(element.name) == null)
.toList();
}
void build() {}

/// Gets a typed environment variable value.
/// Throws if the variable is not found or cannot be converted to type [T].
T get<T>(EnvVariable variable) {
final value = dotenv.get(variable.name);

if (T == bool) {
return (value.toLowerCase() == 'true') as T;
}

if (T == int) {
return int.parse(value) as T;
}

if (T == double) {
return double.parse(value) as T;
}

return value as T;
return switch (T) {
bool => (value.toLowerCase() == 'true') as T,
int => int.parse(value) as T,
double => double.parse(value) as T,
String => value as T,
_ => throw Exception('Unsupported type $T'),
};
}
}
63 changes: 15 additions & 48 deletions lib/app/features/core/providers/feature_flags_provider.c.dart
Original file line number Diff line number Diff line change
@@ -1,64 +1,31 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:ion/app/features/core/model/feature_flags.dart';
import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/app/features/core/providers/env_provider.c.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'feature_flags_provider.c.g.dart';

abstract class FeatureFlagsService {
const FeatureFlagsService();

bool? get(FeatureFlag flag);

Set<FeatureFlag> get supportedFlags;
}

final class LocalFeatureFlagsService extends FeatureFlagsService {
factory LocalFeatureFlagsService() {
return const LocalFeatureFlagsService._({
@Riverpod(keepAlive: true)
class FeatureFlags extends _$FeatureFlags {
@override
Map<FeatureFlag, bool> build() {
return {
/// Local flags
WalletFeatureFlag.buyNftEnabled: false,
FeedFeatureFlag.showTrendingVideo: false,
FeedFeatureFlag.showMentionsSuggestions: false,
HideCreatorsWithoutPicture.hideCreatorsWithoutPicture: true,
});
}

const LocalFeatureFlagsService._(this._featuresMap);

final Map<FeatureFlag, bool> _featuresMap;

@override
bool? get(FeatureFlag flag) => _featuresMap[flag];

@override
Set<FeatureFlag> get supportedFlags => _featuresMap.keys.toSet();
}

@Riverpod(keepAlive: true)
class FeatureFlags extends _$FeatureFlags {
late final Set<FeatureFlagsService> _services;

@override
Future<void> build() async {
_services = {
LocalFeatureFlagsService(),
/// Log flags
if (ref.watch(envProvider.notifier).get(EnvVariable.SHOW_DEBUG_INFO)) ...{
LoggerFeatureFlag.logApp: true,
LoggerFeatureFlag.logRouters: false,
LoggerFeatureFlag.logNostrDart: true,
LoggerFeatureFlag.logIonIdentityClient: true,
},
};
}

bool get(FeatureFlag flag, {bool defaultValue = false}) {
for (final service in _services) {
if (service.supportedFlags.contains(flag)) {
final value = service.get(flag);

if (value != null) {
return value;
}
}
}

Logger.log('${flag.key} not found.');

return defaultValue;
}
bool get(FeatureFlag flag) => state[flag] ?? false;
}
20 changes: 12 additions & 8 deletions lib/app/features/core/providers/init_provider.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,37 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/features/auth/providers/auth_provider.c.dart';
import 'package:ion/app/features/auth/providers/onboarding_complete_provider.c.dart';
import 'package:ion/app/features/core/model/feature_flags.dart';
import 'package:ion/app/features/core/permissions/providers/permissions_provider.c.dart';
import 'package:ion/app/features/core/providers/env_provider.c.dart';
import 'package:ion/app/features/core/providers/feature_flags_provider.c.dart';
import 'package:ion/app/features/core/providers/template_provider.c.dart';
import 'package:ion/app/features/core/providers/window_manager_provider.c.dart';
import 'package:ion/app/features/wallet/data/coins/domain/coin_initializer.c.dart';
import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/app/services/nostr/nostr.dart';
import 'package:ion/app/services/nostr/nostr_logger.dart';
import 'package:ion/app/services/storage/local_storage.c.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'init_provider.c.g.dart';

@Riverpod(keepAlive: true)
Future<void> initApp(Ref ref) async {
Nostr.initialize();
final featureFlagsNotifier = ref.read(featureFlagsProvider.notifier);
final logApp = featureFlagsNotifier.get(LoggerFeatureFlag.logApp);
final logNostrDart = featureFlagsNotifier.get(LoggerFeatureFlag.logNostrDart);

if (logApp) Logger.init();

Nostr.initialize(logNostrDart ? NostrLogger() : null);

await Future.wait([
ref.read(windowManagerProvider.notifier).show(),
ref.read(envProvider.future),
ref.read(sharedPreferencesProvider.future),
]);

await Future.wait([
ref.read(appTemplateProvider.future),
ref.read(authProvider.future),
ref.read(permissionsProvider.notifier).checkAllPermissions(),
ref.read(coinInitializerProvider).initialize(),
ref.read(onboardingCompleteProvider.future),
]);

await ref.read(onboardingCompleteProvider.future);
}
5 changes: 4 additions & 1 deletion lib/app/features/core/views/pages/error_modal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
import 'package:ion/app/exceptions/exceptions.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/core/providers/env_provider.c.dart';
import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/generated/assets.gen.dart';

class ErrorModal extends ConsumerWidget {
const ErrorModal({required this.error, super.key});
ErrorModal({required this.error, super.key}) {
Logger.error(error);
}

final Object error;

Expand Down
75 changes: 75 additions & 0 deletions lib/app/features/debug/views/debug_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/core/providers/feature_flags_provider.c.dart';
import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
import 'package:ion/app/services/logger/logger.dart';
import 'package:talker_flutter/talker_flutter.dart';

class DebugPage extends ConsumerWidget {
const DebugPage({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final featureFlags = ref.watch(featureFlagsProvider);
final talker = Logger.talker;

return Column(
mainAxisSize: MainAxisSize.min,
children: [
NavigationAppBar.modal(
title: const Text('🐞 Debug'),
showBackButton: false,
actions: const [NavigationCloseButton()],
),
Flexible(
child: Padding(
padding: const EdgeInsets.all(16),
child: ListView(
shrinkWrap: true,
children: [
if (talker != null)
Card(
child: ListTile(
leading: const Icon(Icons.bug_report),
title: const Text('View Debug Logs'),
subtitle: const Text('Check application logs and diagnostics'),
trailing: const Icon(Icons.chevron_right),
onTap: () => Navigator.of(context).push(
MaterialPageRoute<TalkerScreen>(
builder: (context) => TalkerScreen(
talker: talker,
),
),
),
),
),
SizedBox(height: 16.0.s),
ExpansionTile(
title: const Text('Feature Flags'),
children: featureFlags.entries
.map(
(entry) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: ListTile(
title: Text(entry.key.key),
trailing: Icon(
entry.value ? Icons.check_circle : Icons.cancel,
color: entry.value ? Colors.green : Colors.red,
),
),
),
)
.toList(),
),
],
),
),
),
],
);
}
}
Loading

0 comments on commit ada521e

Please sign in to comment.