0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-02-04 01:09:14 -05:00

refactor: logging

This commit is contained in:
shenlong-tanwen 2024-09-21 10:14:21 +05:30
parent 2e337265ff
commit d438e333d5
14 changed files with 105 additions and 72 deletions

View file

@ -4,22 +4,24 @@ import 'package:logging/logging.dart';
/// Log levels according to dart logging [Level] /// Log levels according to dart logging [Level]
enum LogLevel { enum LogLevel {
// do not change this order! // do not change this order!
all, verbose,
finest, debug,
finer,
fine,
config,
info, info,
warning, warning,
severe, error,
shout, wtf,
off,
} }
extension LevelExtension on Level { extension LevelExtension on Level {
LogLevel toLogLevel() => LogLevel toLogLevel() => switch (this) {
LogLevel.values.elementAtOrNull(Level.LEVELS.indexOf(this)) ?? Level.FINEST => LogLevel.verbose,
LogLevel.info; Level.FINE => LogLevel.debug,
Level.INFO => LogLevel.info,
Level.WARNING => LogLevel.warning,
Level.SEVERE => LogLevel.error,
Level.SHOUT => LogLevel.wtf,
_ => LogLevel.info,
};
} }
@immutable @immutable

View file

@ -8,9 +8,9 @@ import 'package:immich_mobile/domain/models/render_list.model.dart';
import 'package:immich_mobile/domain/models/render_list_element.model.dart'; import 'package:immich_mobile/domain/models/render_list_element.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart'; import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/extensions/drift.extension.dart'; import 'package:immich_mobile/utils/extensions/drift.extension.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class RemoteAssetDriftRepository with LogContext implements IAssetRepository { class RemoteAssetDriftRepository with LogMixin implements IAssetRepository {
final DriftDatabaseRepository _db; final DriftDatabaseRepository _db;
const RemoteAssetDriftRepository(this._db); const RemoteAssetDriftRepository(this._db);
@ -25,7 +25,7 @@ class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
return true; return true;
} catch (e, s) { } catch (e, s) {
log.severe("Cannot insert remote assets into table", e, s); log.e("Cannot insert remote assets into table", e, s);
return false; return false;
} }
} }
@ -36,7 +36,7 @@ class RemoteAssetDriftRepository with LogContext implements IAssetRepository {
await _db.asset.deleteAll(); await _db.asset.deleteAll();
return true; return true;
} catch (e, s) { } catch (e, s) {
log.severe("Cannot clear remote assets", e, s); log.e("Cannot clear remote assets", e, s);
return false; return false;
} }
} }

View file

@ -5,9 +5,9 @@ import 'package:immich_mobile/domain/entities/store.entity.drift.dart';
import 'package:immich_mobile/domain/interfaces/store.interface.dart'; import 'package:immich_mobile/domain/interfaces/store.interface.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart'; import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class StoreDriftRepository with LogContext implements IStoreRepository { class StoreDriftRepository with LogMixin implements IStoreRepository {
final DriftDatabaseRepository _db; final DriftDatabaseRepository _db;
const StoreDriftRepository(this._db); const StoreDriftRepository(this._db);
@ -42,7 +42,7 @@ class StoreDriftRepository with LogContext implements IStoreRepository {
)); ));
return true; return true;
} catch (e, s) { } catch (e, s) {
log.severe("Cannot set store value - ${key.name}; id - ${key.id}", e, s); log.e("Cannot set store value - ${key.name}; id - ${key.id}", e, s);
return false; return false;
} }
} }

View file

@ -5,9 +5,9 @@ import 'package:immich_mobile/domain/entities/user.entity.drift.dart';
import 'package:immich_mobile/domain/interfaces/user.interface.dart'; import 'package:immich_mobile/domain/interfaces/user.interface.dart';
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/domain/repositories/database.repository.dart'; import 'package:immich_mobile/domain/repositories/database.repository.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
class UserDriftRepository with LogContext implements IUserRepository { class UserDriftRepository with LogMixin implements IUserRepository {
final DriftDatabaseRepository _db; final DriftDatabaseRepository _db;
const UserDriftRepository(this._db); const UserDriftRepository(this._db);
@ -40,7 +40,7 @@ class UserDriftRepository with LogContext implements IUserRepository {
); );
return true; return true;
} catch (e, s) { } catch (e, s) {
log.severe("Cannot insert User into table - $user", e, s); log.e("Cannot insert User into table - $user", e, s);
return false; return false;
} }
} }

