Files
goclaw/cmd/traces_output.go

178 lines
3.9 KiB
Go

package cmd
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"os"
"strings"
"text/tabwriter"
"time"
"github.com/google/uuid"
)
func outputFormatIsJSON() bool {
return strings.EqualFold(strings.TrimSpace(gatewayOutputFormat), "json")
}
func validateTraceOutputFormat() error {
switch strings.ToLower(strings.TrimSpace(gatewayOutputFormat)) {
case "", "table", "json":
return nil
default:
return fmt.Errorf("unsupported output format %q; use table or json", gatewayOutputFormat)
}
}
func printJSON(value any) error {
data, err := json.MarshalIndent(value, "", " ")
if err != nil {
return err
}
fmt.Println(string(data))
return nil
}
func printTraceList(resp traceListResponse) error {
if outputFormatIsJSON() {
return printJSON(resp)
}
if len(resp.Traces) == 0 {
fmt.Println("No traces found.")
return nil
}
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "TRACE\tSTATUS\tAGENT\tSESSION\tRUN\tTOKENS\tSTARTED")
for _, tr := range resp.Traces {
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%d/%d\t%s\n",
shortTraceID(tr.ID.String()),
tr.Status,
shortOptionalUUID(tr.AgentID),
truncateStr(tr.SessionKey, 36),
truncateStr(tr.RunID, 24),
tr.TotalInputTokens,
tr.TotalOutputTokens,
formatTraceTime(tr.StartTime),
)
}
return tw.Flush()
}
func printTraceDetail(resp traceDetailResponse) error {
if outputFormatIsJSON() {
return printJSON(resp)
}
tr := resp.Trace
fmt.Printf("Trace: %s\n", tr.ID)
fmt.Printf("Status: %s\n", tr.Status)
fmt.Printf("Agent: %s\n", shortOptionalUUID(tr.AgentID))
fmt.Printf("Session: %s\n", tr.SessionKey)
fmt.Printf("Run: %s\n", tr.RunID)
fmt.Printf("Tokens: %d input / %d output\n", tr.TotalInputTokens, tr.TotalOutputTokens)
if tr.Error != "" {
fmt.Printf("Error: %s\n", tr.Error)
}
if len(resp.Spans) == 0 {
fmt.Println("\nNo spans found.")
return nil
}
fmt.Println()
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "SPAN\tTYPE\tSTATUS\tPROVIDER\tMODEL\tDURATION\tNAME")
for _, sp := range resp.Spans {
fmt.Fprintf(tw, "%s\t%s\t%s\t%s\t%s\t%dms\t%s\n",
shortTraceID(sp.ID.String()),
sp.SpanType,
sp.Status,
sp.Provider,
sp.Model,
sp.DurationMS,
truncateStr(sp.Name, 48),
)
}
return tw.Flush()
}
func printTraceFollow(resp traceFollowResponse) error {
if outputFormatIsJSON() {
return printJSON(resp)
}
if resp.NextSince != "" {
fmt.Printf("Next since: %s\n\n", resp.NextSince)
}
return printTraceList(traceListResponse{
Traces: resp.Traces,
Total: len(resp.Traces),
Limit: resp.Limit,
})
}
func printTraceTimeline(resp traceTimelineResponse) error {
if outputFormatIsJSON() {
return printJSON(resp)
}
if len(resp.Items) == 0 {
fmt.Println("No timeline items found.")
return nil
}
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(tw, "SEQ\tTYPE\tSTATUS\tTITLE\tPREVIEW\tCREATED")
for _, item := range resp.Items {
fmt.Fprintf(tw, "%d\t%s\t%s\t%s\t%s\t%s\n",
item.Seq,
item.ItemType,
item.Status,
truncateStr(item.Title, 32),
truncateStr(firstNonEmpty(item.Preview, item.Content), 60),
formatTraceTime(item.CreatedAt),
)
}
return tw.Flush()
}
func printGzipJSON(raw []byte) error {
gr, err := gzip.NewReader(bytes.NewReader(raw))
if err != nil {
return err
}
defer gr.Close()
data, err := io.ReadAll(gr)
if err != nil {
return err
}
var value any
if err := json.Unmarshal(data, &value); err != nil {
return err
}
return printJSON(value)
}
func defaultTraceExportPath(traceID string) string {
short := shortTraceID(traceID)
return fmt.Sprintf("trace-%s-%s.json.gz", short, time.Now().UTC().Format("20060102"))
}
func shortTraceID(id string) string {
if len(id) <= 8 {
return id
}
return id[:8]
}
func shortOptionalUUID(id *uuid.UUID) string {
if id == nil {
return "-"
}
return shortTraceID(id.String())
}
func formatTraceTime(t time.Time) string {
if t.IsZero() {
return "-"
}
return t.Local().Format(time.DateTime)
}