Skip to content

Commit 3f6b34b

Browse files
committed
Add XunitLoggerProvider
1 parent 729e688 commit 3f6b34b

File tree

7 files changed

+127
-34
lines changed

7 files changed

+127
-34
lines changed

tests/ModelContextProtocol.TestSseServer/Program.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ namespace ModelContextProtocol.TestSseServer;
1010

1111
public class Program
1212
{
13-
private static ILoggerFactory CreateLoggerFactory()
13+
private static ILoggerFactory CreateLoggerFactory() => LoggerFactory.Create(ConfigureSerilog);
14+
15+
public static void ConfigureSerilog(ILoggingBuilder loggingBuilder)
1416
{
15-
// Use serilog
1617
Log.Logger = new LoggerConfiguration()
1718
.MinimumLevel.Verbose() // Capture all log levels
1819
.WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "TestServer_.log"),
@@ -21,15 +22,12 @@ private static ILoggerFactory CreateLoggerFactory()
2122
.CreateLogger();
2223

2324
var logsPath = Path.Combine(AppContext.BaseDirectory, "testserver.log");
24-
return LoggerFactory.Create(builder =>
25-
{
26-
builder.AddSerilog();
27-
});
25+
loggingBuilder.AddSerilog();
2826
}
2927

3028
public static Task Main(string[] args) => MainAsync(args);
3129

32-
public static async Task MainAsync(string[] args, CancellationToken cancellationToken = default)
30+
public static async Task MainAsync(string[] args, ILoggerFactory? loggerFactory = null, CancellationToken cancellationToken = default)
3331
{
3432
Console.WriteLine("Starting server...");
3533

@@ -385,7 +383,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st
385383
},
386384
};
387385

388-
using var loggerFactory = CreateLoggerFactory();
386+
loggerFactory ??= CreateLoggerFactory();
389387
server = McpServerFactory.Create(new HttpListenerSseServerTransport("TestServer", 3001, loggerFactory), options, loggerFactory);
390388

391389
Console.WriteLine("Server initialized.");

tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
namespace ModelContextProtocol.Tests;
77

8-
public class ClientIntegrationTestFixture : IDisposable
8+
public class ClientIntegrationTestFixture
99
{
10-
public ILoggerFactory LoggerFactory { get; }
10+
private ILoggerFactory? _loggerFactory;
11+
1112
public McpClientOptions DefaultOptions { get; }
1213
public McpServerConfig EverythingServerConfig { get; }
1314
public McpServerConfig TestServerConfig { get; }
@@ -16,10 +17,6 @@ public class ClientIntegrationTestFixture : IDisposable
1617

1718
public ClientIntegrationTestFixture()
1819
{
19-
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
20-
builder.AddConsole()
21-
.SetMinimumLevel(LogLevel.Debug));
22-
2320
DefaultOptions = new()
2421
{
2522
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" },
@@ -56,17 +53,16 @@ public ClientIntegrationTestFixture()
5653
}
5754
}
5855

56+
public void Initialize(ILoggerFactory loggerFactory)
57+
{
58+
_loggerFactory = loggerFactory;
59+
}
60+
5961
public Task<IMcpClient> CreateClientAsync(string clientId, McpClientOptions? clientOptions = null) =>
6062
McpClientFactory.CreateAsync(clientId switch
6163
{
6264
"everything" => EverythingServerConfig,
6365
"test_server" => TestServerConfig,
6466
_ => throw new ArgumentException($"Unknown client ID: {clientId}")
65-
}, clientOptions ?? DefaultOptions, loggerFactory: LoggerFactory);
66-
67-
public void Dispose()
68-
{
69-
LoggerFactory?.Dispose();
70-
GC.SuppressFinalize(this);
71-
}
67+
}, clientOptions ?? DefaultOptions, loggerFactory: _loggerFactory);
7268
}

tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@
66
using System.Text.Json;
77
using ModelContextProtocol.Configuration;
88
using ModelContextProtocol.Protocol.Transport;
9+
using ModelContextProtocol.Tests.Utils;
910
using Xunit.Sdk;
1011
using System.Text.Encodings.Web;
1112
using System.Text.Json.Serialization.Metadata;
1213
using System.Text.Json.Serialization;
1314

