mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(events): add configurable runtime event logging
This commit is contained in:
+7
-3
@@ -39,9 +39,10 @@ type AgentLoop struct {
|
||||
state *state.Manager
|
||||
|
||||
// Runtime event system
|
||||
runtimeEvents runtimeevents.Bus
|
||||
ownsRuntimeEvents bool
|
||||
hooks *HookManager
|
||||
runtimeEvents runtimeevents.Bus
|
||||
ownsRuntimeEvents bool
|
||||
runtimeEventLogSub runtimeevents.Subscription
|
||||
hooks *HookManager
|
||||
|
||||
// Runtime state
|
||||
running atomic.Bool
|
||||
@@ -285,6 +286,9 @@ func (al *AgentLoop) Close() {
|
||||
if al.hooks != nil {
|
||||
al.hooks.Close()
|
||||
}
|
||||
if al.runtimeEventLogSub != nil {
|
||||
_ = al.runtimeEventLogSub.Close()
|
||||
}
|
||||
if al.runtimeEvents != nil && al.ownsRuntimeEvents {
|
||||
if err := al.runtimeEvents.Close(); err != nil {
|
||||
logger.ErrorCF("agent", "Failed to close runtime event bus",
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"fmt"
|
||||
|
||||
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
func (al *AgentLoop) newTurnEventScope(agentID, sessionKey string, turnCtx *TurnContext) turnEventScope {
|
||||
@@ -48,108 +47,9 @@ func (al *AgentLoop) emitEvent(kind runtimeevents.Kind, meta HookMeta, payload a
|
||||
return
|
||||
}
|
||||
|
||||
al.logEvent(evt, clonedMeta, eventCtx)
|
||||
|
||||
al.publishRuntimeEvent(evt)
|
||||
}
|
||||
|
||||
func (al *AgentLoop) logEvent(evt runtimeevents.Event, meta HookMeta, eventCtx *TurnContext) {
|
||||
fields := map[string]any{
|
||||
"event_kind": evt.Kind.String(),
|
||||
"agent_id": meta.AgentID,
|
||||
"turn_id": meta.TurnID,
|
||||
"session_key": meta.SessionKey,
|
||||
"iteration": meta.Iteration,
|
||||
}
|
||||
|
||||
if meta.TracePath != "" {
|
||||
fields["trace"] = meta.TracePath
|
||||
}
|
||||
if meta.Source != "" {
|
||||
fields["source"] = meta.Source
|
||||
}
|
||||
|
||||
appendEventContextFields(fields, eventCtx)
|
||||
|
||||
switch payload := evt.Payload.(type) {
|
||||
case TurnStartPayload:
|
||||
fields["user_len"] = len(payload.UserMessage)
|
||||
fields["media_count"] = payload.MediaCount
|
||||
case TurnEndPayload:
|
||||
fields["status"] = payload.Status
|
||||
fields["iterations_total"] = payload.Iterations
|
||||
fields["duration_ms"] = payload.Duration.Milliseconds()
|
||||
fields["final_len"] = payload.FinalContentLen
|
||||
case LLMRequestPayload:
|
||||
fields["model"] = payload.Model
|
||||
fields["messages"] = payload.MessagesCount
|
||||
fields["tools"] = payload.ToolsCount
|
||||
fields["max_tokens"] = payload.MaxTokens
|
||||
case LLMDeltaPayload:
|
||||
fields["content_delta_len"] = payload.ContentDeltaLen
|
||||
fields["reasoning_delta_len"] = payload.ReasoningDeltaLen
|
||||
case LLMResponsePayload:
|
||||
fields["content_len"] = payload.ContentLen
|
||||
fields["tool_calls"] = payload.ToolCalls
|
||||
fields["has_reasoning"] = payload.HasReasoning
|
||||
case LLMRetryPayload:
|
||||
fields["attempt"] = payload.Attempt
|
||||
fields["max_retries"] = payload.MaxRetries
|
||||
fields["reason"] = payload.Reason
|
||||
fields["error"] = payload.Error
|
||||
fields["backoff_ms"] = payload.Backoff.Milliseconds()
|
||||
case ContextCompressPayload:
|
||||
fields["reason"] = payload.Reason
|
||||
fields["dropped_messages"] = payload.DroppedMessages
|
||||
fields["remaining_messages"] = payload.RemainingMessages
|
||||
case SessionSummarizePayload:
|
||||
fields["summarized_messages"] = payload.SummarizedMessages
|
||||
fields["kept_messages"] = payload.KeptMessages
|
||||
fields["summary_len"] = payload.SummaryLen
|
||||
fields["omitted_oversized"] = payload.OmittedOversized
|
||||
case ToolExecStartPayload:
|
||||
fields["tool"] = payload.Tool
|
||||
fields["args_count"] = len(payload.Arguments)
|
||||
case ToolExecEndPayload:
|
||||
fields["tool"] = payload.Tool
|
||||
fields["duration_ms"] = payload.Duration.Milliseconds()
|
||||
fields["for_llm_len"] = payload.ForLLMLen
|
||||
fields["for_user_len"] = payload.ForUserLen
|
||||
fields["is_error"] = payload.IsError
|
||||
fields["async"] = payload.Async
|
||||
case ToolExecSkippedPayload:
|
||||
fields["tool"] = payload.Tool
|
||||
fields["reason"] = payload.Reason
|
||||
case SteeringInjectedPayload:
|
||||
fields["count"] = payload.Count
|
||||
fields["total_content_len"] = payload.TotalContentLen
|
||||
case FollowUpQueuedPayload:
|
||||
fields["source_tool"] = payload.SourceTool
|
||||
fields["content_len"] = payload.ContentLen
|
||||
case InterruptReceivedPayload:
|
||||
fields["interrupt_kind"] = payload.Kind
|
||||
fields["role"] = payload.Role
|
||||
fields["content_len"] = payload.ContentLen
|
||||
fields["queue_depth"] = payload.QueueDepth
|
||||
fields["hint_len"] = payload.HintLen
|
||||
case SubTurnSpawnPayload:
|
||||
fields["child_agent_id"] = payload.AgentID
|
||||
fields["label"] = payload.Label
|
||||
case SubTurnEndPayload:
|
||||
fields["child_agent_id"] = payload.AgentID
|
||||
fields["status"] = payload.Status
|
||||
case SubTurnResultDeliveredPayload:
|
||||
fields["target_channel"] = payload.TargetChannel
|
||||
fields["target_chat_id"] = payload.TargetChatID
|
||||
fields["content_len"] = payload.ContentLen
|
||||
case ErrorPayload:
|
||||
fields["stage"] = payload.Stage
|
||||
fields["error"] = payload.Message
|
||||
}
|
||||
|
||||
logger.InfoCF("eventbus", fmt.Sprintf("Agent event: %s", evt.Kind.String()), fields)
|
||||
}
|
||||
|
||||
// MountHook registers an in-process hook on the agent loop.
|
||||
func (al *AgentLoop) MountHook(reg HookRegistration) error {
|
||||
if al == nil || al.hooks == nil {
|
||||
|
||||
@@ -75,6 +75,7 @@ func NewAgentLoop(
|
||||
al.runtimeEvents = runtimeevents.NewBus()
|
||||
al.ownsRuntimeEvents = true
|
||||
}
|
||||
al.runtimeEventLogSub = subscribeRuntimeEventLogger(cfg, al.runtimeEvents)
|
||||
al.providerFactory = providers.CreateProviderFromConfig
|
||||
al.hooks = NewHookManager(al.runtimeEvents.Channel())
|
||||
configureHookManagerFromConfig(al.hooks, cfg)
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
|
||||
"github.com/sipeed/picoclaw/pkg/logger"
|
||||
)
|
||||
|
||||
const runtimeEventLoggerBuffer = 256
|
||||
|
||||
type runtimeEventLogger struct {
|
||||
cfg config.EventLoggingConfig
|
||||
}
|
||||
|
||||
func subscribeRuntimeEventLogger(cfg *config.Config, eventBus runtimeevents.Bus) runtimeevents.Subscription {
|
||||
eventLogger := newRuntimeEventLogger(cfg)
|
||||
sub, err := eventLogger.subscribe(context.Background(), eventBus)
|
||||
if err != nil {
|
||||
logger.WarnCF("events", "Failed to subscribe runtime event logger", map[string]any{"error": err.Error()})
|
||||
return nil
|
||||
}
|
||||
return sub
|
||||
}
|
||||
|
||||
func newRuntimeEventLogger(cfg *config.Config) *runtimeEventLogger {
|
||||
logCfg := config.EffectiveEventLoggingConfig(cfg)
|
||||
if !logCfg.Enabled {
|
||||
return nil
|
||||
}
|
||||
return &runtimeEventLogger{cfg: logCfg}
|
||||
}
|
||||
|
||||
func (l *runtimeEventLogger) subscribe(
|
||||
ctx context.Context,
|
||||
eventBus runtimeevents.Bus,
|
||||
) (runtimeevents.Subscription, error) {
|
||||
if l == nil || eventBus == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return eventBus.Channel().Subscribe(ctx, runtimeevents.SubscribeOptions{
|
||||
Name: "runtime-event-logger",
|
||||
Buffer: runtimeEventLoggerBuffer,
|
||||
Concurrency: runtimeevents.Locked,
|
||||
Backpressure: runtimeevents.DropNewest,
|
||||
PanicPolicy: runtimeevents.RecoverAndLog,
|
||||
}, l.handle)
|
||||
}
|
||||
|
||||
func (l *runtimeEventLogger) handle(_ context.Context, evt runtimeevents.Event) error {
|
||||
if l == nil || !l.shouldLog(evt) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fields := runtimeEventLogFields(evt)
|
||||
if l.cfg.IncludePayload && evt.Payload != nil {
|
||||
fields["payload"] = evt.Payload
|
||||
}
|
||||
|
||||
logRuntimeEvent(evt, fields)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *runtimeEventLogger) shouldLog(evt runtimeevents.Event) bool {
|
||||
if l == nil || !l.cfg.Enabled {
|
||||
return false
|
||||
}
|
||||
if runtimeEventSeverityRank(evt.Severity) < runtimeEventSeverityRank(parseRuntimeEventSeverity(l.cfg.MinSeverity)) {
|
||||
return false
|
||||
}
|
||||
|
||||
kind := evt.Kind.String()
|
||||
if !matchAnyRuntimeEventPattern(l.cfg.Include, kind, true) {
|
||||
return false
|
||||
}
|
||||
return !matchAnyRuntimeEventPattern(l.cfg.Exclude, kind, false)
|
||||
}
|
||||
|
||||
func logRuntimeEvent(evt runtimeevents.Event, fields map[string]any) {
|
||||
message := fmt.Sprintf("Runtime event: %s", evt.Kind.String())
|
||||
switch normalizeRuntimeEventSeverity(evt.Severity) {
|
||||
case runtimeevents.SeverityDebug:
|
||||
logger.DebugCF("events", message, fields)
|
||||
case runtimeevents.SeverityWarn:
|
||||
logger.WarnCF("events", message, fields)
|
||||
case runtimeevents.SeverityError:
|
||||
logger.ErrorCF("events", message, fields)
|
||||
default:
|
||||
logger.InfoCF("events", message, fields)
|
||||
}
|
||||
}
|
||||
|
||||
func runtimeEventLogFields(evt runtimeevents.Event) map[string]any {
|
||||
fields := map[string]any{
|
||||
"event_id": evt.ID,
|
||||
"event_kind": evt.Kind.String(),
|
||||
"severity": string(normalizeRuntimeEventSeverity(evt.Severity)),
|
||||
}
|
||||
if !evt.Time.IsZero() {
|
||||
fields["event_time"] = evt.Time.Format(time.RFC3339Nano)
|
||||
}
|
||||
appendRuntimeEventSourceFields(fields, evt.Source)
|
||||
appendRuntimeEventScopeFields(fields, evt.Scope)
|
||||
appendRuntimeEventCorrelationFields(fields, evt.Correlation)
|
||||
appendRuntimeEventAttrs(fields, evt.Attrs)
|
||||
appendRuntimeEventPayloadSummary(fields, evt.Payload)
|
||||
return fields
|
||||
}
|
||||
|
||||
func appendRuntimeEventSourceFields(fields map[string]any, source runtimeevents.Source) {
|
||||
if source.Component != "" {
|
||||
fields["source_component"] = source.Component
|
||||
}
|
||||
if source.Name != "" {
|
||||
fields["source_name"] = source.Name
|
||||
}
|
||||
}
|
||||
|
||||
func appendRuntimeEventScopeFields(fields map[string]any, scope runtimeevents.Scope) {
|
||||
setStringField(fields, "runtime_id", scope.RuntimeID)
|
||||
setStringField(fields, "agent_id", scope.AgentID)
|
||||
setStringField(fields, "session_key", scope.SessionKey)
|
||||
setStringField(fields, "turn_id", scope.TurnID)
|
||||
setStringField(fields, "channel", scope.Channel)
|
||||
setStringField(fields, "account", scope.Account)
|
||||
setStringField(fields, "chat_id", scope.ChatID)
|
||||
setStringField(fields, "topic_id", scope.TopicID)
|
||||
setStringField(fields, "space_id", scope.SpaceID)
|
||||
setStringField(fields, "space_type", scope.SpaceType)
|
||||
setStringField(fields, "chat_type", scope.ChatType)
|
||||
setStringField(fields, "sender_id", scope.SenderID)
|
||||
setStringField(fields, "message_id", scope.MessageID)
|
||||
}
|
||||
|
||||
func appendRuntimeEventCorrelationFields(fields map[string]any, correlation runtimeevents.Correlation) {
|
||||
setStringField(fields, "trace_id", correlation.TraceID)
|
||||
setStringField(fields, "parent_turn_id", correlation.ParentTurnID)
|
||||
setStringField(fields, "request_id", correlation.RequestID)
|
||||
setStringField(fields, "reply_to_id", correlation.ReplyToID)
|
||||
}
|
||||
|
||||
func appendRuntimeEventAttrs(fields map[string]any, attrs map[string]any) {
|
||||
for key, value := range attrs {
|
||||
if key == "" || value == nil {
|
||||
continue
|
||||
}
|
||||
if _, exists := fields[key]; exists {
|
||||
fields["attr_"+key] = value
|
||||
continue
|
||||
}
|
||||
fields[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func appendRuntimeEventPayloadSummary(fields map[string]any, payload any) {
|
||||
switch payload := payload.(type) {
|
||||
case TurnStartPayload:
|
||||
fields["user_len"] = len(payload.UserMessage)
|
||||
fields["media_count"] = payload.MediaCount
|
||||
case TurnEndPayload:
|
||||
fields["status"] = payload.Status
|
||||
fields["iterations_total"] = payload.Iterations
|
||||
fields["duration_ms"] = payload.Duration.Milliseconds()
|
||||
fields["final_len"] = payload.FinalContentLen
|
||||
case LLMRequestPayload:
|
||||
fields["model"] = payload.Model
|
||||
fields["messages"] = payload.MessagesCount
|
||||
fields["tools"] = payload.ToolsCount
|
||||
fields["max_tokens"] = payload.MaxTokens
|
||||
case LLMDeltaPayload:
|
||||
fields["content_delta_len"] = payload.ContentDeltaLen
|
||||
fields["reasoning_delta_len"] = payload.ReasoningDeltaLen
|
||||
case LLMResponsePayload:
|
||||
fields["content_len"] = payload.ContentLen
|
||||
fields["tool_calls"] = payload.ToolCalls
|
||||
fields["has_reasoning"] = payload.HasReasoning
|
||||
case LLMRetryPayload:
|
||||
fields["attempt"] = payload.Attempt
|
||||
fields["max_retries"] = payload.MaxRetries
|
||||
fields["reason"] = payload.Reason
|
||||
fields["error"] = payload.Error
|
||||
fields["backoff_ms"] = payload.Backoff.Milliseconds()
|
||||
case ContextCompressPayload:
|
||||
fields["reason"] = payload.Reason
|
||||
fields["dropped_messages"] = payload.DroppedMessages
|
||||
fields["remaining_messages"] = payload.RemainingMessages
|
||||
case SessionSummarizePayload:
|
||||
fields["summarized_messages"] = payload.SummarizedMessages
|
||||
fields["kept_messages"] = payload.KeptMessages
|
||||
fields["summary_len"] = payload.SummaryLen
|
||||
fields["omitted_oversized"] = payload.OmittedOversized
|
||||
case ToolExecStartPayload:
|
||||
fields["tool"] = payload.Tool
|
||||
fields["args_count"] = len(payload.Arguments)
|
||||
case ToolExecEndPayload:
|
||||
fields["tool"] = payload.Tool
|
||||
fields["duration_ms"] = payload.Duration.Milliseconds()
|
||||
fields["for_llm_len"] = payload.ForLLMLen
|
||||
fields["for_user_len"] = payload.ForUserLen
|
||||
fields["is_error"] = payload.IsError
|
||||
fields["async"] = payload.Async
|
||||
case ToolExecSkippedPayload:
|
||||
fields["tool"] = payload.Tool
|
||||
fields["reason"] = payload.Reason
|
||||
case SteeringInjectedPayload:
|
||||
fields["count"] = payload.Count
|
||||
fields["total_content_len"] = payload.TotalContentLen
|
||||
case FollowUpQueuedPayload:
|
||||
fields["source_tool"] = payload.SourceTool
|
||||
fields["content_len"] = payload.ContentLen
|
||||
case InterruptReceivedPayload:
|
||||
fields["interrupt_kind"] = payload.Kind
|
||||
fields["role"] = payload.Role
|
||||
fields["content_len"] = payload.ContentLen
|
||||
fields["queue_depth"] = payload.QueueDepth
|
||||
fields["hint_len"] = payload.HintLen
|
||||
case SubTurnSpawnPayload:
|
||||
fields["child_agent_id"] = payload.AgentID
|
||||
fields["label"] = payload.Label
|
||||
case SubTurnEndPayload:
|
||||
fields["child_agent_id"] = payload.AgentID
|
||||
fields["status"] = payload.Status
|
||||
case SubTurnResultDeliveredPayload:
|
||||
fields["target_channel"] = payload.TargetChannel
|
||||
fields["target_chat_id"] = payload.TargetChatID
|
||||
fields["content_len"] = payload.ContentLen
|
||||
case SubTurnOrphanPayload:
|
||||
fields["parent_turn_id"] = payload.ParentTurnID
|
||||
fields["child_turn_id"] = payload.ChildTurnID
|
||||
fields["reason"] = payload.Reason
|
||||
case ErrorPayload:
|
||||
fields["stage"] = payload.Stage
|
||||
fields["error"] = payload.Message
|
||||
}
|
||||
}
|
||||
|
||||
func setStringField(fields map[string]any, key, value string) {
|
||||
if value != "" {
|
||||
fields[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func matchAnyRuntimeEventPattern(patterns []string, kind string, emptyMatches bool) bool {
|
||||
if len(patterns) == 0 {
|
||||
return emptyMatches
|
||||
}
|
||||
for _, pattern := range patterns {
|
||||
if matchRuntimeEventPattern(pattern, kind) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func matchRuntimeEventPattern(pattern, kind string) bool {
|
||||
pattern = strings.TrimSpace(pattern)
|
||||
if pattern == "" {
|
||||
return false
|
||||
}
|
||||
if pattern == "*" {
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(pattern, ".*") {
|
||||
return strings.HasPrefix(kind, strings.TrimSuffix(pattern, "*"))
|
||||
}
|
||||
matched, err := path.Match(pattern, kind)
|
||||
if err == nil {
|
||||
return matched
|
||||
}
|
||||
return pattern == kind
|
||||
}
|
||||
|
||||
func parseRuntimeEventSeverity(severity string) runtimeevents.Severity {
|
||||
switch strings.ToLower(strings.TrimSpace(severity)) {
|
||||
case "debug":
|
||||
return runtimeevents.SeverityDebug
|
||||
case "warn", "warning":
|
||||
return runtimeevents.SeverityWarn
|
||||
case "error":
|
||||
return runtimeevents.SeverityError
|
||||
default:
|
||||
return runtimeevents.SeverityInfo
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeRuntimeEventSeverity(severity runtimeevents.Severity) runtimeevents.Severity {
|
||||
switch severity {
|
||||
case runtimeevents.SeverityDebug,
|
||||
runtimeevents.SeverityInfo,
|
||||
runtimeevents.SeverityWarn,
|
||||
runtimeevents.SeverityError:
|
||||
return severity
|
||||
default:
|
||||
return runtimeevents.SeverityInfo
|
||||
}
|
||||
}
|
||||
|
||||
func runtimeEventSeverityRank(severity runtimeevents.Severity) int {
|
||||
switch normalizeRuntimeEventSeverity(severity) {
|
||||
case runtimeevents.SeverityDebug:
|
||||
return 0
|
||||
case runtimeevents.SeverityInfo:
|
||||
return 1
|
||||
case runtimeevents.SeverityWarn:
|
||||
return 2
|
||||
case runtimeevents.SeverityError:
|
||||
return 3
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
runtimeevents "github.com/sipeed/picoclaw/pkg/events"
|
||||
)
|
||||
|
||||
func TestRuntimeEventLoggerFiltering(t *testing.T) {
|
||||
cfg := config.DefaultConfig()
|
||||
eventLogger := newRuntimeEventLogger(cfg)
|
||||
if eventLogger == nil {
|
||||
t.Fatal("default runtime event logger is nil")
|
||||
}
|
||||
|
||||
if !eventLogger.shouldLog(runtimeevents.Event{
|
||||
Kind: runtimeevents.KindAgentTurnStart,
|
||||
Severity: runtimeevents.SeverityInfo,
|
||||
}) {
|
||||
t.Fatal("default config should log agent events")
|
||||
}
|
||||
if eventLogger.shouldLog(runtimeevents.Event{
|
||||
Kind: runtimeevents.KindChannelLifecycleStarted,
|
||||
Severity: runtimeevents.SeverityInfo,
|
||||
}) {
|
||||
t.Fatal("default config should not log non-agent events")
|
||||
}
|
||||
|
||||
cfg.Events.Logging.Include = []string{"*"}
|
||||
cfg.Events.Logging.Exclude = []string{"mcp.*"}
|
||||
eventLogger = newRuntimeEventLogger(cfg)
|
||||
if !eventLogger.shouldLog(runtimeevents.Event{
|
||||
Kind: runtimeevents.KindGatewayReady,
|
||||
Severity: runtimeevents.SeverityInfo,
|
||||
}) {
|
||||
t.Fatal("include * should log gateway events")
|
||||
}
|
||||
if eventLogger.shouldLog(runtimeevents.Event{
|
||||
Kind: runtimeevents.KindMCPServerConnected,
|
||||
Severity: runtimeevents.SeverityInfo,
|
||||
}) {
|
||||
t.Fatal("exclude mcp.* should suppress MCP events")
|
||||
}
|
||||
|
||||
cfg.Events.Logging.Exclude = nil
|
||||
cfg.Events.Logging.MinSeverity = "warn"
|
||||
eventLogger = newRuntimeEventLogger(cfg)
|
||||
if eventLogger.shouldLog(runtimeevents.Event{
|
||||
Kind: runtimeevents.KindGatewayReady,
|
||||
Severity: runtimeevents.SeverityInfo,
|
||||
}) {
|
||||
t.Fatal("min severity warn should suppress info events")
|
||||
}
|
||||
if !eventLogger.shouldLog(runtimeevents.Event{
|
||||
Kind: runtimeevents.KindGatewayReloadFailed,
|
||||
Severity: runtimeevents.SeverityError,
|
||||
}) {
|
||||
t.Fatal("min severity warn should allow error events")
|
||||
}
|
||||
|
||||
cfg.Events.Logging.Enabled = false
|
||||
if newRuntimeEventLogger(cfg) != nil {
|
||||
t.Fatal("disabled config should not create runtime event logger")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeEventLogFieldsSummarizeAgentPayload(t *testing.T) {
|
||||
fields := runtimeEventLogFields(runtimeevents.Event{
|
||||
ID: "evt-test",
|
||||
Kind: runtimeevents.KindAgentToolExecStart,
|
||||
Severity: runtimeevents.SeverityInfo,
|
||||
Source: runtimeevents.Source{
|
||||
Component: "agent",
|
||||
Name: "main",
|
||||
},
|
||||
Scope: runtimeevents.Scope{
|
||||
AgentID: "main",
|
||||
SessionKey: "session-1",
|
||||
TurnID: "turn-1",
|
||||
},
|
||||
Payload: ToolExecStartPayload{
|
||||
Tool: "exec",
|
||||
Arguments: map[string]any{
|
||||
"secret": "should-not-be-logged-by-default",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
if fields["event_id"] != "evt-test" || fields["source_component"] != "agent" {
|
||||
t.Fatalf("missing common event fields: %#v", fields)
|
||||
}
|
||||
if fields["tool"] != "exec" || fields["args_count"] != 1 {
|
||||
t.Fatalf("missing safe agent payload summary fields: %#v", fields)
|
||||
}
|
||||
if _, ok := fields["payload"]; ok {
|
||||
t.Fatalf("raw payload should not be included by runtimeEventLogFields: %#v", fields)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user