Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions TelegramSearchBot.Common/LoggerHolders.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Serilog;

namespace TelegramSearchBot.Common {
/// <summary>
/// Shared logger holder for EF Core logging.
/// Initialized by TelegramSearchBot.Program and used by Database project.
/// </summary>
public static class LoggerHolders {
public static Serilog.ILogger EfCoreLogger { get; set; } = null!;
}
}
1 change: 1 addition & 0 deletions TelegramSearchBot.Common/TelegramSearchBot.Common.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="Serilog" Version="4.3.1" />
</ItemGroup>
</Project>
4 changes: 3 additions & 1 deletion TelegramSearchBot.Database/Model/DataDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ public class DataDbContext : DbContext {
public DataDbContext(DbContextOptions<DataDbContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
// 日志配置
optionsBuilder.LogTo(Log.Logger.Information, LogLevel.Information);
optionsBuilder.LogTo(
(TelegramSearchBot.Common.LoggerHolders.EfCoreLogger ?? Log.Logger).Information,
LogLevel.Information);

// 数据库配置应该由外部通过DbContextOptions提供
// 不要在这里配置默认数据库
Expand Down
4 changes: 3 additions & 1 deletion TelegramSearchBot.Database/Model/SearchCacheDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ public class SearchCacheDbContext : DbContext {
public SearchCacheDbContext(DbContextOptions<SearchCacheDbContext> options) : base(options) { }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.LogTo(Log.Logger.Information, LogLevel.Information);
optionsBuilder.LogTo(
(TelegramSearchBot.Common.LoggerHolders.EfCoreLogger ?? Log.Logger).Information,
LogLevel.Information);
}

protected override void OnModelCreating(ModelBuilder modelBuilder) {
Expand Down
11 changes: 11 additions & 0 deletions TelegramSearchBot/AppBootstrap/QRBootstrap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,22 @@ public static async Task Process(string[] args) {
continue;
}
var task = db.ListLeftPop("QRTasks").ToString();
if (string.IsNullOrWhiteSpace(task)) {
Log.Logger.Warning("Empty task from QRTasks queue — skipping");
continue;
}
var photoPath = db.StringGetDelete($"QRPost-{task}").ToString();
if (string.IsNullOrWhiteSpace(photoPath)) {
Log.Logger.Warning("Empty QRPost key for task {task} — data not yet available or already consumed", task);
continue;
}
Comment on lines +32 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Publish a result before skipping an invalid payload

On Line 32-35, continue drops the task without setting QRResult-{task}. The caller in SubProcessService.RunRpc waits for that key and will fail on timeout.

Proposed fix
 if (string.IsNullOrWhiteSpace(photoPath)) {
     Log.Logger.Warning("Empty QRPost key for task {task} — data not yet available or already consumed", task);
+    db.StringSet($"QRResult-{task}", string.Empty);
     continue;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (string.IsNullOrWhiteSpace(photoPath)) {
Log.Logger.Warning("Empty QRPost key for task {task} — data not yet available or already consumed", task);
continue;
}
if (string.IsNullOrWhiteSpace(photoPath)) {
Log.Logger.Warning("Empty QRPost key for task {task} — data not yet available or already consumed", task);
db.StringSet($"QRResult-{task}", string.Empty);
continue;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot/AppBootstrap/QRBootstrap.cs` around lines 32 - 35, The code
currently skips tasks when photoPath is empty and uses "continue", which drops
setting the expected result key "QRResult-{task}" and causes
SubProcessService.RunRpc to time out; replace the bare continue in the photoPath
null/whitespace branch by publishing a failure/empty result to the same
mechanism used elsewhere to set the "QRResult-{task}" key (use the same
method/Redis/publish helper used by QRBootstrap to write QRResult), include the
task id and an explicit error/empty payload, then continue so the caller waiting
in SubProcessService.RunRpc receives a timely result for that task.

string response = string.Empty;
Log.Logger.Information("QR processing started: task={task}, path={path}", task, photoPath);
try {
response = await qr.ExecuteAsync(photoPath);
Log.Logger.Information("QR result: task={task}, len={len}, content={preview}", task, response?.Length ?? -1, response?.Length > 100 ? response.Substring(0, 100) + "..." : response);
Comment on lines +37 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid logging raw QR path/content at Info level

Line 37 and Line 40 log photoPath and QR content preview. QR payloads often contain secrets/PII (tokens, private links). Log metadata only (task/length), not content.

Proposed fix
-Log.Logger.Information("QR processing started: task={task}, path={path}", task, photoPath);
+Log.Logger.Information("QR processing started: task={task}", task);

-Log.Logger.Information("QR result: task={task}, len={len}, content={preview}", task, response?.Length ?? -1, response?.Length > 100 ? response.Substring(0, 100) + "..." : response);
+Log.Logger.Information("QR result: task={task}, len={len}", task, response?.Length ?? -1);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Log.Logger.Information("QR processing started: task={task}, path={path}", task, photoPath);
try {
response = await qr.ExecuteAsync(photoPath);
Log.Logger.Information("QR result: task={task}, len={len}, content={preview}", task, response?.Length ?? -1, response?.Length > 100 ? response.Substring(0, 100) + "..." : response);
Log.Logger.Information("QR processing started: task={task}", task);
try {
response = await qr.ExecuteAsync(photoPath);
Log.Logger.Information("QR result: task={task}, len={len}", task, response?.Length ?? -1);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot/AppBootstrap/QRBootstrap.cs` around lines 37 - 40, The
current Log.Logger.Information calls in QRBootstrap (the calls that log task,
photoPath and the response preview after qr.ExecuteAsync(photoPath)) expose
sensitive QR paths/content; change these logs to only emit non-sensitive
metadata (e.g., task and response length via response?.Length ?? -1) and remove
photoPath and the response preview; if you need traceability keep a
hashed/redacted token (not raw content) or move full content to a
Debug/Trace-level log behind an explicit opt-in flag. Update the two
Log.Logger.Information invocations that reference photoPath and response to log
only task and length (and optionally a fixed "[REDACTED]" or hash) instead of
raw photoPath or response.Substring.

} catch (Exception ex) {
Log.Logger.Warning(ex, "QR processing failed for task {task}", task);
}

db.StringSet($"QRResult-{task}", response);
Expand Down
8 changes: 7 additions & 1 deletion TelegramSearchBot/Controller/AI/QR/AutoQRController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ public async Task ExecuteAsync(PipelineContext p) {
if (p.BotMessageType != BotMessageType.Message) {
return;
}
_logger.LogInformation("AutoQR processing started for {ChatId}/{MessageId}",
e.Message.Chat.Id, e.Message.MessageId);

try {
var filePath = IProcessPhoto.GetPhotoPath(e);
if (filePath == null) {
Expand All @@ -67,10 +70,13 @@ public async Task ExecuteAsync(PipelineContext p) {
var qrStr = await _autoQRService.ExecuteAsync(filePath);

if (string.IsNullOrWhiteSpace(qrStr)) {
_logger.LogInformation("No QR code detected for {ChatId}/{MessageId}",
e.Message.Chat.Id, e.Message.MessageId);
return;
}

_logger.LogInformation("QR Code recognized for {ChatId}/{MessageId}. Content: {QrStr}", e.Message.Chat.Id, e.Message.MessageId, qrStr);
_logger.LogInformation("QR Code recognized for {ChatId}/{MessageId}. Content: {QrStr}, Length: {QrStrLen}",
e.Message.Chat.Id, e.Message.MessageId, qrStr, qrStr.Length);
Comment on lines +78 to +79

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Do not log raw QR content

Line 78-79 logs qrStr directly. This can leak sensitive tokens/links into logs. Keep only metadata (length, maybe hash).

Proposed fix
-_logger.LogInformation("QR Code recognized for {ChatId}/{MessageId}. Content: {QrStr}, Length: {QrStrLen}",
-    e.Message.Chat.Id, e.Message.MessageId, qrStr, qrStr.Length);
+_logger.LogInformation("QR Code recognized for {ChatId}/{MessageId}. Length: {QrStrLen}",
+    e.Message.Chat.Id, e.Message.MessageId, qrStr.Length);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_logger.LogInformation("QR Code recognized for {ChatId}/{MessageId}. Content: {QrStr}, Length: {QrStrLen}",
e.Message.Chat.Id, e.Message.MessageId, qrStr, qrStr.Length);
_logger.LogInformation("QR Code recognized for {ChatId}/{MessageId}. Length: {QrStrLen}",
e.Message.Chat.Id, e.Message.MessageId, qrStr.Length);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot/Controller/AI/QR/AutoQRController.cs` around lines 78 - 79,
The LogInformation call in AutoQRController currently logs the raw QR content
(qrStr); change it to avoid sensitive data by removing qrStr from the message
and instead log only metadata such as qrStr.Length and a non-reversible
fingerprint (e.g., SHA256 hash) or a redacted placeholder. Locate the
_logger.LogInformation invocation and replace the qrStr parameter with
qrStr.Length and a computed hash string (compute via a helper method like
ComputeSha256Hash(string) or an existing utility) so the log line includes
ChatId, MessageId, Length and Hash but never the raw qrStr.


// Add QR result to processing results
p.ProcessingResults.Add($"[QR识别结果] {qrStr}");
Expand Down
32 changes: 30 additions & 2 deletions TelegramSearchBot/Extension/IDatabaseAsyncExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,45 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using StackExchange.Redis;

namespace TelegramSearchBot.Extension {
public static class IDatabaseAsyncExtension {
public static async Task<string> StringWaitGetDeleteAsync(this IDatabaseAsync db, string key) {
/// <summary>
/// Waits for a Redis key to appear and returns its value, then deletes the key.
/// Uses a polling approach with configurable timeout (default 30 seconds).
/// </summary>
/// <param name="db">The IDatabaseAsync instance.</param>
/// <param name="key">The Redis key to wait for.</param>
/// <param name="timeout">Maximum time to wait. Defaults to 30 seconds if null.</param>
/// <param name="cancellationToken">Cancellation token to abort the operation.</param>
/// <returns>The string value of the key.</returns>
/// <exception cref="OperationCanceledException">Thrown when cancellation is requested.</exception>
/// <exception cref="TimeoutException">Thrown when the timeout elapses before the key appears.</exception>
public static async Task<string> StringWaitGetDeleteAsync(
this IDatabaseAsync db,
string key,
TimeSpan? timeout = null,
CancellationToken cancellationToken = default)
{
var effectiveTimeout = timeout ?? TimeSpan.FromSeconds(30);
var startTime = DateTime.UtcNow;

while (true) {
cancellationToken.ThrowIfCancellationRequested();

var elapsed = DateTime.UtcNow - startTime;
if (elapsed > effectiveTimeout) {
throw new TimeoutException($"Timeout waiting for Redis key: {key}");
}

if (!( await db.StringGetAsync(key) ).Equals(RedisValue.Null)) {
// Note: check-then-delete is not atomic but acceptable here since only one consumer polls this key
return ( await db.StringGetDeleteAsync(key) ).ToString();
} else {
await Task.Delay(1000);
await Task.Delay(500, cancellationToken);
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions TelegramSearchBot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@
namespace TelegramSearchBot {
class Program {
static async Task Main(string[] args) {
// Separate logger for EF Core - writes only to logs/efcore-.txt
LoggerHolders.EfCoreLogger = new LoggerConfiguration()
.MinimumLevel.Information()
.WriteTo.File(
$"{Env.WorkDir}/logs/efcore-.txt",
rollingInterval: RollingInterval.Day,
outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss} {Level:u3}] [EF] {Message:lj}{NewLine}{Exception}")
.CreateLogger();

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information() // 设置最低日志级别
.MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", Serilog.Events.LogEventLevel.Debug) // SQL 语句只在 Debug 级别输出
Expand Down
19 changes: 15 additions & 4 deletions TelegramSearchBot/Service/Abstract/SubProcessService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using StackExchange.Redis;
using TelegramSearchBot.Attributes;
using TelegramSearchBot.Common;
Expand All @@ -22,10 +23,20 @@ public SubProcessService(IConnectionMultiplexer connectionMultiplexer) {
public async Task<string> RunRpc(string payload) {
var db = connectionMultiplexer.GetDatabase();
var guid = Guid.NewGuid();
await db.ListRightPushAsync($"{ForkName}Tasks", $"{guid}");
await db.StringSetAsync($"{ForkName}Post-{guid}", payload);
await AppBootstrap.AppBootstrap.RateLimitForkAsync([ForkName, $"{Env.SchedulerPort}"]);
return await db.StringWaitGetDeleteAsync($"{ForkName}Result-{guid}");
Log.Information("RunRpc started: ForkName={ForkName}, guid={guid}, payloadLen={payloadLen}",
ForkName, guid, payload?.Length ?? -1);
try {
await db.StringSetAsync($"{ForkName}Post-{guid}", payload);
await db.ListRightPushAsync($"{ForkName}Tasks", $"{guid}");
await AppBootstrap.AppBootstrap.RateLimitForkAsync([ForkName, $"{Env.SchedulerPort}"]);
var result = await db.StringWaitGetDeleteAsync($"{ForkName}Result-{guid}");
Log.Information("RunRpc completed: ForkName={ForkName}, guid={guid}, resultLen={resultLen}",
ForkName, guid, result?.Length ?? -1);
return result;
} catch (Exception ex) {
Log.Error(ex, "RunRpc failed: ForkName={ForkName}, guid={guid}", ForkName, guid);
throw;
}
}
}
}
10 changes: 4 additions & 6 deletions TelegramSearchBot/Service/BotAPI/SendMessageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,15 @@ public async Task TrySendMessageWithFallback(long chatId, int messageId, string

try {
if (isEdit) {
Message editedMessage = null;
await Send.AddTask(async () => {
editedMessage = await botClient.EditMessageText(
var editedMessage = await Send.AddTaskWithResult<Message>(async () => {
return await botClient.EditMessageText(
chatId: chatId, messageId: messageId, parseMode: currentParseMode, text: textToSend, linkPreviewOptions: linkPreviewOptions);
}, isGroup);

if (editedMessage != null && editedMessage.MessageId > 0) { logger.LogInformation($"Edited message {editedMessage.MessageId} successfully with {currentParseMode}."); } else { logger.LogWarning($"Editing message {messageId} with {currentParseMode} failed silently. Edit will be skipped."); }
} else {
Message sentMsg = null;
await Send.AddTask(async () => {
sentMsg = await botClient.SendMessage(
var sentMsg = await Send.AddTaskWithResult<Message>(async () => {
return await botClient.SendMessage(
chatId: chatId, text: textToSend, parseMode: currentParseMode,
replyParameters: new ReplyParameters() { MessageId = replyToMessageId },
linkPreviewOptions: linkPreviewOptions);
Expand Down
Loading