Files
jules/engine/trace/trace.go
T
2026-06-06 18:52:20 +03:00

122 lines
3.1 KiB
Go

package trace
import (
"context"
"log/slog"
"slices"
"time"
"m8sh.su/d/jules/chat"
"m8sh.su/d/jules/database"
"m8sh.su/d/jules/engine/actions"
"m8sh.su/d/jules/engine/user"
)
type Span struct {
ctx context.Context
attrs []slog.Attr
start time.Time
}
func FromMessage(ctx context.Context, msg chat.Message) *Span {
e := &Span{start: time.Now(), ctx: ctx}
e.attrs = append(e.attrs,
slog.String("type", "message_processing"),
slog.String("message_platform", msg.Chat),
slog.String("message_platform_user_id", msg.ID),
slog.String("message_content", msg.Text),
)
return e
}
func FromNotification(ctx context.Context, notif database.Notification) *Span {
e := &Span{start: time.Now(), ctx: ctx}
e.attrs = append(e.attrs,
slog.String("type", "notification_processing"),
slog.String("notification_id", notif.ID.String()),
slog.String("notification_user_id", notif.UserID.String()),
slog.String("notification_initiator_id", notif.InitiatorID.String()),
slog.String("notification_content", notif.Content),
)
return e
}
func (s *Span) User(u *user.User) *Span {
if u == nil || u.User == nil {
return s
}
s.attrs = append(s.attrs,
slog.String("user_id", u.ID.String()),
slog.String("user_lang", u.Language),
slog.String("user_tz", u.Timezone),
slog.String("user_chat", u.PreferredChat),
slog.String("user_role", u.Role),
)
for _, chat := range u.Chats {
s.attrs = append(s.attrs, slog.String("chat", chat.Platform))
}
for _, fact := range u.Facts {
s.attrs = append(s.attrs, slog.String("fact", fact.Value))
}
for _, contact := range u.Contacts {
s.attrs = append(s.attrs, slog.String("contact", contact.Name))
}
for _, n := range u.IncomingNotifications {
s.attrs = append(s.attrs, slog.String("incoming_notif", n.Content))
}
for _, n := range u.OutgoingNotifications {
s.attrs = append(s.attrs, slog.String("outgoing_notif", n.Content))
}
for _, a := range u.RecentActions {
s.attrs = append(s.attrs, slog.String("action", a.Type+" "+a.Content))
}
return s
}
func (s *Span) LLMResponse(content string) *Span {
for i, attr := range s.attrs {
if attr.Key == "llm_response" {
s.attrs[i] = slog.String("llm_response", content)
return s
}
}
s.attrs = append(s.attrs, slog.String("llm_response", content))
return s
}
func (s *Span) Actions(as []actions.Action) *Span {
s.attrs = slices.DeleteFunc(s.attrs, func(a slog.Attr) bool {
return a.Key == "action"
})
for _, action := range as {
s.attrs = append(s.attrs, slog.Any("action", string(actions.Raw(action))))
}
return s
}
func (s *Span) Info(msg string) {
s.log(slog.LevelInfo, msg)
}
func (s *Span) Warn(msg string, errs ...error) {
if len(errs) > 0 {
for _, err := range errs {
s.attrs = append(s.attrs, slog.Any("error", err))
}
}
s.log(slog.LevelWarn, msg)
}
func (s *Span) Error(msg string, err error) {
s.attrs = append(s.attrs, slog.String("error", err.Error()))
s.log(slog.LevelError, msg)
}
func (s *Span) log(level slog.Level, msg string) {
if s.ctx == nil {
s.ctx = context.Background()
}
s.attrs = append(s.attrs, slog.String("duration", time.Since(s.start).String()))
slog.LogAttrs(s.ctx, level, msg, s.attrs...)
}