View file

@ -9,11 +9,11 @@ import 'package:immich_mobile/utils/collection_util.dart';
import 'package:immich_mobile/utils/constants/globals.dart'; import 'package:immich_mobile/utils/constants/globals.dart';
import 'package:immich_mobile/utils/immich_api_client.dart'; import 'package:immich_mobile/utils/immich_api_client.dart';
import 'package:immich_mobile/utils/isolate_helper.dart'; import 'package:immich_mobile/utils/isolate_helper.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/log_manager.dart';
import 'package:logging/logging.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class AssetSyncService with LogContext { class AssetSyncService with LogMixin {
const AssetSyncService(); const AssetSyncService();
Future<bool> doFullRemoteSyncForUserDrift( Future<bool> doFullRemoteSyncForUserDrift(
@ -23,7 +23,7 @@ class AssetSyncService with LogContext {
}) async { }) async {
return await IsolateHelper.run(() async { return await IsolateHelper.run(() async {
try { try {
final logger = Logger("SyncService <Isolate>"); final logger = LogManager.I.get("SyncService <Isolate>");
final syncClient = di<ImmichApiClient>().getSyncApi(); final syncClient = di<ImmichApiClient>().getSyncApi();
final chunkSize = limit ?? kFullSyncChunkSize; final chunkSize = limit ?? kFullSyncChunkSize;
@ -32,7 +32,7 @@ class AssetSyncService with LogContext {
String? lastAssetId; String? lastAssetId;
while (true) { while (true) {
logger.info( logger.d(
"Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}", "Requesting more chunks from lastId - ${lastAssetId ?? "<initial_fetch>"}",
); );
@ -67,7 +67,7 @@ class AssetSyncService with LogContext {
return true; return true;
} catch (e, s) { } catch (e, s) {
log.severe("Error performing full sync for user - ${user.name}", e, s); log.e("Error performing full sync for user - ${user.name}", e, s);
} }
return false; return false;
}); });

View file

@ -9,10 +9,10 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/domain/services/user.service.dart'; import 'package:immich_mobile/domain/services/user.service.dart';
import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/immich_api_client.dart'; import 'package:immich_mobile/utils/immich_api_client.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class LoginService with LogContext { class LoginService with LogMixin {
const LoginService(); const LoginService();
Future<bool> isEndpointAvailable(Uri uri, {ImmichApiClient? client}) async { Future<bool> isEndpointAvailable(Uri uri, {ImmichApiClient? client}) async {
@ -27,7 +27,7 @@ class LoginService with LogContext {
try { try {
await serverAPI.pingServer(); await serverAPI.pingServer();
} catch (e) { } catch (e) {
log.severe("Exception occured while validating endpoint", e); log.e("Exception occured while validating endpoint", e);
return false; return false;
} }
return true; return true;
@ -52,7 +52,7 @@ class LoginService with LogContext {
return endpoint.startsWith('/') ? "$baseUrl$endpoint" : endpoint; return endpoint.startsWith('/') ? "$baseUrl$endpoint" : endpoint;
} }
} catch (e) { } catch (e) {
log.fine("Could not locate /.well-known/immich at $baseUrl", e); log.e("Could not locate /.well-known/immich at $baseUrl", e);
} }
// No well-known, return the baseUrl // No well-known, return the baseUrl
@ -68,7 +68,7 @@ class LoginService with LogContext {
return loginResponse?.accessToken; return loginResponse?.accessToken;
} catch (e, s) { } catch (e, s) {
log.severe("Exception occured while performing password login", e, s); log.e("Exception occured while performing password login", e, s);
} }
return null; return null;
} }
@ -85,7 +85,7 @@ class LoginService with LogContext {
final oAuthUrlRes = oAuthUrl?.url; final oAuthUrlRes = oAuthUrl?.url;
if (oAuthUrlRes == null) { if (oAuthUrlRes == null) {
log.severe( log.e(
"oAuth Server URL not available. Kindly ensure oAuth login is enabled in the server", "oAuth Server URL not available. Kindly ensure oAuth login is enabled in the server",
); );
return null; return null;
@ -102,7 +102,7 @@ class LoginService with LogContext {
return loginResponse?.accessToken; return loginResponse?.accessToken;
} catch (e) { } catch (e) {
log.severe("Exception occured while performing oauth login", e); log.e("Exception occured while performing oauth login", e);
} }
return null; return null;
} }

View file

@ -1,9 +1,9 @@
import 'package:immich_mobile/domain/models/server-info/server_config.model.dart'; import 'package:immich_mobile/domain/models/server-info/server_config.model.dart';
import 'package:immich_mobile/domain/models/server-info/server_features.model.dart'; import 'package:immich_mobile/domain/models/server-info/server_features.model.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class ServerInfoService with LogContext { class ServerInfoService with LogMixin {
final ServerApi _serverInfo; final ServerApi _serverInfo;
const ServerInfoService(this._serverInfo); const ServerInfoService(this._serverInfo);
@ -15,7 +15,7 @@ class ServerInfoService with LogContext {
return ServerFeatures.fromDto(dto); return ServerFeatures.fromDto(dto);
} }
} catch (e, s) { } catch (e, s) {
log.severe("Error while fetching server features", e, s); log.e("Error while fetching server features", e, s);
} }
return null; return null;
} }
@ -27,7 +27,7 @@ class ServerInfoService with LogContext {
return ServerConfig.fromDto(dto); return ServerConfig.fromDto(dto);
} }
} catch (e, s) { } catch (e, s) {
log.severe("Error while fetching server config", e, s); log.e("Error while fetching server config", e, s);
} }
return null; return null;
} }

