Skip to content

Commit f844bbf

Browse files
author
Timothy Dodd
committed
Add validation and caching for log processing
- Added Microsoft.Extensions package references for caching and HTTP client support. - Registered `SettingsService` with memory caching in `Program.cs`. - Integrated `SettingsService` into `BatchingService` for log validation. - Enhanced `ProcessNewBatchAsync` to include pre-validation of logs. - Introduced `ValidateBatchAsync` for detailed log validation and error logging. - Updated `BatchingServiceStats` to track total validation failures. - Improved logging in `LogController` for validation errors and added endpoint for validation settings. - Created `SettingsService` to fetch and cache validation settings from an API. - Defined `ValidationSettings` for log validation constraints. - Implemented `LogLineValidator` for validating log lines and batches. - Added `ValidationResult` and `BatchValidationResult` classes for encapsulating validation results.
1 parent 3f09d4c commit f844bbf

6 files changed

Lines changed: 322 additions & 4 deletions

File tree

src/LogMkAgent/LogMkAgent.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
</PropertyGroup>
1414

1515
<ItemGroup>
16+
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="9.0.8" />
1617
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.8" />
1718
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.8" />
1819
<PackageReference Include="System.Text.Json" Version="9.0.8" />

src/LogMkAgent/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public static async Task Main(string[] args)
3333
// Register services
3434
services.AddSingleton<BatchingService>();
3535
services.AddSingleton<HttpLogger>();
36+
services.AddSingleton<SettingsService>();
37+
services.AddMemoryCache(); // Required for SettingsService caching
3638

