Add a structured logger and collector so runs can be traced across processes.
Sync and async sinks, redaction, and a queryable HTTP service keep concurrent logs tied to a trace id. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
package logsys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// FormatJSON 把条目编码为一行 JSON(含换行)。
|
||||
func FormatJSON(e Entry) ([]byte, error) {
|
||||
buf := &bytes.Buffer{}
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(e); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// FormatHuman 把条目格式化成单行文本(含换行)。
|
||||
func FormatHuman(e Entry) string {
|
||||
ts := e.Time.UTC().Format(time.RFC3339Nano)
|
||||
var b strings.Builder
|
||||
fmt.Fprintf(&b, "%s %s", ts, strings.ToUpper(e.Level))
|
||||
if e.Service != "" || e.Module != "" {
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(e.Service)
|
||||
if e.Module != "" {
|
||||
if e.Service != "" {
|
||||
b.WriteByte('/')
|
||||
}
|
||||
b.WriteString(e.Module)
|
||||
}
|
||||
}
|
||||
b.WriteByte(' ')
|
||||
b.WriteString(e.Message)
|
||||
if e.Code != "" {
|
||||
fmt.Fprintf(&b, " code=%s", e.Code)
|
||||
}
|
||||
if e.TraceID != "" {
|
||||
fmt.Fprintf(&b, " trace=%s", e.TraceID)
|
||||
}
|
||||
if e.Error != "" {
|
||||
fmt.Fprintf(&b, " error=%q", e.Error)
|
||||
}
|
||||
if len(e.Fields) > 0 {
|
||||
keys := make([]string, 0, len(e.Fields))
|
||||
for k := range e.Fields {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
fmt.Fprintf(&b, " %s=%v", k, e.Fields[k])
|
||||
}
|
||||
}
|
||||
b.WriteByte('\n')
|
||||
return b.String()
|
||||
}
|
||||
Reference in New Issue
Block a user