refactor: all logging

This commit is contained in:
cogwheel0
2025-09-25 22:36:42 +05:30
parent db0261ffed
commit 9210b2155a
27 changed files with 1040 additions and 346 deletions

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart' hide debugPrint;
import 'package:dio/dio.dart';
// import 'package:http_parser/http_parser.dart';
// Removed legacy websocket/socket.io imports
@@ -17,6 +17,17 @@ import '../error/api_error_interceptor.dart';
import 'persistent_streaming_service.dart';
import '../utils/debug_logger.dart';
const bool _enableLegacyApiLogs = false;
const bool _traceConversationParsing = false;
const bool _traceFullChatParsing = false;
void debugPrint(String? message, {int? wrapWidth}) {
if (!_enableLegacyApiLogs || message == null) {
return;
}
DebugLogger.fromLegacy(message, scope: 'api/legacy');
}
class ApiService {
final Dio _dio;
final ServerConfig serverConfig;
@@ -187,7 +198,7 @@ class ApiService {
// User info
Future<User> getCurrentUser() async {
final response = await _dio.get('/api/v1/auths/');
DebugLogger.log('User info retrieved successfully');
DebugLogger.log('user-info', scope: 'api/user');
return User.fromJson(response.data);
}
@@ -204,11 +215,15 @@ class ApiService {
// Response is a direct array
models = response.data as List;
} else {
DebugLogger.error('Unexpected models response format');
DebugLogger.error('models-format', scope: 'api/models');
return [];
}
DebugLogger.log('Found ${models.length} models');
DebugLogger.log(
'models-count',
scope: 'api/models',
data: {'count': models.length},
);
return models.map((m) => Model.fromJson(m)).toList();
}
@@ -217,12 +232,14 @@ class ApiService {
try {
final response = await _dio.get('/api/v1/users/user/settings');
DebugLogger.log('User settings retrieved successfully');
DebugLogger.log('settings-ok', scope: 'api/user-settings');
final data = response.data;
if (data is! Map<String, dynamic>) {
DebugLogger.warning(
'User settings response is empty or unexpected type: ${data.runtimeType}',
'settings-format',
scope: 'api/user-settings',
data: {'type': data.runtimeType},
);
return null;
}
@@ -235,16 +252,22 @@ class ApiService {
// Return the first model in the user's preferred models list
final defaultModel = models.first.toString();
DebugLogger.log(
'Found default model from user settings: $defaultModel',
'default-model',
scope: 'api/user-settings',
data: {'id': defaultModel},
);
return defaultModel;
}
}
DebugLogger.log('No default model found in user settings');
DebugLogger.warning('default-model-missing', scope: 'api/user-settings');
return null;
} catch (e) {
DebugLogger.error('Error fetching default model from user settings', e);
DebugLogger.error(
'default-model-error',
scope: 'api/user-settings',
error: e,
);
// Do not call admin-only configs endpoint here; let the caller
// handle fallback (e.g., first available model from /api/models).
return null;
@@ -327,9 +350,15 @@ class ApiService {
);
final regularChatList = allRegularChats;
debugPrint('DEBUG: Found ${regularChatList.length} regular chats');
debugPrint('DEBUG: Found ${pinnedChatList.length} pinned chats');
debugPrint('DEBUG: Found ${archivedChatList.length} archived chats');
DebugLogger.log(
'summary',
scope: 'api/conversations',
data: {
'regular': regularChatList.length,
'pinned': pinnedChatList.length,
'archived': archivedChatList.length,
},
);
// Convert OpenWebUI chat format to our Conversation format
final conversations = <Conversation>[];
@@ -345,7 +374,12 @@ class ApiService {
conversations.add(pinnedConversation);
pinnedIds.add(conversation.id);
} catch (e) {
debugPrint('DEBUG: Error parsing pinned chat ${chatData['id']}: $e');
DebugLogger.error(
'parse-pinned-failed',
scope: 'api/conversations',
error: e,
data: {'conversationId': chatData['id']},
);
}
}
@@ -358,7 +392,12 @@ class ApiService {
conversations.add(archivedConversation);
archivedIds.add(conversation.id);
} catch (e) {
debugPrint('DEBUG: Error parsing archived chat ${chatData['id']}: $e');
DebugLogger.error(
'parse-archived-failed',
scope: 'api/conversations',
error: e,
data: {'conversationId': chatData['id']},
);
}
}
@@ -369,21 +408,28 @@ class ApiService {
// Debug: Check if conversation has folder_id in raw data
if (chatData.containsKey('folder_id') &&
chatData['folder_id'] != null) {
debugPrint(
'🔍 DEBUG: Found conversation with folder_id in raw data: ${chatData['id']} -> ${chatData['folder_id']}',
DebugLogger.log(
'folder-ref',
scope: 'api/conversations',
data: {
'conversationId': chatData['id'],
'folderId': chatData['folder_id'],
},
);
}
if (!loggedSampleChat) {
if (!loggedSampleChat && _traceConversationParsing) {
loggedSampleChat = true;
debugPrint(
'🔍 DEBUG: Sample chat data fields: ${chatData.keys.toList()}',
DebugLogger.log(
'sample-keys',
scope: 'api/conversations',
data: {'keys': chatData.keys.take(6).toList()},
);
DebugLogger.log(
'sample-data',
scope: 'api/conversations',
data: {'preview': chatData.toString()},
);
final samplePreviewSource = chatData.toString();
final preview = samplePreviewSource.length > 200
? samplePreviewSource.substring(0, 200)
: samplePreviewSource;
debugPrint('🔍 DEBUG: Sample chat data: $preview...');
}
final conversation = _parseOpenWebUIChat(chatData);
@@ -393,13 +439,24 @@ class ApiService {
conversations.add(conversation);
}
} catch (e) {
debugPrint('DEBUG: Error parsing chat ${chatData['id']}: $e');
DebugLogger.error(
'parse-regular-failed',
scope: 'api/conversations',
error: e,
data: {'conversationId': chatData['id']},
);
// Continue with other chats even if one fails
}
}
debugPrint(
'DEBUG: Successfully parsed ${conversations.length} conversations (${pinnedIds.length} pinned, ${archivedIds.length} archived)',
DebugLogger.log(
'parse-complete',
scope: 'api/conversations',
data: {
'total': conversations.length,
'pinned': pinnedIds.length,
'archived': archivedIds.length,
},
);
return conversations;
}
@@ -408,21 +465,30 @@ class ApiService {
String path, {
required String debugLabel,
}) async {
final scope = 'api/collection/${debugLabel.replaceAll(' ', '-')}';
try {
final response = await _dio.get(path);
DebugLogger.log('$debugLabel response status: ${response.statusCode}');
DebugLogger.log(
'status',
scope: scope,
data: {'code': response.statusCode},
);
if (response.data is List) {
return (response.data as List).cast<dynamic>();
}
DebugLogger.warning(
'Expected array for $debugLabel, got ${response.data.runtimeType}',
'unexpected-type',
scope: scope,
data: {'type': response.data.runtimeType},
);
} on DioException catch (e) {
DebugLogger.warning(
'Skipping $debugLabel due to network error: ${e.message}',
'network-skip',
scope: scope,
data: {'message': e.message},
);
} catch (e) {
DebugLogger.warning('Skipping $debugLabel due to error: $e');
DebugLogger.warning('error-skip', scope: scope, data: {'error': e});
}
return <dynamic>[];
}
@@ -484,14 +550,22 @@ class ApiService {
final folderId = chatData['folder_id'] as String?;
// Debug logging for folder assignment
if (folderId != null) {
if (_traceConversationParsing && folderId != null) {
final idPreview = id.length > 8 ? id.substring(0, 8) : id;
debugPrint('🔍 DEBUG: Conversation $idPreview has folderId: $folderId');
DebugLogger.log(
'folder-ref',
scope: 'api/conversations',
data: {'conversationId': idPreview, 'folderId': folderId},
);
}
debugPrint(
'DEBUG: Parsed conversation $id: pinned=$pinned, archived=$archived',
);
if (_traceConversationParsing) {
DebugLogger.log(
'parsed',
scope: 'api/conversations',
data: {'id': id, 'pinned': pinned, 'archived': archived},
);
}
String? systemPrompt;
final chatObject = chatData['chat'] as Map<String, dynamic>?;
@@ -522,10 +596,10 @@ class ApiService {
}
Future<Conversation> getConversation(String id) async {
DebugLogger.log('Fetching individual chat: $id');
DebugLogger.log('fetch', scope: 'api/chat', data: {'id': id});
final response = await _dio.get('/api/v1/chats/$id');
DebugLogger.log('Chat response received successfully');
DebugLogger.log('fetch-ok', scope: 'api/chat');
// Parse OpenWebUI ChatResponse format
final chatData = response.data as Map<String, dynamic>;
@@ -534,14 +608,24 @@ class ApiService {
// Parse full OpenWebUI chat with messages
Conversation _parseFullOpenWebUIChat(Map<String, dynamic> chatData) {
debugPrint('DEBUG: Parsing full OpenWebUI chat data');
debugPrint('DEBUG: Chat data keys: ${chatData.keys.toList()}');
if (_traceFullChatParsing) {
DebugLogger.log(
'parse-full',
scope: 'api/chat',
data: {'keys': chatData.keys.take(8).toList()},
);
}
final id = chatData['id'] as String;
final title = chatData['title'] as String;
debugPrint('DEBUG: Parsed chat ID: $id');
debugPrint('DEBUG: Parsed chat title: $title');
if (_traceFullChatParsing) {
DebugLogger.log(
'chat-meta',
scope: 'api/chat',
data: {'id': id, 'title': title},
);
}
// Safely parse timestamps with validation
final updatedAt = _parseTimestamp(chatData['updated_at']);
@@ -573,7 +657,13 @@ class ApiService {
final models = chatObject['models'] as List?;
if (models != null && models.isNotEmpty) {
model = models.first as String;
debugPrint('DEBUG: Extracted model from chat.models: $model');
if (_traceFullChatParsing) {
DebugLogger.log(
'model',
scope: 'api/chat',
data: {'id': id, 'model': model},
);
}
}
}
@@ -591,24 +681,40 @@ class ApiService {
final currentId = history['currentId']?.toString();
if (currentId != null && currentId.isNotEmpty) {
messagesList = _buildMessagesListFromHistory(history);
debugPrint(
'DEBUG: Built ${messagesList.length} messages from history chain to currentId=$currentId',
);
if (_traceFullChatParsing) {
DebugLogger.log(
'history-chain',
scope: 'api/chat',
data: {
'id': id,
'count': messagesList.length,
'currentId': currentId,
},
);
}
}
}
// Fallback to chat.messages (list format) if history is missing or empty
if (((messagesList?.isEmpty ?? true)) && chatObject['messages'] != null) {
messagesList = chatObject['messages'] as List;
debugPrint(
'DEBUG: Found ${messagesList.length} messages in chat.messages (fallback)',
);
if (_traceFullChatParsing) {
DebugLogger.log(
'messages-fallback',
scope: 'api/chat',
data: {'id': id, 'count': messagesList.length},
);
}
}
} else if (chatData['messages'] != null) {
messagesList = chatData['messages'] as List;
debugPrint(
'DEBUG: Found ${messagesList.length} messages in top-level messages',
);
if (_traceFullChatParsing) {
DebugLogger.log(
'messages-top-level',
scope: 'api/chat',
data: {'id': id, 'count': messagesList.length},
);
}
}
// Parse messages from list format only (avoiding duplication)
@@ -616,9 +722,18 @@ class ApiService {
for (int idx = 0; idx < messagesList.length; idx++) {
final msgData = messagesList[idx] as Map<String, dynamic>;
try {
debugPrint(
'DEBUG: Parsing message: ${msgData['id']} - role: ${msgData['role']} - content length: ${msgData['content']?.toString().length ?? 0}',
);
if (_traceFullChatParsing) {
DebugLogger.log(
'message-parse',
scope: 'api/chat',
data: {
'chatId': id,
'messageId': msgData['id'],
'role': msgData['role'],
'contentLen': msgData['content']?.toString().length ?? 0,
},
);
}
// If this assistant message includes tool_calls, merge following tool results
final historyMsg = historyMessagesMap != null
@@ -667,9 +782,13 @@ class ApiService {
// Skip the tool messages we just merged
idx = j - 1;
debugPrint(
'DEBUG: Successfully parsed tool_call assistant turn: ${message.id}',
);
if (_traceFullChatParsing) {
DebugLogger.log(
'message-tool-call',
scope: 'api/chat',
data: {'chatId': id, 'messageId': message.id},
);
}
continue;
}
@@ -679,16 +798,35 @@ class ApiService {
historyMsg: historyMsg,
);
messages.add(message);
debugPrint(
'DEBUG: Successfully parsed message: ${message.id} - ${message.role}',
);
if (_traceFullChatParsing) {
DebugLogger.log(
'message',
scope: 'api/chat',
data: {
'chatId': id,
'messageId': message.id,
'role': message.role,
},
);
}
} catch (e) {
debugPrint('DEBUG: Error parsing message: $e');
DebugLogger.error(
'message-parse-failed',
scope: 'api/chat',
error: e,
data: {'chatId': id, 'messageId': msgData['id']},
);
}
}
}
debugPrint('DEBUG: Total parsed messages: ${messages.length}');
if (_traceFullChatParsing) {
DebugLogger.log(
'message-count',
scope: 'api/chat',
data: {'chatId': id, 'count': messages.length},
);
}
return Conversation(
id: id,
@@ -1188,9 +1326,11 @@ class ApiService {
final response = await _dio.post('/api/v1/chats/new', data: chatData);
DebugLogger.log(
'Create conversation response status: ${response.statusCode}',
'create-status',
scope: 'api/conversation',
data: {'code': response.statusCode},
);
DebugLogger.log('Create conversation response received successfully');
DebugLogger.log('create-ok', scope: 'api/conversation');
// Parse the response
final responseData = response.data as Map<String, dynamic>;
@@ -1290,7 +1430,7 @@ class ApiService {
// OpenWebUI uses POST not PUT for updating chats
await _dio.post('/api/v1/chats/$conversationId', data: chatData);
DebugLogger.log('Sync conversation response received successfully');
DebugLogger.log('sync-ok', scope: 'api/conversation');
}
Future<void> updateConversation(
@@ -1381,21 +1521,28 @@ class ApiService {
// Folders
Future<List<Map<String, dynamic>>> getFolders() async {
try {
debugPrint('DEBUG: Fetching folders from /api/v1/folders/');
final response = await _dio.get('/api/v1/folders/');
DebugLogger.log('Folders response status: ${response.statusCode}');
DebugLogger.log('Folders response received successfully');
DebugLogger.log(
'fetch-status',
scope: 'api/folders',
data: {'code': response.statusCode},
);
DebugLogger.log('fetch-ok', scope: 'api/folders');
final data = response.data;
if (data is List) {
debugPrint('DEBUG: Found ${data.length} folders');
return data.cast<Map<String, dynamic>>();
} else {
DebugLogger.log('Response data is not a list: ${data.runtimeType}');
DebugLogger.warning(
'unexpected-type',
scope: 'api/folders',
data: {'type': data.runtimeType},
);
return [];
}
} catch (e) {
debugPrint('DEBUG: Error in getFolders: $e');
DebugLogger.error('fetch-failed', scope: 'api/folders', error: e);
rethrow;
}
}
@@ -1783,17 +1930,23 @@ class ApiService {
data: {'queries': queries},
);
DebugLogger.log('Web search response status: ${response.statusCode}');
DebugLogger.log('Web search response type: ${response.data.runtimeType}');
DebugLogger.log('Web search response received successfully');
DebugLogger.log(
'status',
scope: 'api/web-search',
data: {'code': response.statusCode},
);
DebugLogger.log(
'response-type',
scope: 'api/web-search',
data: {'type': response.data.runtimeType},
);
DebugLogger.log('fetch-ok', scope: 'api/web-search');
return response.data as Map<String, dynamic>;
} catch (e) {
debugPrint('DEBUG: Web search API error: $e');
if (e is DioException) {
DebugLogger.error(
'Web search error response available (truncated for security)',
);
DebugLogger.error('error-response', scope: 'api/web-search', error: e);
debugPrint('DEBUG: Web search error status: ${e.response?.statusCode}');
}
rethrow;
@@ -1810,7 +1963,7 @@ class ApiService {
if (response.statusCode == 200 && response.data != null) {
final modelData = response.data as Map<String, dynamic>;
DebugLogger.log('Model details for $modelId retrieved successfully');
DebugLogger.log('details', scope: 'api/models', data: {'id': modelId});
return modelData;
}
} catch (e) {
@@ -1911,7 +2064,11 @@ class ApiService {
debugPrint(
'DEBUG: Collection query response type: ${response.data.runtimeType}',
);
DebugLogger.log('Collection query response received successfully');
DebugLogger.log(
'query-ok',
scope: 'api/collection',
data: {'name': collectionName},
);
if (response.data is List) {
return response.data as List<dynamic>;
@@ -1951,7 +2108,7 @@ class ApiService {
debugPrint(
'DEBUG: Retrieval config response status: ${response.statusCode}',
);
DebugLogger.log('Retrieval config response received successfully');
DebugLogger.log('config-ok', scope: 'api/retrieval');
return response.data as Map<String, dynamic>;
} catch (e) {
@@ -2036,8 +2193,10 @@ class ApiService {
} on DioException catch (e) {
debugPrint('DEBUG: images/generations failed: ${e.response?.statusCode}');
DebugLogger.error(
'Image generation request to /api/v1/images/generations failed',
e,
'images-generate-failed',
scope: 'api/images',
error: e,
data: {'status': e.response?.statusCode},
);
// Do not attempt singular fallback here - surface the original error
rethrow;
@@ -3325,8 +3484,12 @@ class ApiService {
debugPrint('DEBUG: Uploading to /api/v1/files/');
final response = await _dio.post('/api/v1/files/', data: formData);
DebugLogger.log('Upload response status: ${response.statusCode}');
DebugLogger.log('Upload response received successfully');
DebugLogger.log(
'upload-status',
scope: 'api/files',
data: {'code': response.statusCode},
);
DebugLogger.log('upload-ok', scope: 'api/files');
if (response.data is Map && response.data['id'] != null) {
final fileId = response.data['id'] as String;
@@ -3336,7 +3499,7 @@ class ApiService {
throw Exception('Invalid response format: missing file ID');
}
} catch (e) {
debugPrint('ERROR: File upload failed: $e');
DebugLogger.error('upload-failed', scope: 'api/files', error: e);
rethrow;
}
}
@@ -3370,17 +3533,35 @@ class ApiService {
debugPrint('Testing endpoint: $endpoint');
final response = await _dio.get(endpoint);
debugPrint('$endpoint - Status: ${response.statusCode}');
DebugLogger.log(' Response type: ${response.data.runtimeType}');
DebugLogger.log(
'response-type',
scope: 'api/diagnostics',
data: {'endpoint': endpoint, 'type': response.data.runtimeType},
);
if (response.data is List) {
DebugLogger.log(' Array length: ${(response.data as List).length}');
DebugLogger.log(
'array-length',
scope: 'api/diagnostics',
data: {
'endpoint': endpoint,
'count': (response.data as List).length,
},
);
} else if (response.data is Map) {
DebugLogger.log(' Object keys: ${(response.data as Map).keys}');
DebugLogger.log(
'object-keys',
scope: 'api/diagnostics',
data: {
'endpoint': endpoint,
'keys': (response.data as Map).keys.take(5).toList(),
},
);
}
final dataSampleSource = response.data.toString();
final dataPreview = dataSampleSource.length > 200
? dataSampleSource.substring(0, 200)
: dataSampleSource;
DebugLogger.log(' Sample data: $dataPreview...');
DebugLogger.log(
'sample',
scope: 'api/diagnostics',
data: {'endpoint': endpoint, 'preview': response.data.toString()},
);
} catch (e) {
debugPrint('$endpoint - Error: $e');
}

View File

@@ -2,8 +2,13 @@ import 'dart:async';
import 'dart:convert';
import 'dart:math';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/debug_logger.dart';
void debugPrint(String? message, {int? wrapWidth}) {
if (message == null) return;
DebugLogger.fromLegacy(message, scope: 'attachments/queue');
}
/// Status of a queued attachment upload
enum QueuedAttachmentStatus { pending, uploading, completed, failed, cancelled }

View File

@@ -42,7 +42,9 @@ class BackgroundStreamingHandler {
final String reason = args['reason'] as String;
DebugLogger.stream(
'Background: Streams suspending - $streamIds (reason: $reason)',
'suspending',
scope: 'background',
data: {'count': streamIds.length, 'reason': reason},
);
onStreamsSuspending?.call(streamIds);
@@ -51,7 +53,7 @@ class BackgroundStreamingHandler {
break;
case 'backgroundTaskExpiring':
DebugLogger.stream('Background: Background task expiring');
DebugLogger.stream('task-expiring', scope: 'background');
onBackgroundTaskExpiring?.call();
break;
}
@@ -70,10 +72,17 @@ class BackgroundStreamingHandler {
});
DebugLogger.stream(
'Background: Started background execution for ${streamIds.length} streams',
'start',
scope: 'background',
data: {'count': streamIds.length},
);
} catch (e) {
DebugLogger.error('Background: Failed to start background execution', e);
DebugLogger.error(
'start-failed',
scope: 'background',
error: e,
data: {'count': streamIds.length},
);
}
}
@@ -90,10 +99,17 @@ class BackgroundStreamingHandler {
});
DebugLogger.stream(
'Background: Stopped background execution for ${streamIds.length} streams',
'stop',
scope: 'background',
data: {'count': streamIds.length},
);
} catch (e) {
DebugLogger.error('Background: Failed to stop background execution', e);
DebugLogger.error(
'stop-failed',
scope: 'background',
error: e,
data: {'count': streamIds.length},
);
}
}
@@ -156,7 +172,7 @@ class BackgroundStreamingHandler {
try {
await _channel.invokeMethod('keepAlive');
} catch (e) {
DebugLogger.error('Background: Failed to keep alive', e);
DebugLogger.error('keepalive-failed', scope: 'background', error: e);
}
}
@@ -181,11 +197,13 @@ class BackgroundStreamingHandler {
}
DebugLogger.stream(
'Background: Recovered ${recovered.length} stream states',
'recovered',
scope: 'background',
data: {'count': recovered.length},
);
return recovered;
} catch (e) {
DebugLogger.error('Background: Failed to recover stream states', e);
DebugLogger.error('recover-failed', scope: 'background', error: e);
return [];
}
}
@@ -207,7 +225,12 @@ class BackgroundStreamingHandler {
'reason': reason,
});
} catch (e) {
DebugLogger.error('Background: Failed to save stream states', e);
DebugLogger.error(
'save-states-failed',
scope: 'background',
error: e,
data: {'count': streamIds.length, 'reason': reason},
);
}
}
@@ -290,7 +313,7 @@ class StreamState {
),
);
} catch (e) {
DebugLogger.error('Failed to parse StreamState from map', e);
DebugLogger.error('parse-failed', scope: 'background', error: e);
return null;
}
}

View File

@@ -1,10 +1,15 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'secure_credential_storage.dart';
import '../models/server_config.dart';
import '../models/conversation.dart';
import '../utils/debug_logger.dart';
void debugPrint(String? message, {int? wrapWidth}) {
if (message == null) return;
DebugLogger.fromLegacy(message, scope: 'storage/optimized');
}
/// Optimized storage service with single secure storage implementation
/// Eliminates dual storage overhead and improves performance

View File

@@ -312,8 +312,10 @@ class PersistentStreamingService with WidgetsBindingObserver {
_retryAttempts.remove(streamId);
} catch (e) {
DebugLogger.error(
'PersistentStreaming: Failed to recover stream $streamId',
e,
'recover-failed',
scope: 'streaming/persistent',
error: e,
data: {'streamId': streamId},
);
// Schedule next retry if under limit
@@ -405,11 +407,12 @@ class PersistentStreamingService with WidgetsBindingObserver {
void _enableWakeLock() async {
try {
await WakelockPlus.enable();
DebugLogger.stream('PersistentStreamingService: Wake lock enabled');
DebugLogger.stream('wake-lock-enabled', scope: 'streaming/persistent');
} catch (e) {
DebugLogger.error(
'PersistentStreamingService: Failed to enable wake lock',
e,
'wake-lock-enable-failed',
scope: 'streaming/persistent',
error: e,
);
}
}
@@ -417,11 +420,12 @@ class PersistentStreamingService with WidgetsBindingObserver {
void _disableWakeLock() async {
try {
await WakelockPlus.disable();
DebugLogger.stream('PersistentStreamingService: Wake lock disabled');
DebugLogger.stream('wake-lock-disabled', scope: 'streaming/persistent');
} catch (e) {
DebugLogger.error(
'PersistentStreamingService: Failed to disable wake lock',
e,
'wake-lock-disable-failed',
scope: 'streaming/persistent',
error: e,
);
}
}

View File

@@ -73,9 +73,13 @@ class SecureCredentialStorage {
);
}
DebugLogger.storage('Credentials saved and verified securely');
DebugLogger.storage(
'save-ok',
scope: 'credentials',
data: {'version': '2.0'},
);
} catch (e) {
DebugLogger.error('Failed to save credentials', e);
DebugLogger.error('save-failed', scope: 'credentials', error: e);
rethrow;
}
}
@@ -92,7 +96,7 @@ class SecureCredentialStorage {
final decoded = jsonDecode(jsonString);
if (decoded is! Map<String, dynamic>) {
DebugLogger.warning('Invalid credentials format');
DebugLogger.warning('invalid-format', scope: 'credentials');
await deleteSavedCredentials();
return null;
}
@@ -104,7 +108,9 @@ class SecureCredentialStorage {
if (savedDeviceId != currentDeviceId) {
DebugLogger.info(
'Device fingerprint changed, but allowing credential access for better UX',
'fingerprint-mismatch-allowed',
scope: 'credentials',
data: {'previous': savedDeviceId, 'current': currentDeviceId},
);
// Don't clear credentials immediately - allow the user to continue
// They can re-login if needed, which will update the fingerprint
@@ -115,9 +121,7 @@ class SecureCredentialStorage {
if (!decoded.containsKey('serverId') ||
!decoded.containsKey('username') ||
!decoded.containsKey('password')) {
DebugLogger.warning(
'Invalid saved credentials format - missing required fields',
);
DebugLogger.warning('missing-fields', scope: 'credentials');
await deleteSavedCredentials();
return null;
}
@@ -133,11 +137,17 @@ class SecureCredentialStorage {
// Warn if credentials are very old (but don't delete them)
if (daysSinceCreated > 90) {
DebugLogger.info(
'Saved credentials are $daysSinceCreated days old',
'credentials-old',
scope: 'credentials',
data: {'ageDays': daysSinceCreated},
);
}
} catch (e) {
DebugLogger.warning('Could not parse savedAt timestamp: $e');
DebugLogger.warning(
'savedat-parse-failed',
scope: 'credentials',
data: {'raw': savedAt, 'error': e.toString()},
);
}
}
@@ -148,7 +158,7 @@ class SecureCredentialStorage {
'savedAt': decoded['savedAt']?.toString() ?? '',
};
} catch (e) {
DebugLogger.error('Failed to retrieve credentials', e);
DebugLogger.error('read-failed', scope: 'credentials', error: e);
// Don't delete credentials on retrieval errors - they might be recoverable
return null;
}
@@ -158,9 +168,9 @@ class SecureCredentialStorage {
Future<void> deleteSavedCredentials() async {
try {
await _secureStorage.delete(key: _credentialsKey);
DebugLogger.storage('Credentials deleted');
DebugLogger.storage('delete-ok', scope: 'credentials');
} catch (e) {
DebugLogger.error('Failed to delete credentials', e);
DebugLogger.error('delete-failed', scope: 'credentials', error: e);
}
}
@@ -170,7 +180,11 @@ class SecureCredentialStorage {
final encryptedToken = await _encryptData(token);
await _secureStorage.write(key: _authTokenKey, value: encryptedToken);
} catch (e) {
DebugLogger.error('Failed to save auth token', e);
DebugLogger.error(
'save-token-failed',
scope: 'credentials/token',
error: e,
);
rethrow;
}
}
@@ -183,7 +197,11 @@ class SecureCredentialStorage {
return await _decryptData(encryptedToken);
} catch (e) {
DebugLogger.error('Failed to retrieve auth token', e);
DebugLogger.error(
'read-token-failed',
scope: 'credentials/token',
error: e,
);
return null;
}
}
@@ -193,7 +211,11 @@ class SecureCredentialStorage {
try {
await _secureStorage.delete(key: _authTokenKey);
} catch (e) {
DebugLogger.error('Failed to delete auth token', e);
DebugLogger.error(
'delete-token-failed',
scope: 'credentials/token',
error: e,
);
}
}
@@ -206,7 +228,11 @@ class SecureCredentialStorage {
value: encryptedConfigs,
);
} catch (e) {
DebugLogger.error('Failed to save server configs', e);
DebugLogger.error(
'save-configs-failed',
scope: 'credentials/server-configs',
error: e,
);
rethrow;
}
}
@@ -221,7 +247,11 @@ class SecureCredentialStorage {
return await _decryptData(encryptedConfigs);
} catch (e) {
DebugLogger.error('Failed to retrieve server configs', e);
DebugLogger.error(
'read-configs-failed',
scope: 'credentials/server-configs',
error: e,
);
return null;
}
}
@@ -239,7 +269,11 @@ class SecureCredentialStorage {
return result == testValue;
} catch (e) {
DebugLogger.warning('Secure storage not available: $e');
DebugLogger.warning(
'storage-unavailable',
scope: 'credentials/health',
data: {'error': e.toString()},
);
return false;
}
}
@@ -248,9 +282,9 @@ class SecureCredentialStorage {
Future<void> clearAll() async {
try {
await _secureStorage.deleteAll();
DebugLogger.storage('All secure data cleared');
DebugLogger.storage('clear-ok', scope: 'credentials');
} catch (e) {
DebugLogger.error('Failed to clear secure data', e);
DebugLogger.error('clear-failed', scope: 'credentials', error: e);
}
}
@@ -261,7 +295,11 @@ class SecureCredentialStorage {
// In a more advanced implementation, you could add an additional layer of AES encryption
return data;
} catch (e) {
DebugLogger.error('Failed to encrypt data', e);
DebugLogger.error(
'encrypt-failed',
scope: 'credentials/crypto',
error: e,
);
rethrow;
}
}
@@ -273,7 +311,11 @@ class SecureCredentialStorage {
// This matches the encryption method above
return encryptedData;
} catch (e) {
DebugLogger.error('Failed to decrypt data', e);
DebugLogger.error(
'decrypt-failed',
scope: 'credentials/crypto',
error: e,
);
rethrow;
}
}
@@ -297,7 +339,11 @@ class SecureCredentialStorage {
return digest.toString();
} catch (e) {
DebugLogger.warning('Failed to generate device fingerprint: $e');
DebugLogger.warning(
'fingerprint-failed',
scope: 'credentials',
data: {'error': e.toString()},
);
// Return a consistent fallback fingerprint
return 'stable_fallback_device_id';
}
@@ -315,11 +361,9 @@ class SecureCredentialStorage {
username: oldCredentials['username'] ?? '',
password: oldCredentials['password'] ?? '',
);
DebugLogger.storage(
'Successfully migrated credentials to new secure format',
);
DebugLogger.storage('migrate-ok', scope: 'credentials');
} catch (e) {
DebugLogger.error('Failed to migrate credentials', e);
DebugLogger.error('migrate-failed', scope: 'credentials', error: e);
}
}
}

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart' hide debugPrint;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:share_handler/share_handler.dart' as sh;
@@ -12,6 +12,7 @@ import '../../core/providers/app_providers.dart';
import '../../shared/services/tasks/task_queue.dart';
import 'package:path/path.dart' as path;
import 'navigation_service.dart';
import '../utils/debug_logger.dart';
// Server chat creation/title generation occur on first send via chat providers
/// Lightweight payload for a share event
@@ -199,3 +200,8 @@ Future<void> _processPayload(Ref ref, SharedPayload payload) async {
debugPrint('ShareReceiver: failed to process payload: $e');
}
}
void debugPrint(String? message, {int? wrapWidth}) {
if (message == null) return;
DebugLogger.fromLegacy(message, scope: 'share');
}

