Files
summercms/modules/pier/audit.go
Jakub Zych cfdbaf0e47 feat(19.7-02): pier MCP module tracer with in-process dispatch and sessions
- modules/pier: Host contract, mcp.* config, catalog compiled from
  pact.HasMCP (prefixing, route binding, guard resolution, schema
  reflection, annotations), per-request identity through the published
  verifier and resolvers, built-in backend guard provider, in-process
  dispatch with the bouncer marker, per-identity SDK server with the
  per-request re-filter, stateful session router with rehydration over a
  memory store, audit lines
- modules/surf/pier_on.go: the summercms_mcp hook adapting the router to
  pier.Host; the untagged hello binary carries no SDK
- go.mod: go-sdk v1.8.0 and jsonschema-go v0.4.3 (decision note committed
  before this change)
- TestPierTracerSmoke: SDK client lists and calls acme_echo end to end
  through surf's compiled guard chain; README and root table row
2026-10-22 18:15:59 +02:00

72 lines
2.0 KiB
Go

package pier
import (
"log/slog"
"time"
"git.golem15.com/golem15/summercms/modules/diary"
)
// LogComponent is the diary component pier logs under.
const LogComponent = "pier"
// sessionPrefixLen is how much of a session id the audit lines show.
const sessionPrefixLen = 8
// stateless is the session value of a call served on the stateless path.
const stateless = "stateless"
// logger is the module's logger (component pier).
func logger() *slog.Logger {
return diary.For(LogComponent)
}
// sessionPrefix shortens a session id for logging; a stateless call logs
// "stateless".
func sessionPrefix(id string) string {
if id == "" {
return stateless
}
if len(id) > sessionPrefixLen {
return id[:sessionPrefixLen]
}
return id
}
// logToolCall writes the one audit line per tool call (D-23): the tool,
// its guard, the frontend user id (0 when none), the backend admin id (0
// when none), the OAuth client id, the token kind, the session prefix, the
// route's status and the duration. Never the arguments, the body or a token.
func logToolCall(log *slog.Logger, id Identity, t *tool, session string, status int, took time.Duration) {
var userID, adminID uint
var clientID string
if id.Frontend != nil {
userID = id.Frontend.UserID
clientID = id.Frontend.ClientID
}
if id.Backend != nil {
adminID = id.Backend.UserID
if clientID == "" {
clientID = id.Backend.ClientID
}
}
log.Info("pier: tool call",
"tool", t.name,
"guard", t.guardName,
"user_id", userID,
"admin_id", adminID,
"client_id", clientID,
"token_kind", id.TokenKind,
"session", sessionPrefix(session),
"status", status,
"duration_ms", took.Milliseconds(),
)
}
// logSession writes one line per session lifecycle step (open, rehydrate,
// end, binding_refused, expired) with the session prefix and the binding,
// never the token.
func logSession(log *slog.Logger, step, session, binding string) {
log.Info("pier: session", "step", step, "session", sessionPrefix(session), "binding", binding)
}