3739
// Configure HttpClient with typed client
3840
services.AddHttpClient<LogApiClient>((serviceProvider, client) =>

src/LogMkAgent/Services/BatchService.cs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Collections.Concurrent;
22
using LogMkCommon;
33
using Microsoft.Extensions.Options;
4+
using LogMkAgent.Services;
45

56
namespace LogMkAgent.Services;
67

@@ -10,6 +11,7 @@ public class BatchingService : IDisposable
1011
private readonly LogApiClient _httpClient;
1112
private readonly ILogger<BatchingService> _logger;
1213
private readonly BatchingOptions _options;
14+
private readonly SettingsService _settingsService;
1315
private readonly Timer _timer;
1416
private readonly SemaphoreSlim _sendSemaphore = new(1, 1);
1517
private readonly CancellationTokenSource _cancellationTokenSource = new();
@@ -29,18 +31,21 @@ public class BatchingService : IDisposable
2931
private long _totalItemsProcessed;
3032
private long _totalBatchesSent;
3133
private long _totalFailures;
34+
private long _totalValidationFailures;
3235
private DateTime _lastSuccessfulSend = DateTime.UtcNow;
3336

3437
private volatile bool _disposed;
3538

3639
public BatchingService(
3740
IOptions<BatchingOptions> batchingOptions,
3841
LogApiClient client,
39-
ILogger<BatchingService> logger)
42+
ILogger<BatchingService> logger,
43+
SettingsService settingsService)
4044
{
4145
_httpClient = client ?? throw new ArgumentNullException(nameof(client));
4246
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
4347
_options = batchingOptions?.Value ?? throw new ArgumentNullException(nameof(batchingOptions));
48+
_settingsService = settingsService ?? throw new ArgumentNullException(nameof(settingsService));
4449

4550
ValidateOptions();
4651

@@ -172,7 +177,21 @@ private async Task ProcessNewBatchAsync()
172177
if (currentBatch.Count == 0)
173178
return;
174179

175-
var success = await SendBatchWithRetryAsync(currentBatch, 1).ConfigureAwait(false);
180+
// Pre-validate the batch before sending
181+
var validatedBatch = await ValidateBatchAsync(currentBatch).ConfigureAwait(false);
182+
if (validatedBatch.Count == 0)
183+
{
184+
_logger.LogWarning("Entire batch of {Count} logs failed validation, skipping send", currentBatch.Count);
185+
return;
186+
}
187+
188+
if (validatedBatch.Count < currentBatch.Count)
189+
{
190+
_logger.LogInformation("Pre-validation filtered {Filtered} invalid logs from batch of {Total}. Sending {Valid} valid logs.",
191+
currentBatch.Count - validatedBatch.Count, currentBatch.Count, validatedBatch.Count);
192+
}
193+
194+
var success = await SendBatchWithRetryAsync(validatedBatch, 1).ConfigureAwait(false);
176195

177196
if (!success)
178197
{
@@ -203,6 +222,44 @@ private List<LogLine> ExtractBatch()
203222
return batch;
204223
}
205224

225+
private async Task<List<LogLine>> ValidateBatchAsync(List<LogLine> batch)
226+
{
227+
try
228+
{
229+
var validator = await _settingsService.GetValidatorAsync().ConfigureAwait(false);
230+
if (validator == null)
231+
{
232+
_logger.LogWarning("Failed to get validator from settings service, sending batch without pre-validation");
233+
return batch;
234+
}
235+
236+
var validationResult = validator.ValidateBatch(batch);
237+
238+
if (validationResult.InvalidCount > 0)
239+
{
240+
Interlocked.Add(ref _totalValidationFailures, validationResult.InvalidCount);
241+
242+
// Log the first few validation errors for diagnostics
243+
var errorSummary = validationResult.ValidationResults
244+
.SelectMany(r => r.Errors)
245+
.GroupBy(error => error)
246+
.ToDictionary(g => g.Key, g => g.Count());
247+
248+
_logger.LogWarning("Pre-validation failed for {InvalidCount}/{TotalCount} logs. " +
249+
"Error breakdown: {ErrorSummary}",
250+
validationResult.InvalidCount, validationResult.TotalCount,
251+
string.Join(", ", errorSummary.Select(kvp => $"{kvp.Key}: {kvp.Value}")));
252+
}
253+
254+
return validationResult.ValidLogs;
255+
}
256+
catch (Exception ex)
257+
{
258+
_logger.LogError(ex, "Error during batch validation, sending batch without pre-validation");
259+
return batch;
260+
}
261+
}
262+
206263
private async Task<bool> SendBatchWithRetryAsync(List<LogLine> batch, int attemptNumber)
207264
{
208265
if (batch.Count == 0)
@@ -306,6 +363,7 @@ public BatchingServiceStats GetStats()
306363
TotalItemsProcessed = _totalItemsProcessed,
307364
TotalBatchesSent = _totalBatchesSent,
308365
TotalFailures = _totalFailures,
366+
TotalValidationFailures = _totalValidationFailures,
309367
PendingRetries = _retryQueue.Count,
310368
LastSuccessfulSend = _lastSuccessfulSend
311369
};
@@ -363,6 +421,7 @@ public class BatchingServiceStats
363421
public long TotalItemsProcessed { get; set; }
364422
public long TotalBatchesSent { get; set; }
365423
public long TotalFailures { get; set; }
424+
public long TotalValidationFailures { get; set; }
366425
public int PendingRetries { get; set; }
367426
public DateTime LastSuccessfulSend { get; set; }
368427
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using LogMkCommon;
2+
using Microsoft.Extensions.Caching.Memory;
3+
4+
namespace LogMkAgent.Services;
5+
6+
public class SettingsService
7+
{
8+
private readonly LogApiClient _apiClient;
9+
private readonly ILogger<SettingsService> _logger;
10+
private readonly IMemoryCache _cache;
11+
private readonly TimeSpan _cacheExpiry = TimeSpan.FromMinutes(30);
12+
private const string SETTINGS_CACHE_KEY = "validation_settings";
13+
14+
public SettingsService(LogApiClient apiClient, ILogger<SettingsService> logger, IMemoryCache cache)
15+
{
16+
_apiClient = apiClient;
17+
_logger = logger;
18+
_cache = cache;
19+
}
20+
21+
public async Task<ValidationSettings?> GetValidationSettingsAsync(CancellationToken cancellationToken = default)
22+
{
23+
// Try to get from cache first
24+
if (_cache.TryGetValue(SETTINGS_CACHE_KEY, out ValidationSettings? cachedSettings))
25+
{
26+
_logger.LogDebug("Using cached validation settings");
27+
return cachedSettings;
28+
}
29+
30+
try
31+
{
32+
_logger.LogDebug("Fetching validation settings from API");
33+
var settings = await _apiClient.GetDataAsync<ValidationSettings>("api/log/settings");
34+
35+
// Cache the settings
36+
_cache.Set(SETTINGS_CACHE_KEY, settings, _cacheExpiry);
37+
38+
_logger.LogInformation("Fetched validation settings: MaxDaysOld={MaxDaysOld}, MaxBatchSize={MaxBatchSize}, Version={Version}",
39+
settings.MaxDaysOld, settings.MaxBatchSize, settings.Version);
40+
41+
return settings;
42+
}
43+
catch (Exception ex)
44+
{
45+
_logger.LogWarning(ex, "Failed to fetch validation settings from API, using defaults");
46+
47+
// Return default settings if API call fails
48+
var defaultSettings = new ValidationSettings();
49+
_cache.Set(SETTINGS_CACHE_KEY, defaultSettings, TimeSpan.FromMinutes(5)); // Shorter cache for fallback
50+
51+
return defaultSettings;
52+
}
53+
}
54+
55+
public void InvalidateCache()
56+
{
57+
_cache.Remove(SETTINGS_CACHE_KEY);
58+
_logger.LogDebug("Validation settings cache invalidated");
59+
}
60+
61+
public async Task<LogLineValidator?> GetValidatorAsync(CancellationToken cancellationToken = default)
62+
{
63+
var settings = await GetValidationSettingsAsync(cancellationToken);
64+
return settings != null ? new LogLineValidator(settings) : null;
65+
}
66+
}

src/LogMkApi/Controllers/LogController.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,17 @@ public async Task<ActionResult<LogResponse>> Create(
179179
// Log validation errors for monitoring
180180
if (errors.Any())
181181
{
182-
_logger.LogWarning("Batch {BatchId}: {ErrorCount} validation errors, {SkippedCount} logs skipped",
183-
batchId, errors.Count, skippedCount);
182+
// Group errors by type for better insights
183+
var errorSummary = errors
184+
.SelectMany(e => e.Errors)
185+
.GroupBy(error => error)
186+
.ToDictionary(g => g.Key, g => g.Count());
187+
188+
_logger.LogWarning("Batch {BatchId}: {ErrorCount} validation errors, {SkippedCount} logs skipped. " +
189+
"Error breakdown: {ErrorSummary}",
190+
batchId, errors.Count, skippedCount,
191+
string.Join(", ", errorSummary.Select(kvp => $"{kvp.Key}: {kvp.Value}")));
192+
184193
_metrics.IncrementErrors("validation", skippedCount);
185194
}
186195

@@ -386,6 +395,29 @@ public async Task<IEnumerable<LatestDeploymentEntry>> GetLatestEntryTimes()
386395
var entries = await _logRepo.GetLatestEntryTimes();
387396
return entries;
388397
}
398+
399+
[AllowAnonymous]
400+
[HttpGet("settings")]
401+
public IActionResult GetValidationSettings()
402+
{
403+
var settings = new LogMkCommon.ValidationSettings
404+
{
405+
MaxDaysOld = LogMaxDaysOld,
406+
MaxFutureMinutes = 5,
407+
MaxLineLength = 10000,
408+
MaxDeploymentNameLength = 100,
409+
MaxPodNameLength = 100,
410+
DeploymentNamePattern = @"^[a-zA-Z0-9\-._]+$",
411+
PodNamePattern = @"^[a-zA-Z0-9\-._]+$",
412+
AllowEmptyLogLevel = false,
413+
MaxBatchSize = 1000,
414+
Version = "1.0",
415+
LastUpdated = DateTime.UtcNow
416+
};
417+
418+
_logger.LogDebug("Validation settings requested by agent");
419+
return Ok(settings);
420+
}
389421
[HttpGet("pods")]
390422
public async Task<IEnumerable<Pod>> GetPods()
391423
{

0 commit comments

Comments
 (0)