246 lines
5.0 KiB
Go
246 lines
5.0 KiB
Go
|
|
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
|
||
|
|
}
|