View file

@ -1,8 +1,8 @@
import 'package:immich_mobile/domain/models/user.model.dart'; import 'package:immich_mobile/domain/models/user.model.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class UserService with LogContext { class UserService with LogMixin {
final UsersApi _userApi; final UsersApi _userApi;
const UserService(this._userApi); const UserService(this._userApi);
@ -18,13 +18,13 @@ class UserService with LogContext {
]); ]);
if (userDto == null) { if (userDto == null) {
log.severe("Cannot fetch my user."); log.e("Cannot fetch my user.");
return null; return null;
} }
return User.fromAdminDto(userDto, preferencesDto); return User.fromAdminDto(userDto, preferencesDto);
} catch (e, s) { } catch (e, s) {
log.severe("Error while fetching my user", e, s); log.e("Error while fetching my user", e, s);
} }
return null; return null;
} }

View file

@ -13,10 +13,10 @@ import 'package:immich_mobile/presentation/modules/common/states/server_info/ser
import 'package:immich_mobile/presentation/modules/login/models/login_page.model.dart'; import 'package:immich_mobile/presentation/modules/login/models/login_page.model.dart';
import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/immich_api_client.dart'; import 'package:immich_mobile/utils/immich_api_client.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:immich_mobile/utils/snackbar_manager.dart'; import 'package:immich_mobile/utils/snackbar_manager.dart';
class LoginPageCubit extends Cubit<LoginPageState> with LogContext { class LoginPageCubit extends Cubit<LoginPageState> with LogMixin {
LoginPageCubit() : super(LoginPageState.reset()); LoginPageCubit() : super(LoginPageState.reset());
String _appendSchema(String url) { String _appendSchema(String url) {
@ -96,7 +96,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
await _postLogin(accessToken); await _postLogin(accessToken);
} catch (e, s) { } catch (e, s) {
SnackbarManager.showError(t.login.error.error_login); SnackbarManager.showError(t.login.error.error_login);
log.severe("Cannot perform password login", e, s); log.e("Cannot perform password login", e, s);
} finally { } finally {
emit(state.copyWith(isValidationInProgress: false)); emit(state.copyWith(isValidationInProgress: false));
} }
@ -116,7 +116,7 @@ class LoginPageCubit extends Cubit<LoginPageState> with LogContext {
await _postLogin(accessToken); await _postLogin(accessToken);
} catch (e, s) { } catch (e, s) {
SnackbarManager.showError(t.login.error.error_login_oauth); SnackbarManager.showError(t.login.error.error_login_oauth);
log.severe("Cannot perform oauth login", e, s); log.e("Cannot perform oauth login", e, s);
} finally { } finally {
emit(state.copyWith(isValidationInProgress: false)); emit(state.copyWith(isValidationInProgress: false));
} }

View file

@ -8,7 +8,7 @@ import 'package:immich_mobile/presentation/components/image/immich_logo.widget.d
import 'package:immich_mobile/presentation/modules/login/states/login_page.state.dart'; import 'package:immich_mobile/presentation/modules/login/states/login_page.state.dart';
import 'package:immich_mobile/presentation/router/router.dart'; import 'package:immich_mobile/presentation/router/router.dart';
import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
@RoutePage() @RoutePage()
class SplashScreenWrapperPage extends AutoRouter implements AutoRouteWrapper { class SplashScreenWrapperPage extends AutoRouter implements AutoRouteWrapper {
@ -30,7 +30,7 @@ class SplashScreenPage extends StatefulWidget {
} }
class _SplashScreenState extends State<SplashScreenPage> class _SplashScreenState extends State<SplashScreenPage>
with SingleTickerProviderStateMixin, LogContext { with SingleTickerProviderStateMixin, LogMixin {
late final AnimationController _animationController; late final AnimationController _animationController;
@override @override
@ -65,7 +65,7 @@ class _SplashScreenState extends State<SplashScreenPage>
if (snap.hasData) { if (snap.hasData) {
_tryLogin(); _tryLogin();
} else if (snap.hasError) { } else if (snap.hasError) {
log.severe( log.wtf(
"Error while initializing the app", "Error while initializing the app",
snap.error, snap.error,
snap.stackTrace, snap.stackTrace,

View file

@ -9,10 +9,10 @@ import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/presentation/router/router.dart'; import 'package:immich_mobile/presentation/router/router.dart';
import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/service_locator.dart';
import 'package:immich_mobile/utils/constants/globals.dart'; import 'package:immich_mobile/utils/constants/globals.dart';
import 'package:immich_mobile/utils/mixins/log_context.mixin.dart'; import 'package:immich_mobile/utils/mixins/log.mixin.dart';
import 'package:openapi/api.dart'; import 'package:openapi/api.dart';
class ImmichApiClient extends ApiClient with LogContext { class ImmichApiClient extends ApiClient with LogMixin {
ImmichApiClient({required String endpoint}) : super(basePath: endpoint); ImmichApiClient({required String endpoint}) : super(basePath: endpoint);
Map<String, String> get headers => defaultHeaderMap; Map<String, String> get headers => defaultHeaderMap;
@ -58,7 +58,7 @@ class ImmichApiClient extends ApiClient with LogContext {
); );
if (res.statusCode == HttpStatus.unauthorized) { if (res.statusCode == HttpStatus.unauthorized) {
log.severe("Token invalid. Redirecting to login route"); log.e("Token invalid. Redirecting to login route");
await di<AppRouter>().replaceAll([const LoginRoute()]); await di<AppRouter>().replaceAll([const LoginRoute()]);
throw ApiException(res.statusCode, "Unauthorized"); throw ApiException(res.statusCode, "Unauthorized");
} }

View file

@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:immich_mobile/domain/interfaces/log.interface.dart'; import 'package:immich_mobile/domain/interfaces/log.interface.dart';
import 'package:immich_mobile/domain/models/log.model.dart'; import 'package:immich_mobile/domain/models/log.model.dart';
import 'package:immich_mobile/service_locator.dart'; import 'package:immich_mobile/service_locator.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart' as logging;
/// [LogManager] is a custom logger that is built on top of the [logging] package. /// [LogManager] is a custom logger that is built on top of the [logging] package.
/// The logs are written to the database and onto console, using `debugPrint` method. /// The logs are written to the database and onto console, using `debugPrint` method.
@ -14,14 +14,16 @@ import 'package:logging/logging.dart';
class LogManager { class LogManager {
LogManager._(); LogManager._();
static final LogManager _instance = LogManager._(); static final LogManager _instance = LogManager._();
static final Map<String, Logger> _loggers = <String, Logger>{};
// ignore: match-getter-setter-field-names // ignore: match-getter-setter-field-names
static LogManager get I => _instance; static LogManager get I => _instance;
List<LogMessage> _msgBuffer = []; List<LogMessage> _msgBuffer = [];
Timer? _timer; Timer? _timer;
late final StreamSubscription<LogRecord> _subscription; late final StreamSubscription<logging.LogRecord> _subscription;
void _onLogRecord(LogRecord record) { void _onLogRecord(logging.LogRecord record) {
// Only print in development // Only print in development
assert(() { assert(() {
debugPrint('[${record.level.name}] [${record.time}] ${record.message}'); debugPrint('[${record.level.name}] [${record.time}] ${record.message}');
@ -55,11 +57,17 @@ class LogManager {
} }
void init() { void init() {
_subscription = Logger.root.onRecord.listen(_onLogRecord); _subscription = logging.Logger.root.onRecord.listen(_onLogRecord);
} }
Logger get(String? loggerName) => _loggers.putIfAbsent(
loggerName ?? 'main',
() => Logger(loggerName ?? 'main'),
);
void updateLevel(LogLevel level) { void updateLevel(LogLevel level) {
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index); logging.Logger.root.level =
logging.Level.LEVELS.elementAtOrNull(level.index);
} }
void dispose() { void dispose() {
@ -75,7 +83,7 @@ class LogManager {
static void setGlobalErrorCallbacks() { static void setGlobalErrorCallbacks() {
FlutterError.onError = (details) { FlutterError.onError = (details) {
Logger("FlutterError").severe( LogManager.I.get("FlutterError").wtf(
'Unknown framework error occured in library ${details.library ?? "<unknown>"} at node ${details.context ?? "<unkown>"}', 'Unknown framework error occured in library ${details.library ?? "<unknown>"} at node ${details.context ?? "<unkown>"}',
details.exception, details.exception,
details.stack, details.stack,
@ -84,9 +92,32 @@ class LogManager {
}; };
PlatformDispatcher.instance.onError = (error, stack) { PlatformDispatcher.instance.onError = (error, stack) {
Logger("PlatformDispatcher") LogManager.I
.severe('Unknown error occured in root isolate', error, stack); .get("PlatformDispatcher")
.wtf('Unknown error occured in root isolate', error, stack);
return true; return true;
}; };
} }
} }
class Logger {
final String _loggerName;
const Logger(this._loggerName);
logging.Logger get _logger => logging.Logger(_loggerName);
// Highly detailed
void v(String message) => _logger.finest(message);
// Troubleshooting
void d(String message) => _logger.fine(message);
// General purpose
void i(String message) => _logger.info(message);
// Potential issues
void w(String message) => _logger.warning(message);
// Error
void e(String message, [Object? error, StackTrace? stack]) =>
_logger.severe(message, error, stack);
// Crash / Serious failure
void wtf(String message, [Object? error, StackTrace? stack]) =>
_logger.shout(message, error, stack);
}

View file

@ -0,0 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/utils/log_manager.dart';
mixin LogMixin {
@protected
@nonVirtual
Logger get log => LogManager.I.get(runtimeType.toString());
}

View file

@ -1,8 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
mixin LogContext {
@protected
@nonVirtual
Logger get log => Logger(runtimeType.toString());
}