starlog/internal/pipelinex/formatter.go
2026-03-19 16:37:57 +08:00

138 lines
3.0 KiB
Go

package pipelinex
import (
"encoding/json"
"fmt"
"sort"
"strings"
"time"
)
type Entry struct {
Time time.Time
LevelName string
LoggerName string
Thread string
File string
Line int
Func string
Message string
Error string
Fields map[string]interface{}
}
type TextOptions struct {
IncludeTimestamp bool
IncludeLevel bool
IncludeSource bool
IncludeThread bool
IncludeLogger bool
}
func cloneFields(fields map[string]interface{}) map[string]interface{} {
if len(fields) == 0 {
return nil
}
cloned := make(map[string]interface{}, len(fields))
for key, value := range fields {
cloned[key] = value
}
return cloned
}
func renderFields(fields map[string]interface{}) string {
if len(fields) == 0 {
return ""
}
keys := make([]string, 0, len(fields))
for key := range fields {
keys = append(keys, key)
}
sort.Strings(keys)
pairs := make([]string, 0, len(keys))
for _, key := range keys {
pairs = append(pairs, fmt.Sprintf("%s=%v", key, fields[key]))
}
return strings.Join(pairs, " ")
}
func FormatText(entry Entry, options TextOptions) ([]byte, error) {
parts := make([]string, 0, 6)
if options.IncludeTimestamp {
if !entry.Time.IsZero() {
parts = append(parts, entry.Time.Format("2006-01-02 15:04:05.000000"))
}
}
if options.IncludeSource {
source := ""
if entry.File != "" {
source = fmt.Sprintf("%s:%d", entry.File, entry.Line)
}
if entry.Func != "" {
if source != "" {
source += " "
}
source += "<" + entry.Func + ">"
}
if source != "" {
parts = append(parts, source)
}
}
if options.IncludeThread && entry.Thread != "" {
parts = append(parts, "|"+entry.Thread+"|")
}
if options.IncludeLevel {
if entry.LevelName != "" {
parts = append(parts, "["+entry.LevelName+"]")
}
}
if options.IncludeLogger && entry.LoggerName != "" {
parts = append(parts, "logger="+entry.LoggerName)
}
messageParts := make([]string, 0, 3)
if entry.Message != "" {
messageParts = append(messageParts, entry.Message)
}
if entry.Error != "" {
messageParts = append(messageParts, "error="+entry.Error)
}
fieldText := renderFields(entry.Fields)
if fieldText != "" {
messageParts = append(messageParts, fieldText)
}
if len(messageParts) > 0 {
parts = append(parts, strings.Join(messageParts, " "))
}
return []byte(strings.Join(parts, " ")), nil
}
func FormatJSON(entry Entry, pretty bool) ([]byte, error) {
payload := map[string]interface{}{
"time": entry.Time.Format(time.RFC3339Nano),
"level": entry.LevelName,
"msg": entry.Message,
"logger": entry.LoggerName,
"thread": entry.Thread,
}
if entry.File != "" {
payload["file"] = entry.File
}
if entry.Line > 0 {
payload["line"] = entry.Line
}
if entry.Func != "" {
payload["func"] = entry.Func
}
if entry.Error != "" {
payload["error"] = entry.Error
}
if len(entry.Fields) > 0 {
payload["fields"] = cloneFields(entry.Fields)
}
if pretty {
return json.MarshalIndent(payload, "", " ")
}
return json.Marshal(payload)
}