View File

@@ -1,6 +1,11 @@
import 'package:socket_io_client/socket_io_client.dart' as io;
import 'package:flutter/foundation.dart';
import '../models/server_config.dart';
import '../utils/debug_logger.dart';
void debugPrint(String? message, {int? wrapWidth}) {
if (message == null) return;
DebugLogger.fromLegacy(message, scope: 'socket');
}
class SocketService {
final ServerConfig serverConfig;

View File

@@ -1,10 +1,15 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../models/server_config.dart';
import '../models/conversation.dart';
import 'secure_credential_storage.dart';
import '../utils/debug_logger.dart';
void debugPrint(String? message, {int? wrapWidth}) {
if (message == null) return;
DebugLogger.fromLegacy(message, scope: 'storage');
}
class StorageService {
final FlutterSecureStorage _secureStorage;

View File

@@ -1,7 +1,7 @@
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide debugPrint;
import '../../core/models/chat_message.dart';
import '../../core/services/persistent_streaming_service.dart';
@@ -12,6 +12,12 @@ import '../../core/utils/tool_calls_parser.dart';
import 'navigation_service.dart';
import '../../shared/widgets/themed_dialogs.dart';
import '../../shared/theme/theme_extensions.dart';
import '../utils/debug_logger.dart';
void debugPrint(String? message, {int? wrapWidth}) {
if (message == null) return;
DebugLogger.fromLegacy(message, scope: 'streaming/helper');
}
// Keep local verbosity toggle for socket logs
const bool kSocketVerboseLogging = false;

View File

@@ -1,8 +1,14 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/foundation.dart' hide debugPrint;
import 'package:flutter/material.dart';
import 'package:conduit/l10n/app_localizations.dart';
import '../../shared/theme/theme_extensions.dart';
import 'navigation_service.dart';
import '../utils/debug_logger.dart';
void debugPrint(String? message, {int? wrapWidth}) {
if (message == null) return;
DebugLogger.fromLegacy(message, scope: 'errors/user-friendly');
}
/// User-friendly error messages and recovery actions
class UserFriendlyErrorHandler {