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
SYED MUSTUFA AHMED NAQVI 2026-08-27 10:01:24 +05:00
parent 01ef651965
commit 7d0872b03b
3 changed files with 34 additions and 0 deletions

View File

@ -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;
}

View File

@ -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.");
}
}

View File

@ -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;
}
}