Files
coder/agent/agentchat/log_test.go
T
Ethan 3a9080fff6 feat: tag chat-originating agent logs with chat_id (#25019)
Workspace-agent logs emitted while serving chatd-driven requests were
not correlated with the originating chat, making agent logs hard to
attribute to the corresponding/originating chat.

This adds agent-side chat context middleware that parses `Coder-Chat-Id`
once, enriches agent access logs and structured handler/background logs,
and adds a chatd bridge log when chat headers are attached to an agent
connection.

Closes CODAGT-324
2026-05-08 13:25:30 +10:00

104 lines
3.1 KiB
Go

package agentchat_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"cdr.dev/slog/v3"
"github.com/coder/coder/v2/agent/agentchat"
"github.com/coder/coder/v2/coderd/httpmw/loggermw"
"github.com/coder/coder/v2/coderd/tracing"
"github.com/coder/coder/v2/codersdk/workspacesdk"
"github.com/coder/coder/v2/testutil"
)
func TestMiddlewareAccessLog(t *testing.T) {
t.Parallel()
chatID := uuid.New()
ancestorID := uuid.New()
sink := testutil.NewFakeSink(t)
handler := tracing.StatusWriterMiddleware(loggermw.Logger(sink.Logger())(
agentchat.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(http.StatusNoContent)
})),
))
req := httptest.NewRequest(http.MethodGet, "/test", nil)
req.Header.Set(workspacesdk.CoderChatIDHeader, chatID.String())
req.Header.Set(workspacesdk.CoderAncestorChatIDsHeader, mustMarshalJSON(t, []string{ancestorID.String()}))
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, req)
require.Equal(t, http.StatusNoContent, rw.Code)
entries := sink.Entries()
require.Len(t, entries, 1)
fields := fieldsByName(entries[0].Fields)
require.Equal(t, chatID.String(), fields["chat_id"])
require.Equal(t, []string{ancestorID.String()}, fields["ancestor_chat_ids"])
}
func TestMiddlewareWithoutChatHeader(t *testing.T) {
t.Parallel()
sink := testutil.NewFakeSink(t)
handler := tracing.StatusWriterMiddleware(loggermw.Logger(sink.Logger())(
agentchat.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, _ *http.Request) {
rw.WriteHeader(http.StatusNoContent)
})),
))
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, httptest.NewRequest(http.MethodGet, "/test", nil))
require.Equal(t, http.StatusNoContent, rw.Code)
entries := sink.Entries()
require.Len(t, entries, 1)
fields := fieldsByName(entries[0].Fields)
require.NotContains(t, fields, "chat_id")
require.NotContains(t, fields, "ancestor_chat_ids")
}
func TestMiddlewareContextFields(t *testing.T) {
t.Parallel()
chatID := uuid.New()
sink := testutil.NewFakeSink(t)
handler := tracing.StatusWriterMiddleware(loggermw.Logger(sink.Logger())(
agentchat.Middleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
sink.Logger().With(agentchat.Fields(r.Context())...).Info(r.Context(), "handler log")
rw.WriteHeader(http.StatusNoContent)
})),
))
req := httptest.NewRequest(http.MethodGet, "/test", nil)
req.Header.Set(workspacesdk.CoderChatIDHeader, chatID.String())
rw := httptest.NewRecorder()
handler.ServeHTTP(rw, req)
require.Equal(t, http.StatusNoContent, rw.Code)
entries := sink.Entries()
require.Len(t, entries, 2)
for _, entry := range entries {
if entry.Message != "handler log" {
continue
}
fields := fieldsByName(entry.Fields)
require.Equal(t, chatID.String(), fields["chat_id"])
return
}
t.Fatal("handler log entry not found")
}
func fieldsByName(fields []slog.Field) map[string]any {
byName := make(map[string]any, len(fields))
for _, field := range fields {
byName[field.Name] = field.Value
}
return byName
}