starlog/route_handler.go

246 lines
5.0 KiB
Go
Raw Normal View History

2026-03-19 16:37:57 +08:00
package starlog
import (
"context"
"fmt"
"reflect"
"sync/atomic"
"b612.me/starlog/internal/routerx"
)
type LevelMatcher = routerx.Matcher
type Route struct {
Name string
Match LevelMatcher
Formatter Formatter
Sink Sink
}
type routeSnapshot struct {
name string
match LevelMatcher
formatter Formatter
sink Sink
}
type RouteHandler struct {
routes atomic.Value
}
func NewRouteHandler(routes ...Route) *RouteHandler {
handler := &RouteHandler{}
handler.routes.Store([]routeSnapshot{})
handler.SetRoutes(routes)
return handler
}
func sinkIdentity(sink Sink) (string, bool) {
if sink == nil {
return "", false
}
val := reflect.ValueOf(sink)
if !val.IsValid() {
return "", false
}
switch val.Kind() {
case reflect.Ptr, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.UnsafePointer:
if val.IsNil() {
return "", false
}
return fmt.Sprintf("%T:%x", sink, val.Pointer()), true
default:
return "", false
}
}
func (handler *RouteHandler) SetRoutes(routes []Route) {
if handler == nil {
return
}
handler.routes.Store(normalizeRoutes(routes))
}
func (handler *RouteHandler) ReplaceRoutes(routes ...Route) {
handler.SetRoutes(routes)
}
func (handler *RouteHandler) Handle(ctx context.Context, entry *Entry) error {
if handler == nil || entry == nil {
return nil
}
rawRoutes := handler.routes.Load()
snapshots, ok := rawRoutes.([]routeSnapshot)
if !ok || len(snapshots) == 0 {
return nil
}
var firstErr error
for _, route := range snapshots {
if route.match != nil && !route.match(entry.Level) {
continue
}
formatter := route.formatter
if formatter == nil {
formatter = NewTextFormatter()
}
formatted, err := formatter.Format(entry)
if err != nil {
wrapErr := fmt.Errorf("route %s format failed: %w", route.name, err)
reportWriteError(wrapErr, LogData{
Name: route.name,
Log: entry.Message,
})
if firstErr == nil {
firstErr = wrapErr
}
continue
}
if route.sink == nil {
continue
}
if err = route.sink.Write(formatted); err != nil {
wrapErr := fmt.Errorf("route %s write failed: %w", route.name, err)
reportWriteError(wrapErr, LogData{
Name: route.name,
Log: string(formatted),
})
if firstErr == nil {
firstErr = wrapErr
}
continue
}
}
return firstErr
}
func (handler *RouteHandler) Close() error {
if handler == nil {
return nil
}
rawRoutes := handler.routes.Load()
snapshots, ok := rawRoutes.([]routeSnapshot)
if !ok || len(snapshots) == 0 {
return nil
}
closed := make(map[string]struct{}, len(snapshots))
var firstErr error
for _, route := range snapshots {
if route.sink == nil {
continue
}
if id, ok := sinkIdentity(route.sink); ok {
if _, exists := closed[id]; exists {
continue
}
closed[id] = struct{}{}
}
if err := route.sink.Close(); err != nil {
wrapErr := fmt.Errorf("route %s close failed: %w", route.name, err)
reportWriteError(wrapErr, LogData{
Name: route.name,
})
if firstErr == nil {
firstErr = wrapErr
}
}
}
return firstErr
}
func MatchAllLevels() LevelMatcher {
return routerx.MatchAllLevels()
}
func MatchLevels(levels ...int) LevelMatcher {
return routerx.MatchLevels(levels...)
}
func MatchAtLeast(minLevel int) LevelMatcher {
return routerx.MatchAtLeast(minLevel)
}
type chainedHandler struct {
handlers []Handler
}
func NewChainHandler(handlers ...Handler) Handler {
filtered := make([]Handler, 0, len(handlers))
for _, handler := range handlers {
if handler == nil {
continue
}
filtered = append(filtered, handler)
}
return &chainedHandler{
handlers: filtered,
}
}
func ChainHandler(handlers ...Handler) Handler {
return NewChainHandler(handlers...)
}
func (handler *chainedHandler) Handle(ctx context.Context, entry *Entry) error {
if handler == nil {
return nil
}
var firstErr error
for _, item := range handler.handlers {
if item == nil {
continue
}
if err := item.Handle(ctx, entry); err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
}
func (handler *chainedHandler) Close() error {
if handler == nil {
return nil
}
var firstErr error
for _, item := range handler.handlers {
if item == nil {
continue
}
closer, ok := item.(interface{ Close() error })
if !ok {
continue
}
if err := closer.Close(); err != nil && firstErr == nil {
firstErr = err
}
}
return firstErr
}
func normalizeRoutes(routes []Route) []routeSnapshot {
if len(routes) == 0 {
return nil
}
baseRoutes := make([]routerx.Route, 0, len(routes))
for index, route := range routes {
baseRoutes = append(baseRoutes, routerx.Route{
Index: index,
Name: route.Name,
Match: route.Match,
Enabled: route.Sink != nil,
})
}
normalized := routerx.Normalize(baseRoutes)
result := make([]routeSnapshot, 0, len(normalized))
for _, item := range normalized {
route := routes[item.Index]
result = append(result, routeSnapshot{
name: item.Name,
match: item.Match,
formatter: route.Formatter,
sink: route.Sink,
})
}
return result
}