feat: add centralized error logging system

- Implemented Error Log API to capture and store error logs from various services.
- Updated admin_ui to include Error Log Viewer component for inspecting logs.
- Enhanced error handling in auth_api, employee_api, and transit_api to report errors to the Error Log API.
- Modified existing services to utilize the new error logging functionality.
- Updated Docker Compose configuration to include the Error Log API service.
- Added error logging service in bus_terminal for Flutter application.
This commit is contained in:
Sven laptop
2026-05-17 23:58:23 +02:00
parent 73de0d713e
commit 0e19aa0521
31 changed files with 688 additions and 34 deletions
+12 -1
View File
@@ -2,14 +2,23 @@ import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import '../models/user.dart';
import 'error_log_service.dart';
/// Authentication Service
class AuthService {
static const String _apiBaseUrl = 'http://api.transit.local:5000';
static const String _apiBaseUrl = 'https://transit.xtransit.testinstance.nl';
static const String _tokenKey = 'auth_token';
late SharedPreferences _prefs;
Future<void> _reportError(String partOfService, Object error) {
return ErrorLogService.report(
service: 'bus_terminal',
partOfService: partOfService,
error: error.toString(),
);
}
Future<void> _initPrefs() async {
_prefs = await SharedPreferences.getInstance();
}
@@ -42,6 +51,7 @@ class AuthService {
return authResponse;
} catch (e) {
await _reportError('AuthService.login.parse', e);
// Surface the response body to help debug backend field names / formats
throw Exception('Login parse error: $e -- response body: $body');
}
@@ -51,6 +61,7 @@ class AuthService {
throw Exception('Login failed: ${response.statusCode}');
}
} catch (e) {
await _reportError('AuthService.login', e);
throw Exception('Login error: $e');
}
}
@@ -0,0 +1,26 @@
import 'dart:convert';
import 'package:http/http.dart' as http;
class ErrorLogService {
static const String _apiBaseUrl = 'https://transit.xtransit.testinstance.nl';
static Future<void> report({
required String service,
required String partOfService,
required String error,
}) async {
try {
await http.post(
Uri.parse('$_apiBaseUrl/error_log'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({
'service': service,
'partOfService': partOfService,
'error': error,
}),
);
} catch (_) {
// Intentionally ignore logging failures.
}
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
/// Custom exception for authentication errors
// Custom exception for authentication errors
import 'app_navigator.dart';
class UnauthorizedException implements Exception {
@@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:geolocator/geolocator.dart';
import 'package:http/http.dart' as http;
import 'package:latlong2/latlong.dart';
import 'error_log_service.dart';
import '../models/navigation.dart';
/// Navigation Service using OpenStreetMap
@@ -10,6 +11,13 @@ class NavigationService {
static const String _osrmBaseUrl = 'https://router.project-osrm.org/route/v1/driving';
/// Get current device location
Future<void> _reportError(String partOfService, Object error) {
return ErrorLogService.report(
service: 'bus_terminal',
partOfService: partOfService,
error: error.toString(),
);
}
Future<NavigationPoint?> getCurrentLocation() async {
try {
final permission = await Geolocator.checkPermission();
@@ -27,6 +35,7 @@ class NavigationService {
name: 'Current Location',
);
} catch (e) {
await _reportError('NavigationService.getCurrentLocation', e);
throw Exception('Failed to get location: $e');
}
}
@@ -88,6 +97,7 @@ class NavigationService {
throw Exception('Failed to get route: ${response.statusCode} -- response body: ${response.body}');
}
} catch (e) {
await _reportError('NavigationService.getRoute', e);
throw Exception('Navigation error: $e');
}
}
@@ -152,6 +162,7 @@ class NavigationService {
end: stops.last,
);
} catch (e) {
await _reportError('NavigationService.getRouteThroughStops', e);
throw Exception('Navigation error: $e');
}
}
@@ -252,6 +263,7 @@ class NavigationService {
orderedStops: orderedStops.isNotEmpty ? orderedStops : stops,
);
} catch (e) {
await _reportError('NavigationService.getOptimizedRouteThroughStops', e);
throw Exception('Navigation error: $e');
}
}
@@ -308,6 +320,7 @@ class NavigationService {
throw Exception('Failed to get routes: ${response.statusCode} -- response body: ${response.body}');
}
} catch (e) {
await _reportError('NavigationService.getMultipleRoutes', e);
throw Exception('Navigation error: $e');
}
}
@@ -358,6 +371,7 @@ class NavigationService {
return NavigationPoint(latitude: lat, longitude: lon, name: display);
} catch (e) {
await _reportError('NavigationService.geocodePlace', e);
throw Exception('Geocode error: $e');
}
}
@@ -3,6 +3,7 @@ import 'package:http/http.dart' as http;
import '../models/schedule.dart';
import '../models/navigation.dart';
import 'auth_service.dart';
import 'error_log_service.dart';
import 'exceptions.dart';
/// Schedule/Route Service
@@ -10,6 +11,14 @@ class ScheduleService {
static const String _apiBaseUrl = 'https://transit.xtransit.testinstance.nl';
final _authService = AuthService();
Future<void> _reportError(String partOfService, Object error) {
return ErrorLogService.report(
service: 'bus_terminal',
partOfService: partOfService,
error: error.toString(),
);
}
/// Get public schedule overview for a specific date.
Future<List<ServiceSchedule>> getSchedules(DateTime date, {int? lineId}) async {
try {
@@ -32,6 +41,7 @@ class ScheduleService {
throw Exception('Failed to load schedules: ${response.statusCode}');
} catch (e) {
await _reportError('ScheduleService.getSchedules', e);
throw Exception('Schedule error: $e');
}
}
@@ -57,6 +67,7 @@ class ScheduleService {
throw Exception('Failed to load recurring schedules: ${response.statusCode}');
} catch (e) {
await _reportError('ScheduleService.getRecurringSchedules', e);
throw Exception('Recurring schedule error: $e');
}
}
@@ -76,6 +87,7 @@ class ScheduleService {
throw Exception('Failed to load schedule stops: ${response.statusCode}');
} catch (e) {
await _reportError('ScheduleService.getScheduleStops', e);
throw Exception('Schedule stops error: $e');
}
}
@@ -130,6 +142,7 @@ class ScheduleService {
throw Exception('Failed to load schedule: ${response.statusCode}');
}
} catch (e) {
await _reportError('ScheduleService.getDailySchedule', e);
throw Exception('Schedule error: $e');
}
}
@@ -152,6 +165,7 @@ class ScheduleService {
throw Exception('Failed to load progress: ${response.statusCode}');
} catch (e) {
await _reportError('ScheduleService.getScheduleProgress', e);
throw Exception('Schedule progress error: $e');
}
}
@@ -177,6 +191,7 @@ class ScheduleService {
}
} catch (e) {
if (e is UnauthorizedException) rethrow;
await _reportError('ScheduleService.getRouteDetails', e);
throw Exception('Route error: $e');
}
}
@@ -200,6 +215,7 @@ class ScheduleService {
}
} catch (e) {
if (e is UnauthorizedException) rethrow;
await _reportError('ScheduleService.updateRouteStatus', e);
throw Exception('Status update error: $e');
}
}
@@ -228,6 +244,7 @@ class ScheduleService {
}
} catch (e) {
if (e is UnauthorizedException) rethrow;
await _reportError('ScheduleService.updateScheduleStatus', e);
throw Exception('Schedule status update error: $e');
}
}
@@ -266,6 +283,7 @@ class ScheduleService {
}
} catch (e) {
if (e is UnauthorizedException) rethrow;
await _reportError('ScheduleService.getStops', e);
throw Exception('Get stops error: $e');
}
}
@@ -298,6 +316,7 @@ class ScheduleService {
}
} catch (e) {
if (e is UnauthorizedException) rethrow;
await _reportError('ScheduleService.markScheduleStopPassed', e);
throw Exception('Mark stop passed error: $e');
}
}