1415
namespace ModelContextProtocol.Tests;
1516

16-
public class ClientIntegrationTests : IClassFixture<ClientIntegrationTestFixture>
17+
public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegrationTestFixture>
1718
{
1819
private static readonly string? s_openAIKey = Environment.GetEnvironmentVariable("AI:OpenAI:ApiKey")!;
1920

2021
private readonly ClientIntegrationTestFixture _fixture;
2122

22-
public ClientIntegrationTests(ClientIntegrationTestFixture fixture)
23+
public ClientIntegrationTests(ClientIntegrationTestFixture fixture, ITestOutputHelper testOutputHelper)
24+
: base(testOutputHelper)
2325
{
2426
_fixture = fixture;
27+
_fixture.Initialize(LoggerFactory);
2528
}
2629

2730
public static IEnumerable<object[]> GetClients() =>
@@ -474,7 +477,7 @@ public async Task CallTool_Stdio_MemoryServer()
474477
await using var client = await McpClientFactory.CreateAsync(
475478
serverConfig,
476479
clientOptions,
477-
loggerFactory: _fixture.LoggerFactory,
480+
loggerFactory: LoggerFactory,
478481
cancellationToken: TestContext.Current.CancellationToken);
479482

480483
// act

tests/ModelContextProtocol.Tests/SseServerIntegrationTestFixture.cs

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,29 @@
22
using ModelContextProtocol.Client;
33
using ModelContextProtocol.Configuration;
44
using ModelContextProtocol.Protocol.Transport;
5+
using ModelContextProtocol.Test.Utils;
6+
using ModelContextProtocol.TestSseServer;
57

68
namespace ModelContextProtocol.Tests;
79

810
public class SseServerIntegrationTestFixture : IAsyncDisposable
911
{
10-
private readonly CancellationTokenSource _stopCts = new();
1112
private readonly Task _serverTask;
13+
private readonly CancellationTokenSource _stopCts = new();
14+
private DelegatingTestOutputHelper _delegatingTestOutputHelper = new();
15+
16+
private ILoggerFactory _redirectingLoggerFactory;
1217

13-
public ILoggerFactory LoggerFactory { get; }
1418
public McpClientOptions DefaultOptions { get; }
1519
public McpServerConfig DefaultConfig { get; }
1620

1721
public SseServerIntegrationTestFixture()
1822
{
19-
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
20-
builder.AddConsole()
21-
.SetMinimumLevel(LogLevel.Debug));
23+
_redirectingLoggerFactory = LoggerFactory.Create(builder =>
24+
{
25+
Program.ConfigureSerilog(builder);
26+
builder.AddProvider(new XunitLoggerProvider(_delegatingTestOutputHelper));
27+
});
2228

2329
DefaultOptions = new()
2430
{
@@ -34,12 +40,17 @@ public SseServerIntegrationTestFixture()
3440
Location = "http://localhost:3001/sse"
3541
};
3642

37-
_serverTask = TestSseServer.Program.MainAsync([], _stopCts.Token);
43+
_serverTask = Program.MainAsync([], _redirectingLoggerFactory, _stopCts.Token);
44+
}
45+
46+
public void Initialize(ITestOutputHelper output)
47+
{
48+
_delegatingTestOutputHelper.CurrentTestOutputHelper = output;
3849
}
3950

4051
public async ValueTask DisposeAsync()
4152
{
42-
LoggerFactory.Dispose();
53+
_redirectingLoggerFactory.Dispose();
4354
_stopCts.Cancel();
4455
try
4556
{
@@ -50,4 +61,16 @@ public async ValueTask DisposeAsync()
5061
}
5162
_stopCts.Dispose();
5263
}
64+
65+
private class DelegatingTestOutputHelper() : ITestOutputHelper
66+
{
67+
public ITestOutputHelper? CurrentTestOutputHelper { get; set; }
68+
69+
public string Output => CurrentTestOutputHelper?.Output ?? string.Empty;
70+
71+
public void Write(string message) => CurrentTestOutputHelper?.Write(message);
72+
public void Write(string format, params object[] args) => CurrentTestOutputHelper?.Write(format, args);
73+
public void WriteLine(string message) => CurrentTestOutputHelper?.WriteLine(message);
74+
public void WriteLine(string format, params object[] args) => CurrentTestOutputHelper?.WriteLine(format, args);
75+
}
5376
}

