Compare commits
8 Commits
c5a4e350e9
...
50c10b6e75
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50c10b6e75 | ||
|
|
075b6d13af | ||
|
|
324f1d9c7c | ||
|
|
c592ca32fb | ||
|
|
7ce418d474 | ||
|
|
ab260ad0a6 | ||
|
|
b3b87df320 | ||
|
|
da73324e3a |
@@ -7,6 +7,7 @@
|
|||||||
<Project Path="src/ClaudeDo.Installer/ClaudeDo.Installer.csproj" />
|
<Project Path="src/ClaudeDo.Installer/ClaudeDo.Installer.csproj" />
|
||||||
<Project Path="src/ClaudeDo.Releases/ClaudeDo.Releases.csproj" />
|
<Project Path="src/ClaudeDo.Releases/ClaudeDo.Releases.csproj" />
|
||||||
<Project Path="src/ClaudeDo.Localization/ClaudeDo.Localization.csproj" />
|
<Project Path="src/ClaudeDo.Localization/ClaudeDo.Localization.csproj" />
|
||||||
|
<Project Path="src/ClaudeDo.Logging/ClaudeDo.Logging.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/tests/">
|
<Folder Name="/tests/">
|
||||||
<Project Path="tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj" />
|
<Project Path="tests/ClaudeDo.Data.Tests/ClaudeDo.Data.Tests.csproj" />
|
||||||
|
|||||||
@@ -24,10 +24,12 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ClaudeDo.Ui\ClaudeDo.Ui.csproj" />
|
<ProjectReference Include="..\ClaudeDo.Ui\ClaudeDo.Ui.csproj" />
|
||||||
|
<ProjectReference Include="..\ClaudeDo.Logging\ClaudeDo.Logging.csproj" />
|
||||||
<ProjectReference Include="..\ClaudeDo.Localization\ClaudeDo.Localization.csproj" />
|
<ProjectReference Include="..\ClaudeDo.Localization\ClaudeDo.Localization.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="..\ClaudeDo.Localization\Locales.targets" />
|
<Import Project="..\ClaudeDo.Localization\Locales.targets" />
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ using ClaudeDo.Ui.ViewModels.Modals;
|
|||||||
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
using ClaudeDo.Ui.ViewModels.Modals.Settings;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -77,6 +79,12 @@ sealed class Program
|
|||||||
|
|
||||||
var sc = new ServiceCollection();
|
var sc = new ServiceCollection();
|
||||||
|
|
||||||
|
var logRoot = Path.Combine(Path.GetDirectoryName(dbPath)!, "logs");
|
||||||
|
var serilogLogger = ClaudeDo.Logging.LoggingSetup
|
||||||
|
.Configure(new LoggerConfiguration(), "app", logRoot)
|
||||||
|
.CreateLogger();
|
||||||
|
sc.AddLogging(b => b.AddSerilog(serilogLogger, dispose: true));
|
||||||
|
|
||||||
// Infrastructure
|
// Infrastructure
|
||||||
sc.AddSingleton(settings);
|
sc.AddSingleton(settings);
|
||||||
var localesDir = Path.Combine(AppContext.BaseDirectory, "locales");
|
var localesDir = Path.Combine(AppContext.BaseDirectory, "locales");
|
||||||
@@ -98,7 +106,9 @@ sealed class Program
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
sc.AddSingleton<GitService>();
|
sc.AddSingleton<GitService>();
|
||||||
sc.AddSingleton(sp => new WorkerClient(sp.GetRequiredService<AppSettings>().SignalRUrl));
|
sc.AddSingleton(sp => new WorkerClient(
|
||||||
|
sp.GetRequiredService<AppSettings>().SignalRUrl,
|
||||||
|
sp.GetRequiredService<ILogger<WorkerClient>>()));
|
||||||
sc.AddSingleton<IWorkerClient>(sp => sp.GetRequiredService<WorkerClient>());
|
sc.AddSingleton<IWorkerClient>(sp => sp.GetRequiredService<WorkerClient>());
|
||||||
|
|
||||||
// Release check + installer update
|
// Release check + installer update
|
||||||
|
|||||||
14
src/ClaudeDo.Logging/BuildConfig.cs
Normal file
14
src/ClaudeDo.Logging/BuildConfig.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Logging;
|
||||||
|
|
||||||
|
/// <summary>Runtime build-configuration detection — the replacement for #if DEBUG.
|
||||||
|
/// Debug builds compile with the JIT optimizer disabled; Release builds enable it.</summary>
|
||||||
|
public static class BuildConfig
|
||||||
|
{
|
||||||
|
public static bool IsDebug { get; } =
|
||||||
|
Assembly.GetEntryAssembly()
|
||||||
|
?.GetCustomAttribute<DebuggableAttribute>()
|
||||||
|
?.IsJITOptimizerDisabled ?? false;
|
||||||
|
}
|
||||||
19
src/ClaudeDo.Logging/ClaudeDo.Logging.csproj
Normal file
19
src/ClaudeDo.Logging/ClaudeDo.Logging.csproj
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="ClaudeDo.Worker.Tests" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
15
src/ClaudeDo.Logging/DefaultTaskIdEnricher.cs
Normal file
15
src/ClaudeDo.Logging/DefaultTaskIdEnricher.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Logging;
|
||||||
|
|
||||||
|
/// <summary>Ensures every log event carries a TaskId property (defaulting to "-")
|
||||||
|
/// so the output template's [{TaskId}] column never renders the raw token.</summary>
|
||||||
|
public sealed class DefaultTaskIdEnricher : ILogEventEnricher
|
||||||
|
{
|
||||||
|
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
|
||||||
|
{
|
||||||
|
if (!logEvent.Properties.ContainsKey("TaskId"))
|
||||||
|
logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("TaskId", "-"));
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/ClaudeDo.Logging/LoggingSetup.cs
Normal file
44
src/ClaudeDo.Logging/LoggingSetup.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Serilog;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Logging;
|
||||||
|
|
||||||
|
public static class LoggingSetup
|
||||||
|
{
|
||||||
|
private const string OutputTemplate =
|
||||||
|
"[{Timestamp:HH:mm:ss.fff} {Level:u3}] {Process}/{SourceContext} [{TaskId}] {Message:lj}{NewLine}{Exception}";
|
||||||
|
|
||||||
|
public static LoggerConfiguration Configure(LoggerConfiguration cfg, string processTag, string logRoot)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(logRoot);
|
||||||
|
var logFile = Path.Combine(logRoot, "claudedo-.log");
|
||||||
|
|
||||||
|
cfg.Enrich.FromLogContext()
|
||||||
|
.Enrich.WithProperty("Process", processTag)
|
||||||
|
.Enrich.With(new DefaultTaskIdEnricher());
|
||||||
|
|
||||||
|
if (BuildConfig.IsDebug)
|
||||||
|
{
|
||||||
|
cfg.MinimumLevel.Debug()
|
||||||
|
.WriteTo.Console(outputTemplate: OutputTemplate)
|
||||||
|
.WriteTo.File(
|
||||||
|
logFile,
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
retainedFileCountLimit: 2,
|
||||||
|
shared: true,
|
||||||
|
outputTemplate: OutputTemplate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cfg.MinimumLevel.Warning()
|
||||||
|
.WriteTo.File(
|
||||||
|
logFile,
|
||||||
|
rollingInterval: RollingInterval.Day,
|
||||||
|
retainedFileCountLimit: 2,
|
||||||
|
shared: true,
|
||||||
|
outputTemplate: OutputTemplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,9 @@
|
|||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.1" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.11" />
|
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
|
||||||
|
<PackageReference Include="Serilog" Version="4.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.AspNetCore.SignalR.Client;
|
using Microsoft.AspNetCore.SignalR.Client;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Serilog.Context;
|
||||||
|
|
||||||
namespace ClaudeDo.Ui.Services;
|
namespace ClaudeDo.Ui.Services;
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ sealed class IndefiniteRetryPolicy : IRetryPolicy
|
|||||||
public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerClient
|
public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerClient
|
||||||
{
|
{
|
||||||
private readonly HubConnection _hub;
|
private readonly HubConnection _hub;
|
||||||
|
private readonly ILogger<WorkerClient> _logger;
|
||||||
private CancellationTokenSource? _startCts;
|
private CancellationTokenSource? _startCts;
|
||||||
private Task _retryLoopTask = Task.CompletedTask;
|
private Task _retryLoopTask = Task.CompletedTask;
|
||||||
private readonly object _startLock = new();
|
private readonly object _startLock = new();
|
||||||
@@ -65,8 +68,9 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
|
|
||||||
public string? LastMergeAllTarget { get; private set; }
|
public string? LastMergeAllTarget { get; private set; }
|
||||||
|
|
||||||
public WorkerClient(string signalRUrl)
|
public WorkerClient(string signalRUrl, ILogger<WorkerClient> logger)
|
||||||
{
|
{
|
||||||
|
_logger = logger;
|
||||||
_hub = new HubConnectionBuilder()
|
_hub = new HubConnectionBuilder()
|
||||||
.WithUrl(signalRUrl)
|
.WithUrl(signalRUrl)
|
||||||
.WithAutomaticReconnect(new IndefiniteRetryPolicy())
|
.WithAutomaticReconnect(new IndefiniteRetryPolicy())
|
||||||
@@ -240,20 +244,24 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
catch { return default; }
|
catch { return default; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RunNowAsync(string taskId)
|
/// <summary>Invoke a task-targeted hub method under a TaskId log scope, emitting a debug trace line.</summary>
|
||||||
|
private async Task InvokeForTaskAsync(string taskId, string method, params object?[] args)
|
||||||
{
|
{
|
||||||
await _hub.InvokeAsync("RunNow", taskId);
|
using (LogContext.PushProperty("TaskId", taskId))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("UI invoking {Method} for task {TaskId}", method, taskId);
|
||||||
|
await _hub.InvokeCoreAsync(method, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ContinueTaskAsync(string taskId, string followUpPrompt)
|
public Task RunNowAsync(string taskId)
|
||||||
{
|
=> InvokeForTaskAsync(taskId, "RunNow", taskId);
|
||||||
await _hub.InvokeAsync("ContinueTask", taskId, followUpPrompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ResetTaskAsync(string taskId)
|
public Task ContinueTaskAsync(string taskId, string followUpPrompt)
|
||||||
{
|
=> InvokeForTaskAsync(taskId, "ContinueTask", taskId, followUpPrompt);
|
||||||
await _hub.InvokeAsync("ResetTask", taskId);
|
|
||||||
}
|
public Task ResetTaskAsync(string taskId)
|
||||||
|
=> InvokeForTaskAsync(taskId, "ResetTask", taskId);
|
||||||
|
|
||||||
public async Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage)
|
public async Task<MergeResultDto> MergeTaskAsync(string taskId, string targetBranch, bool removeWorktree, string commitMessage)
|
||||||
{
|
{
|
||||||
@@ -264,10 +272,8 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
public Task<MergeTargetsDto?> GetMergeTargetsAsync(string taskId)
|
public Task<MergeTargetsDto?> GetMergeTargetsAsync(string taskId)
|
||||||
=> TryInvokeAsync<MergeTargetsDto>("GetMergeTargets", taskId);
|
=> TryInvokeAsync<MergeTargetsDto>("GetMergeTargets", taskId);
|
||||||
|
|
||||||
public async Task CancelTaskAsync(string taskId)
|
public Task CancelTaskAsync(string taskId)
|
||||||
{
|
=> InvokeForTaskAsync(taskId, "CancelTask", taskId);
|
||||||
await _hub.InvokeAsync("CancelTask", taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task WakeQueueAsync()
|
public async Task WakeQueueAsync()
|
||||||
{
|
{
|
||||||
@@ -386,25 +392,17 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
|
|||||||
await _hub.InvokeAsync("SetTaskStatus", taskId, status.ToString());
|
await _hub.InvokeAsync("SetTaskStatus", taskId, status.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApproveReviewAsync(string taskId)
|
public Task ApproveReviewAsync(string taskId)
|
||||||
{
|
=> InvokeForTaskAsync(taskId, "ApproveReview", taskId);
|
||||||
await _hub.InvokeAsync("ApproveReview", taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RejectReviewToQueueAsync(string taskId, string feedback)
|
public Task RejectReviewToQueueAsync(string taskId, string feedback)
|
||||||
{
|
=> InvokeForTaskAsync(taskId, "RejectReviewToQueue", taskId, feedback);
|
||||||
await _hub.InvokeAsync("RejectReviewToQueue", taskId, feedback);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task RejectReviewToIdleAsync(string taskId)
|
public Task RejectReviewToIdleAsync(string taskId)
|
||||||
{
|
=> InvokeForTaskAsync(taskId, "RejectReviewToIdle", taskId);
|
||||||
await _hub.InvokeAsync("RejectReviewToIdle", taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task CancelReviewAsync(string taskId)
|
public Task CancelReviewAsync(string taskId)
|
||||||
{
|
=> InvokeForTaskAsync(taskId, "CancelReview", taskId);
|
||||||
await _hub.InvokeAsync("CancelReview", taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null)
|
public Task<WorktreeCleanupDto?> CleanupFinishedWorktreesAsync(string? listId = null)
|
||||||
=> TryInvokeAsync<WorktreeCleanupDto>("CleanupFinishedWorktrees", listId);
|
=> TryInvokeAsync<WorktreeCleanupDto>("CleanupFinishedWorktrees", listId);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\ClaudeDo.Data\ClaudeDo.Data.csproj" />
|
<ProjectReference Include="..\ClaudeDo.Data\ClaudeDo.Data.csproj" />
|
||||||
|
<ProjectReference Include="..\ClaudeDo.Logging\ClaudeDo.Logging.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -31,13 +31,8 @@ var builder = WebApplication.CreateBuilder(args);
|
|||||||
|
|
||||||
var logRoot = cfg.LogRoot;
|
var logRoot = cfg.LogRoot;
|
||||||
Directory.CreateDirectory(logRoot);
|
Directory.CreateDirectory(logRoot);
|
||||||
builder.Host.UseSerilog((ctx, lc) => lc
|
builder.Host.UseSerilog((ctx, lc) =>
|
||||||
.MinimumLevel.Information()
|
ClaudeDo.Logging.LoggingSetup.Configure(lc, "worker", logRoot));
|
||||||
.WriteTo.File(
|
|
||||||
System.IO.Path.Combine(logRoot, "worker-.log"),
|
|
||||||
rollingInterval: RollingInterval.Day,
|
|
||||||
retainedFileCountLimit: 7,
|
|
||||||
shared: true));
|
|
||||||
|
|
||||||
builder.Services.AddDbContextFactory<ClaudeDoDbContext>(opt =>
|
builder.Services.AddDbContextFactory<ClaudeDoDbContext>(opt =>
|
||||||
opt.UseSqlite($"Data Source={cfg.DbPath}"));
|
opt.UseSqlite($"Data Source={cfg.DbPath}"));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ClaudeDo.Data;
|
using ClaudeDo.Data;
|
||||||
|
using Serilog.Context;
|
||||||
using ClaudeDo.Data.Models;
|
using ClaudeDo.Data.Models;
|
||||||
using ClaudeDo.Data.Repositories;
|
using ClaudeDo.Data.Repositories;
|
||||||
using ClaudeDo.Worker.Config;
|
using ClaudeDo.Worker.Config;
|
||||||
@@ -46,6 +47,7 @@ public sealed class TaskRunner
|
|||||||
|
|
||||||
public async Task RunAsync(TaskEntity task, string slot, CancellationToken ct)
|
public async Task RunAsync(TaskEntity task, string slot, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
using var _taskScope = LogContext.PushProperty("TaskId", task.Id);
|
||||||
string? mcpToken = null;
|
string? mcpToken = null;
|
||||||
string? mcpConfigPath = null;
|
string? mcpConfigPath = null;
|
||||||
try
|
try
|
||||||
@@ -170,6 +172,7 @@ public sealed class TaskRunner
|
|||||||
|
|
||||||
public async Task ContinueAsync(string taskId, string followUpPrompt, string slot, CancellationToken ct)
|
public async Task ContinueAsync(string taskId, string followUpPrompt, string slot, CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
using var _taskScope = LogContext.PushProperty("TaskId", taskId);
|
||||||
TaskEntity task;
|
TaskEntity task;
|
||||||
TaskRunEntity lastRun;
|
TaskRunEntity lastRun;
|
||||||
ListEntity list;
|
ListEntity list;
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<ProjectReference Include="..\..\src\ClaudeDo.Worker\ClaudeDo.Worker.csproj" />
|
<ProjectReference Include="..\..\src\ClaudeDo.Worker\ClaudeDo.Worker.csproj" />
|
||||||
<ProjectReference Include="..\..\src\ClaudeDo.Data\ClaudeDo.Data.csproj" />
|
<ProjectReference Include="..\..\src\ClaudeDo.Data\ClaudeDo.Data.csproj" />
|
||||||
<ProjectReference Include="..\..\src\ClaudeDo.Ui\ClaudeDo.Ui.csproj" />
|
<ProjectReference Include="..\..\src\ClaudeDo.Ui\ClaudeDo.Ui.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\ClaudeDo.Logging\ClaudeDo.Logging.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
19
tests/ClaudeDo.Worker.Tests/Logging/BuildConfigTests.cs
Normal file
19
tests/ClaudeDo.Worker.Tests/Logging/BuildConfigTests.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using ClaudeDo.Logging;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Tests.Logging;
|
||||||
|
|
||||||
|
public sealed class BuildConfigTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void IsDebug_MatchesEntryAssemblyDebuggableAttribute()
|
||||||
|
{
|
||||||
|
var entry = Assembly.GetEntryAssembly();
|
||||||
|
var expected = entry?
|
||||||
|
.GetCustomAttribute<DebuggableAttribute>()
|
||||||
|
?.IsJITOptimizerDisabled ?? false;
|
||||||
|
|
||||||
|
Assert.Equal(expected, BuildConfig.IsDebug);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using ClaudeDo.Logging;
|
||||||
|
using Serilog;
|
||||||
|
using Serilog.Context;
|
||||||
|
using Serilog.Core;
|
||||||
|
using Serilog.Events;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Tests.Logging;
|
||||||
|
|
||||||
|
public sealed class DefaultTaskIdEnricherTests
|
||||||
|
{
|
||||||
|
private sealed class CollectingSink : ILogEventSink
|
||||||
|
{
|
||||||
|
public List<LogEvent> Events { get; } = new();
|
||||||
|
public void Emit(LogEvent logEvent) => Events.Add(logEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AddsDash_WhenNoTaskIdInScope()
|
||||||
|
{
|
||||||
|
var sink = new CollectingSink();
|
||||||
|
using var logger = new LoggerConfiguration()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.With(new DefaultTaskIdEnricher())
|
||||||
|
.WriteTo.Sink(sink)
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
logger.Information("hello");
|
||||||
|
|
||||||
|
var prop = Assert.Single(sink.Events).Properties["TaskId"];
|
||||||
|
Assert.Equal("\"-\"", prop.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void KeepsPushedTaskId_WhenInScope()
|
||||||
|
{
|
||||||
|
var sink = new CollectingSink();
|
||||||
|
using var logger = new LoggerConfiguration()
|
||||||
|
.Enrich.FromLogContext()
|
||||||
|
.Enrich.With(new DefaultTaskIdEnricher())
|
||||||
|
.WriteTo.Sink(sink)
|
||||||
|
.CreateLogger();
|
||||||
|
|
||||||
|
using (LogContext.PushProperty("TaskId", "task-42"))
|
||||||
|
logger.Information("hello");
|
||||||
|
|
||||||
|
var prop = Assert.Single(sink.Events).Properties["TaskId"];
|
||||||
|
Assert.Equal("\"task-42\"", prop.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
30
tests/ClaudeDo.Worker.Tests/Logging/LoggingSetupTests.cs
Normal file
30
tests/ClaudeDo.Worker.Tests/Logging/LoggingSetupTests.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using ClaudeDo.Logging;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
|
namespace ClaudeDo.Worker.Tests.Logging;
|
||||||
|
|
||||||
|
public sealed class LoggingSetupTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Configure_WritesSharedLogFile()
|
||||||
|
{
|
||||||
|
var logRoot = Path.Combine(Path.GetTempPath(), "claudedo-logtest-" + Guid.NewGuid().ToString("N"));
|
||||||
|
Directory.CreateDirectory(logRoot);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var logger = LoggingSetup.Configure(new LoggerConfiguration(), "test", logRoot).CreateLogger();
|
||||||
|
logger.Warning("marker-{Marker}", "xyz");
|
||||||
|
logger.Dispose(); // flush + release the file handle
|
||||||
|
|
||||||
|
var files = Directory.GetFiles(logRoot, "claudedo-*.log");
|
||||||
|
var file = Assert.Single(files);
|
||||||
|
var contents = File.ReadAllText(file);
|
||||||
|
Assert.Contains("marker-", contents);
|
||||||
|
Assert.Contains("test/", contents); // {Process} tag in the template
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { Directory.Delete(logRoot, recursive: true); } catch { /* best effort */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user