chore(logging): add trace logging to attendance, template, and photo services
Adds TraceEnter / TraceExit at service boundaries for attendance sync, template sync, and photo fetch.main
parent
01ef651965
commit
7d0872b03b
|
|
@ -11,6 +11,8 @@ public sealed class AttendanceSyncService(
|
|||
public async Task<SyncHistoryEntry> SyncAsync(Device device, DateTime fromLocal, DateTime toLocal, bool useLastSyncCursor,
|
||||
IProgress<AttendanceSyncProgress>? progress, CancellationToken cancellationToken)
|
||||
{
|
||||
AppLogger.TraceEnter("ATTENDANCE_SYNC", nameof(SyncAsync),
|
||||
$"device={device.IpAddress} from={fromLocal:yyyy-MM-dd HH:mm} to={toLocal:yyyy-MM-dd HH:mm} useCursor={useLastSyncCursor}");
|
||||
var entry = new SyncHistoryEntry
|
||||
{
|
||||
Operation = SyncOperationKind.AttendanceSync,
|
||||
|
|
@ -37,7 +39,9 @@ public sealed class AttendanceSyncService(
|
|||
}
|
||||
|
||||
progress?.Report(new AttendanceSyncProgress(0, 0, 0, 0, "Fetching attendance records from device…"));
|
||||
AppLogger.TraceEnter("ATTENDANCE_SYNC", "HikvisionIsapiClient.FetchAcsEventsAsync", $"device={device.IpAddress}");
|
||||
var punches = await hikvision.FetchAcsEventsAsync(device, fromLocal, toLocal, cancellationToken);
|
||||
AppLogger.TraceExit("ATTENDANCE_SYNC", "HikvisionIsapiClient.FetchAcsEventsAsync", $"fetched={punches.Count}");
|
||||
fetched = punches.Count;
|
||||
|
||||
for (var index = 0; index < punches.Count; index++)
|
||||
|
|
@ -81,6 +85,8 @@ public sealed class AttendanceSyncService(
|
|||
entry.Failed = failed;
|
||||
entry.Status = failed > 0 ? "Completed with errors" : "Completed";
|
||||
await history.UpdateAsync(entry, cancellationToken);
|
||||
AppLogger.TraceExit("ATTENDANCE_SYNC", nameof(SyncAsync),
|
||||
$"fetched={fetched} inserted={inserted} skipped={skipped} failed={failed}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -92,6 +98,7 @@ public sealed class AttendanceSyncService(
|
|||
entry.Status = "Failed";
|
||||
entry.Context += " — " + ex.Message;
|
||||
await history.UpdateAsync(entry, cancellationToken);
|
||||
AppLogger.TraceExit("ATTENDANCE_SYNC", nameof(SyncAsync), $"result=FAILED reason={ex.Message}");
|
||||
throw;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,24 +12,43 @@ public sealed class EmployeePhotoService
|
|||
|
||||
public async Task<ApiResultWithBytes> DownloadAndNormalizeAsync(HrmsEmployee employee, CancellationToken cancellationToken)
|
||||
{
|
||||
AppLogger.TraceEnter("USER_CREATE", nameof(DownloadAndNormalizeAsync), $"employee={employee.SerialNumber} id={employee.Id}");
|
||||
try
|
||||
{
|
||||
var response = await _client.GetAsync(GetPhotoUri(employee), cancellationToken);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
AppLogger.TraceExit("USER_CREATE", nameof(DownloadAndNormalizeAsync),
|
||||
$"employee={employee.SerialNumber} success=false http={(int)response.StatusCode}");
|
||||
return ApiResultWithBytes.Failed($"Photo not found or unavailable (HTTP {(int)response.StatusCode}).");
|
||||
}
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken);
|
||||
if (bytes.Length < 4 || bytes[0] != 0xFF || bytes[1] != 0xD8 || bytes[2] != 0xFF)
|
||||
{
|
||||
AppLogger.TraceExit("USER_CREATE", nameof(DownloadAndNormalizeAsync),
|
||||
$"employee={employee.SerialNumber} success=false reason=not-jpeg");
|
||||
return ApiResultWithBytes.Failed("Face processing failed: the employee photo is not a JPEG.");
|
||||
}
|
||||
if (bytes.Length > 2 * 1024 * 1024)
|
||||
{
|
||||
AppLogger.TraceExit("USER_CREATE", nameof(DownloadAndNormalizeAsync),
|
||||
$"employee={employee.SerialNumber} success=false reason=too-large");
|
||||
return ApiResultWithBytes.Failed("Face processing failed: photo exceeds the 2 MB safety limit.");
|
||||
}
|
||||
AppLogger.TraceExit("USER_CREATE", nameof(DownloadAndNormalizeAsync),
|
||||
$"employee={employee.SerialNumber} success=true bytes={bytes.Length}");
|
||||
return ApiResultWithBytes.Succeeded(bytes);
|
||||
}
|
||||
catch (TaskCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
AppLogger.TraceExit("USER_CREATE", nameof(DownloadAndNormalizeAsync),
|
||||
$"employee={employee.SerialNumber} success=false reason=timeout");
|
||||
return ApiResultWithBytes.Failed("Photo download timed out.");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
AppLogger.TraceExit("USER_CREATE", nameof(DownloadAndNormalizeAsync),
|
||||
$"employee={employee.SerialNumber} success=false reason=error");
|
||||
return ApiResultWithBytes.Failed("Photo download failed.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ public sealed class TemplateSyncService(
|
|||
{
|
||||
public async Task<SyncHistoryEntry> DeviceToDbAsync(Device device, IProgress<UserSyncProgress>? progress, CancellationToken cancellationToken)
|
||||
{
|
||||
AppLogger.TraceEnter("TEMPLATE_SYNC", nameof(DeviceToDbAsync), $"device={device.IpAddress} direction=DeviceToDb");
|
||||
var entry = new SyncHistoryEntry
|
||||
{
|
||||
Operation = SyncOperationKind.DeviceToDb,
|
||||
|
|
@ -19,6 +20,7 @@ public sealed class TemplateSyncService(
|
|||
await history.AddAsync(entry, cancellationToken);
|
||||
|
||||
var users = await hikvision.GetUsersAsync(device, cancellationToken);
|
||||
AppLogger.Info($"[TEMPLATE_SYNC] DeviceToDb loaded {users.Count} user(s) from device");
|
||||
entry.Total = users.Count;
|
||||
var success = 0;
|
||||
var failed = 0;
|
||||
|
|
@ -65,12 +67,16 @@ public sealed class TemplateSyncService(
|
|||
entry.Status = failed > 0 ? "Completed with errors" : "Completed";
|
||||
await history.UpdateAsync(entry, cancellationToken);
|
||||
progress?.Report(new UserSyncProgress(users.Count, users.Count, "", "Completed", success, 0, 0, 0, skipped, failed));
|
||||
AppLogger.TraceExit("TEMPLATE_SYNC", nameof(DeviceToDbAsync),
|
||||
$"success={success} failed={failed} skipped={skipped}");
|
||||
return entry;
|
||||
}
|
||||
|
||||
public async Task<SyncHistoryEntry> DbToDeviceAsync(Device source, Device target, IReadOnlyList<string> serialNumbers,
|
||||
IProgress<UserSyncProgress>? progress, CancellationToken cancellationToken)
|
||||
{
|
||||
AppLogger.TraceEnter("TEMPLATE_SYNC", nameof(DbToDeviceAsync),
|
||||
$"source={source.IpAddress} target={target.IpAddress} count={serialNumbers.Count}");
|
||||
if (string.Equals(source.MachineId, target.MachineId, StringComparison.OrdinalIgnoreCase) &&
|
||||
string.Equals(source.IpAddress, target.IpAddress, StringComparison.OrdinalIgnoreCase))
|
||||
throw new InvalidOperationException("Source and target devices must be different.");
|
||||
|
|
@ -151,6 +157,8 @@ public sealed class TemplateSyncService(
|
|||
entry.Status = failed > 0 ? "Completed with errors" : "Completed";
|
||||
await history.UpdateAsync(entry, cancellationToken);
|
||||
progress?.Report(new UserSyncProgress(serialNumbers.Count, serialNumbers.Count, "", "Completed", created, 0, faces, 0, skipped, failed));
|
||||
AppLogger.TraceExit("TEMPLATE_SYNC", nameof(DbToDeviceAsync),
|
||||
$"created={created} faces={faces} failed={failed} skipped={skipped}");
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue