Skip to content

Commit 12eb9e9

Browse files
committed
add Conversation Log Cleanup
1 parent 09e1512 commit 12eb9e9

7 files changed

Lines changed: 153 additions & 0 deletions

File tree

src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public class CleanConversationSetting
2323
public int BatchSize { get; set; }
2424
public int MessageLimit { get; set; }
2525
public int BufferHours { get; set; }
26+
public int LogRetentionDays { get; set; }
27+
public int LogBatchSize { get; set; } = 2000;
2628
public IEnumerable<string> ExcludeAgentIds { get; set; } = new List<string>();
2729
}
2830

src/Infrastructure/BotSharp.Abstraction/Repositories/IBotSharpRepository.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ Task<DateTimePagination<ConversationStateLogModel>> GetConversationStateLogs(str
198198
=> throw new NotImplementedException();
199199
#endregion
200200

201+
#region Log Cleanup
202+
Task<int> DeleteOldConversationLogs(int retentionDays, int batchSize)
203+
=> throw new NotImplementedException();
204+
#endregion
205+
201206
#region Instruction Log
202207
Task<bool> SaveInstructionLogs(IEnumerable<InstructionLogModel> logs)
203208
=> throw new NotImplementedException();

src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Log.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,15 @@ public async Task<DateTimePagination<ConversationStateLogModel>> GetConversation
178178
}
179179
#endregion
180180

181+
#region Log Cleanup
182+
public async Task<int> DeleteOldConversationLogs(int retentionDays, int batchSize = 2000)
183+
{
184+
// For file repository, we might not need to implement this fully or we can just return 0
185+
// as it's typically used for local dev. Implementing it would require iterating all conversations.
186+
return await Task.FromResult(0);
187+
}
188+
#endregion
189+
181190
#region Instruction Log
182191
public async Task<bool> SaveInstructionLogs(IEnumerable<InstructionLogModel> logs)
183192
{
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using BotSharp.Abstraction.Conversations.Settings;
2+
using BotSharp.Abstraction.Repositories;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.Hosting;
5+
using Microsoft.Extensions.Logging;
6+
using System;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
10+
namespace BotSharp.OpenAPI.BackgroundServices
11+
{
12+
public class ConversationLogCleanupService : BackgroundService
13+
{
14+
private readonly IServiceProvider _services;
15+
private readonly ILogger<ConversationLogCleanupService> _logger;
16+
17+
public ConversationLogCleanupService(IServiceProvider services, ILogger<ConversationLogCleanupService> logger)
18+
{
19+
_services = services;
20+
_logger = logger;
21+
}
22+
23+
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
24+
{
25+
_logger.LogInformation("Conversation Log Cleanup Service is running...");
26+
27+
_ = Task.Run(async () =>
28+
{
29+
await DoWork(stoppingToken);
30+
});
31+
}
32+
33+
private async Task DoWork(CancellationToken stoppingToken)
34+
{
35+
_logger.LogInformation("Conversation Log Cleanup Service is doing work...");
36+
37+
try
38+
{
39+
while (true)
40+
{
41+
stoppingToken.ThrowIfCancellationRequested();
42+
// Run once a day
43+
var delay = Task.Delay(TimeSpan.FromHours(24), stoppingToken);
44+
try
45+
{
46+
await CleanOldLogsAsync();
47+
}
48+
catch (Exception ex)
49+
{
50+
_logger.LogError(ex, $"Error occurred cleaning old conversation logs.");
51+
}
52+
await delay;
53+
}
54+
}
55+
catch (OperationCanceledException) { }
56+
}
57+
58+
public override async Task StopAsync(CancellationToken stoppingToken)
59+
{
60+
_logger.LogInformation("Conversation Log Cleanup Service is stopping.");
61+
await base.StopAsync(stoppingToken);
62+
}
63+
64+
private async Task CleanOldLogsAsync()
65+
{
66+
using var scope = _services.CreateScope();
67+
var settings = scope.ServiceProvider.GetRequiredService<ConversationSetting>();
68+
var cleanSetting = settings.CleanSetting;
69+
70+
if (cleanSetting == null || !cleanSetting.Enable || cleanSetting.LogRetentionDays <= 0) return;
71+
72+
var locker = scope.ServiceProvider.GetRequiredService<BotSharp.Abstraction.Infrastructures.IDistributedLocker>();
73+
var lockResource = nameof(ConversationLogCleanupService);
74+
await locker.LockAsync(lockResource, async () =>
75+
{
76+
await ExecuteCleanup(scope.ServiceProvider, cleanSetting.LogRetentionDays, cleanSetting.LogBatchSize);
77+
}, timeout: 3600); // 1 hour lock timeout to prevent other pods from running it
78+
}
79+
80+
private async Task ExecuteCleanup(IServiceProvider serviceProvider, int retentionDays, int batchSize)
81+
{
82+
var db = serviceProvider.GetRequiredService<IBotSharpRepository>();
83+
int totalDeleted = 0;
84+
85+
while (true)
86+
{
87+
var deletedCount = await db.DeleteOldConversationLogs(retentionDays, batchSize);
88+
if (deletedCount == 0) break;
89+
90+
totalDeleted += deletedCount;
91+
_logger.LogInformation($"Cleaned {deletedCount} conversation logs older than {retentionDays} days in this batch.");
92+
93+
// Sleep slightly to yield database resources
94+
await Task.Delay(1000);
95+
}
96+
97+
if (totalDeleted > 0)
98+
{
99+
_logger.LogInformation($"Successfully cleaned a total of {totalDeleted} conversation logs older than {retentionDays} days.");
100+
}
101+
}
102+
}
103+
}

src/Infrastructure/BotSharp.OpenAPI/BotSharpOpenApiExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static IServiceCollection AddBotSharpOpenAPI(this IServiceCollection serv
3737
{
3838
services.AddScoped<IUserIdentity, UserIdentity>();
3939
services.AddHostedService<ConversationTimeoutService>();
40+
services.AddHostedService<ConversationLogCleanupService>();
4041

4142
// Add bearer authentication
4243
var schema = "MIXED_SCHEME";

src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Log.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,37 @@ public async Task<DateTimePagination<ConversationStateLogModel>> GetConversation
139139
}
140140
#endregion
141141

142+
#region Log Cleanup
143+
public async Task<int> DeleteOldConversationLogs(int retentionDays, int batchSize)
144+
{
145+
if (retentionDays <= 0) return 0;
146+
var threshold = DateTime.UtcNow.AddDays(-retentionDays);
147+
148+
var contentLogFilter = Builders<ConversationContentLogDocument>.Filter.Lt(x => x.CreatedTime, threshold);
149+
var stateLogFilter = Builders<ConversationStateLogDocument>.Filter.Lt(x => x.CreatedTime, threshold);
150+
151+
var contentDocsToDelete = await _dc.ContentLogs.Find(contentLogFilter).Limit(batchSize).Project(x => x.Id).ToListAsync();
152+
long contentDeletedCount = 0;
153+
if (contentDocsToDelete.Any())
154+
{
155+
var deleteFilter = Builders<ConversationContentLogDocument>.Filter.In(x => x.Id, contentDocsToDelete);
156+
var contentDeleted = await _dc.ContentLogs.DeleteManyAsync(deleteFilter);
157+
contentDeletedCount = contentDeleted.DeletedCount;
158+
}
159+
160+
var stateDocsToDelete = await _dc.StateLogs.Find(stateLogFilter).Limit(batchSize).Project(x => x.Id).ToListAsync();
161+
long stateDeletedCount = 0;
162+
if (stateDocsToDelete.Any())
163+
{
164+
var deleteFilter = Builders<ConversationStateLogDocument>.Filter.In(x => x.Id, stateDocsToDelete);
165+
var stateDeleted = await _dc.StateLogs.DeleteManyAsync(deleteFilter);
166+
stateDeletedCount = stateDeleted.DeletedCount;
167+
}
168+
169+
return (int)(contentDeletedCount + stateDeletedCount);
170+
}
171+
#endregion
172+
142173
#region Instruction Log
143174
public async Task<bool> SaveInstructionLogs(IEnumerable<InstructionLogModel> logs)
144175
{

src/WebStarter/appsettings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,8 @@
707707
"BatchSize": 50,
708708
"MessageLimit": 2,
709709
"BufferHours": 12,
710+
"LogRetentionDays": 30,
711+
"LogBatchSize": 2000,
710712
"ExcludeAgentIds": []
711713
},
712714
"RateLimit": {

0 commit comments

Comments
 (0)