tests/ModelContextProtocol.Tests/SseServerIntegrationTests.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
11
using ModelContextProtocol.Client;
22
using ModelContextProtocol.Protocol.Types;
3+
using ModelContextProtocol.Tests.Utils;
34

45
namespace ModelContextProtocol.Tests;
56

6-
public class SseServerIntegrationTests : IClassFixture<SseServerIntegrationTestFixture>
7+
public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerIntegrationTestFixture>
78
{
89
private readonly SseServerIntegrationTestFixture _fixture;
910

10-
public SseServerIntegrationTests(SseServerIntegrationTestFixture fixture)
11+
public SseServerIntegrationTests(SseServerIntegrationTestFixture fixture, ITestOutputHelper output)
12+
: base(output)
1113
{
1214
_fixture = fixture;
15+
_fixture.Initialize(output);
1316
}
1417

1518
private Task<IMcpClient> GetClientAsync(McpClientOptions? options = null)
1619
{
1720
return McpClientFactory.CreateAsync(
1821
_fixture.DefaultConfig,
1922
options ?? _fixture.DefaultOptions,
20-
loggerFactory: _fixture.LoggerFactory);
23+
loggerFactory: LoggerFactory);
2124
}
2225

2326
[Fact]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Microsoft.Extensions.Logging;
2+
using ModelContextProtocol.Test.Utils;
3+
4+
namespace ModelContextProtocol.Tests.Utils;
5+
6+
public class LoggedTest(ITestOutputHelper testOutputHelper)
7+
{
8+
public ITestOutputHelper TestOutputHelper { get; } = testOutputHelper;
9+
public ILoggerFactory LoggerFactory { get; } = CreateLoggerFactory(testOutputHelper);
10+
11+
private static ILoggerFactory CreateLoggerFactory(ITestOutputHelper testOutputHelper)
12+
{
13+
return Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
14+
{
15+
builder.AddProvider(new XunitLoggerProvider(testOutputHelper));
16+
});
17+
}
18+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System.Globalization;
2+
using System.Text;
3+
using Microsoft.Extensions.Logging;
4+
5+
namespace ModelContextProtocol.Test.Utils;
6+
7+
public class XunitLoggerProvider(ITestOutputHelper output) : ILoggerProvider
8+
{
9+
public ILogger CreateLogger(string categoryName)
10+
{
11+
return new XunitLogger(output, categoryName);
12+
}
13+
14+
public void Dispose()
15+
{
16+
}
17+
18+
private class XunitLogger(ITestOutputHelper output, string category) : ILogger
19+
{
20+
public void Log<TState>(
21+
LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
22+
{
23+
var sb = new StringBuilder();
24+
25+
var timestamp = DateTimeOffset.UtcNow.ToString("s", CultureInfo.InvariantCulture);
26+
var prefix = $"| [{timestamp}] {category} {logLevel}: ";
27+
var lines = formatter(state, exception);
28+
sb.Append(prefix);
29+
sb.Append(lines);
30+
31+
if (exception is not null)
32+
{
33+
sb.AppendLine();
34+
sb.Append(exception.ToString());
35+
}
36+
37+
output.WriteLine(sb.ToString());
38+
}
39+
40+
public bool IsEnabled(LogLevel logLevel) => true;
41+
42+
public IDisposable BeginScope<TState>(TState state) where TState : notnull
43+
=> new NoopDisposable();
44+
45+
private sealed class NoopDisposable : IDisposable
46+
{
47+
public void Dispose()
48+
{
49+
}
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)