feat(mobile): share logs from the startup error screen (#32288)
* fix(mobile): hide server addresses and tokens in shared logs * feat(mobile): share logs from the startup error screen * use only the shared logs patterns on the startup error screen * fix(mobile): stop writing server urls and oauth values to the log * fix(mobile): redact logs where they are read * redact the startup error screen share * fix(mobile): hide server addresses and tokens in shared logs * fix(mobile): stop writing server urls and oauth values to the log * fix(mobile): redact logs where they are read * fix(mobile): redact logs when they are written * log the startup error and share it as a log row * fix(mobile): hide ipv6 addresses and name labels after their settings * comment why the startup error row is only added here (cherry picked from commit f7fa8a3a2d2c3b0ab59f90fbcc3c230c58a2aede)
This commit is contained in:
@@ -74,6 +74,7 @@ void main() async {
|
||||
),
|
||||
);
|
||||
} catch (error, stack) {
|
||||
Logger('main').severe('Failed to start', error, stack);
|
||||
runApp(BootstrapErrorWidget(error: error.toString(), stack: stack.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/providers/view_intent/view_intent_handler.provider.dart';
|
||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/immich_logger.service.dart';
|
||||
import 'package:immich_mobile/theme/color_scheme.dart';
|
||||
import 'package:immich_mobile/theme/theme_data.dart';
|
||||
import 'package:immich_mobile/utils/fallback_localizations.dart';
|
||||
@@ -78,9 +79,12 @@ class BootstrapErrorWidget extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
const SafeArea(
|
||||
SafeArea(
|
||||
top: false,
|
||||
child: Padding(padding: EdgeInsets.fromLTRB(24, 16, 24, 16), child: _BottomPanel()),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 16, 24, 16),
|
||||
child: _BottomPanel(startupError: '$error\n\n$stack'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -93,7 +97,9 @@ class BootstrapErrorWidget extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _BottomPanel extends StatefulWidget {
|
||||
const _BottomPanel();
|
||||
final String startupError;
|
||||
|
||||
const _BottomPanel({required this.startupError});
|
||||
|
||||
@override
|
||||
State<_BottomPanel> createState() => _BottomPanelState();
|
||||
@@ -157,8 +163,8 @@ class _BottomPanelState extends State<_BottomPanel> {
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
Wrap(
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
_ActionLink(
|
||||
icon: Icons.chat_bubble_outline,
|
||||
@@ -173,6 +179,11 @@ class _BottomPanelState extends State<_BottomPanel> {
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
),
|
||||
_ActionLink(
|
||||
icon: Icons.share_outlined,
|
||||
label: context.t.share_logs,
|
||||
onTap: () => ImmichLogger.shareLogs(context, startupError: widget.startupError),
|
||||
),
|
||||
if (!_cleared)
|
||||
_ActionLink(
|
||||
icon: Icons.delete_outline,
|
||||
|
||||
@@ -2,7 +2,11 @@ import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:immich_mobile/data/db/logger/database.dart';
|
||||
import 'package:immich_mobile/data/db/main/database.dart';
|
||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
@@ -15,7 +19,26 @@ import 'package:share_plus/share_plus.dart';
|
||||
/// Logs can be shared by calling the `shareLogs` method, which will open a share dialog
|
||||
/// and generate a csv file.
|
||||
abstract final class ImmichLogger {
|
||||
static Future<void> shareLogs(BuildContext context) async {
|
||||
// the startup error screen can show before the log service starts, and then the startup error was not logged
|
||||
static Future<List<LogMessage>> _read(String? startupError) async {
|
||||
try {
|
||||
return await LogService.I.getMessages();
|
||||
} on LoggerUnInitializedException {
|
||||
final logs = DriftLogger.sqlite(await openSqliteConnection(name: 'immich_logs'));
|
||||
try {
|
||||
return [
|
||||
// if the log service had started, main would have logged this already
|
||||
if (startupError != null)
|
||||
LogMessage(message: startupError, level: LogLevel.severe, createdAt: DateTime.now()),
|
||||
...await LogRepository(logs).getAll(),
|
||||
];
|
||||
} finally {
|
||||
await logs.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> shareLogs(BuildContext context, {String? startupError}) async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final dateTime = DateTime.now().toIso8601String();
|
||||
final filePath = '${tempDir.path}/Immich_log_$dateTime.log';
|
||||
@@ -23,7 +46,7 @@ abstract final class ImmichLogger {
|
||||
final io = logFile.openWrite();
|
||||
try {
|
||||
// Write messages
|
||||
for (final m in await LogService.I.getMessages()) {
|
||||
for (final m in await _read(startupError)) {
|
||||
final created = m.createdAt;
|
||||
final level = m.level.name.padRight(8);
|
||||
final logger = (m.logger ?? "<UNKNOWN_LOGGER>").padRight(20);
|
||||
|
||||
Reference in New Issue
Block a user