summaryrefslogtreecommitdiff
path: root/src/Agent.App/Logging/McpHostLogObserver.cs
blob: b1410e81b8928b92b52793f02861b33e6fe46f69 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
using System.Globalization;
using System.Text.Json;
using Agent.Core.Localization;
using Agent.Mcp;
using System.Collections.Concurrent;

namespace Agent.App.Logging;

public sealed class McpHostLogObserver : IMcpHostObserver, IDisposable
{
    private readonly RollingFileLog _application;
    private readonly RollingFileWriter? _http;
    private readonly LocaleCatalog _locale;
    private readonly string _logDirectory;
    private readonly ConcurrentDictionary<string, byte> _observedConnections = new(StringComparer.Ordinal);

    public McpHostLogObserver(RollingFileLog application, LocaleCatalog locale, string logDirectory,
        bool httpRequests, long maxBytes, int retainedFiles)
    {
        _application = application;
        _locale = locale;
        _logDirectory = logDirectory;
        if (httpRequests) _http = new RollingFileWriter(Path.Combine(logDirectory, "http.jsonl"), maxBytes, retainedFiles);
    }

    public void Ready(McpReadyInfo info)
    {
        string message = info.Transport == "http"
            ? _locale.Format("app.ready.http", new Dictionary<string, object?>
            {
                ["endpoint"] = info.Endpoint, ["protocol"] = info.ProtocolVersion, ["stateless"] = info.Stateless.ToString().ToLowerInvariant(),
                ["fallback"] = info.LegacyFallback.ToString().ToLowerInvariant(), ["pid"] = info.ProcessId, ["logs"] = _logDirectory
            })
            : _locale.Format("app.ready.stdio", new Dictionary<string, object?>
            {
                ["protocol"] = info.ProtocolVersion, ["pid"] = info.ProcessId, ["logs"] = _logDirectory
            });
        _application.Operational(message);
    }

    public void HttpRequestStarted(McpHttpRequestStartInfo info)
    {
        if (_observedConnections.TryAdd(info.ConnectionId, 0))
            _application.Information($"HTTP connection observed connection={info.ConnectionId} remote={info.RemoteIp}:{info.RemotePort}");
        _application.Information($"HTTP request started connection={info.ConnectionId} trace={info.TraceId} remote={info.RemoteIp}:{info.RemotePort} method={info.Method} path={info.Path}");
    }

    public void HttpRequest(McpHttpRequestInfo info)
    {
        _application.Information(string.Create(CultureInfo.InvariantCulture,
            $"HTTP request completed connection={info.ConnectionId} remote={info.RemoteIp}:{info.RemotePort} method={info.Method} path={info.Path} status={info.Status} duration_ms={info.DurationMs:F3}"));
        _http?.WriteLine(JsonSerializer.Serialize(new
        {
            timestamp = info.Timestamp,
            connection_id = info.ConnectionId,
            trace_id = info.TraceId,
            remote_ip = info.RemoteIp,
            remote_port = info.RemotePort,
            local_ip = info.LocalIp,
            local_port = info.LocalPort,
            method = info.Method,
            path = info.Path,
            status = info.Status,
            duration_ms = info.DurationMs,
            content_type = info.ContentType,
            content_length = info.ContentLength,
            user_agent = info.UserAgent,
            mcp_protocol_version = NullIfEmpty(info.ProtocolVersion),
            mcp_method = NullIfEmpty(info.McpMethod),
            mcp_name = NullIfEmpty(info.McpName)
        }));
    }

    private static string? NullIfEmpty(string? value) => string.IsNullOrWhiteSpace(value) ? null : value;
    public void Dispose() => _http?.Dispose();
}