refactor(dotnet): add scheduler audit trail
This commit is contained in:
@@ -3,6 +3,7 @@ using Hangfire.States;
|
||||
using Hangfire.Dashboard;
|
||||
using Hangfire.PostgreSql;
|
||||
using Hangfire.MemoryStorage;
|
||||
using System.Text.Json;
|
||||
using System.Linq.Expressions;
|
||||
using QuantEngine.Application.Services;
|
||||
using QuantEngine.Application.Interfaces;
|
||||
@@ -22,6 +23,7 @@ public class SchedulerService
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly GatherTradingDataParser _parser;
|
||||
private readonly string _auditRoot;
|
||||
|
||||
public SchedulerService(
|
||||
ILogger<SchedulerService> logger,
|
||||
@@ -37,6 +39,55 @@ public class SchedulerService
|
||||
_scopeFactory = scopeFactory;
|
||||
_configuration = configuration;
|
||||
_parser = parser;
|
||||
_auditRoot = FindRepoTempRoot();
|
||||
}
|
||||
|
||||
private static string FindRepoTempRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current != null)
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(current.FullName, ".git")))
|
||||
{
|
||||
return Path.Combine(current.FullName, "Temp", "scheduler_audit");
|
||||
}
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return Path.Combine(Directory.GetCurrentDirectory(), "Temp", "scheduler_audit");
|
||||
}
|
||||
|
||||
private void AppendAudit(SchedulerJobExecutionAudit audit)
|
||||
{
|
||||
Directory.CreateDirectory(_auditRoot);
|
||||
var path = Path.Combine(_auditRoot, $"{audit.JobId}.jsonl");
|
||||
File.AppendAllText(path, JsonSerializer.Serialize(audit, new JsonSerializerOptions { WriteIndented = false }) + Environment.NewLine);
|
||||
}
|
||||
|
||||
private async Task<T> ExecuteWithAuditAsync<T>(string jobId, Func<Task<T>> action, Func<T>? fallback = null, string? resourceKey = null)
|
||||
{
|
||||
var startedAt = DateTimeOffset.UtcNow;
|
||||
AppendAudit(new SchedulerJobExecutionAudit(jobId, $"{jobId}-{startedAt:yyyyMMddHHmmssfff}", SchedulerStates.Running, null, startedAt, null, resourceKey));
|
||||
try
|
||||
{
|
||||
var result = await action();
|
||||
AppendAudit(new SchedulerJobExecutionAudit(jobId, $"{jobId}-{startedAt:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, startedAt, DateTimeOffset.UtcNow, resourceKey));
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AppendAudit(new SchedulerJobExecutionAudit(jobId, $"{jobId}-{startedAt:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, startedAt, DateTimeOffset.UtcNow, resourceKey));
|
||||
if (fallback is not null)
|
||||
{
|
||||
return fallback();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExecuteWithAuditAsync(string jobId, Func<Task> action, string? resourceKey = null)
|
||||
{
|
||||
await ExecuteWithAuditAsync(jobId, async () => { await action(); return true; }, resourceKey: resourceKey);
|
||||
}
|
||||
|
||||
private List<string> LoadTickersFromJson()
|
||||
@@ -139,6 +190,7 @@ public class SchedulerService
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Starting daily data collection job at {Time}", DateTime.Now);
|
||||
AppendAudit(new SchedulerJobExecutionAudit("daily-collection", $"daily-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "collection"));
|
||||
|
||||
var tickers = LoadTickersFromJson();
|
||||
|
||||
@@ -153,7 +205,10 @@ public class SchedulerService
|
||||
var accountMode = _configuration["Kis:AccountMode"] ?? "mock";
|
||||
|
||||
// Execute collection
|
||||
var result = await orchestrator.RunCollectionAsync(runId, accountMode, tickers);
|
||||
var result = await ExecuteWithAuditAsync(
|
||||
"daily-collection",
|
||||
() => orchestrator.RunCollectionAsync(runId, accountMode, tickers),
|
||||
resourceKey: "collection");
|
||||
|
||||
// Log completion
|
||||
_logger.LogInformation("Collection run {RunId} completed: {Snapshots} snapshots, {Errors} errors",
|
||||
@@ -173,6 +228,7 @@ public class SchedulerService
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Starting hourly price update at {Time}", DateTime.Now);
|
||||
AppendAudit(new SchedulerJobExecutionAudit("hourly-price-update", $"hourly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "price-update"));
|
||||
|
||||
var tickers = LoadTickersFromJson();
|
||||
|
||||
@@ -186,6 +242,7 @@ public class SchedulerService
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to enqueue price update for {Ticker}", ticker);
|
||||
AppendAudit(new SchedulerJobExecutionAudit("hourly-price-update", $"hourly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Retrying, ex.Message, DateTimeOffset.UtcNow, null, ticker));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,10 +265,12 @@ public class SchedulerService
|
||||
// TODO: Implement actual price fetching
|
||||
await Task.Delay(50);
|
||||
_logger.LogInformation("Price fetched successfully for {Ticker}", ticker);
|
||||
AppendAudit(new SchedulerJobExecutionAudit("fetch-price", $"fetch-{ticker}-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, ticker));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching price for {Ticker}", ticker);
|
||||
AppendAudit(new SchedulerJobExecutionAudit("fetch-price", $"fetch-{ticker}-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, ticker));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,15 +282,18 @@ public class SchedulerService
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Starting weekly report generation at {Time}", DateTime.Now);
|
||||
AppendAudit(new SchedulerJobExecutionAudit("weekly-report", $"weekly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "report"));
|
||||
|
||||
// TODO: Implement report generation logic
|
||||
await Task.Delay(500);
|
||||
|
||||
_logger.LogInformation("Weekly report generated successfully");
|
||||
AppendAudit(new SchedulerJobExecutionAudit("weekly-report", $"weekly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "report"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error generating weekly report");
|
||||
AppendAudit(new SchedulerJobExecutionAudit("weekly-report", $"weekly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "report"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,15 +305,18 @@ public class SchedulerService
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Starting monthly optimization at {Time}", DateTime.Now);
|
||||
AppendAudit(new SchedulerJobExecutionAudit("monthly-optimization", $"monthly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Pending, null, DateTimeOffset.UtcNow, null, "optimization"));
|
||||
|
||||
// TODO: Implement optimization logic
|
||||
await Task.Delay(1000);
|
||||
|
||||
_logger.LogInformation("Monthly optimization completed");
|
||||
AppendAudit(new SchedulerJobExecutionAudit("monthly-optimization", $"monthly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Succeeded, null, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "optimization"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error during monthly optimization");
|
||||
AppendAudit(new SchedulerJobExecutionAudit("monthly-optimization", $"monthly-{DateTime.UtcNow:yyyyMMddHHmmssfff}", SchedulerStates.Failed, ex.Message, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, "optimization"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user