package log import ( "context" "io" "os" "sync" "sync/atomic" "time" "github.com/sagernet/sing/common" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/observable" "github.com/sagernet/sing/service/filemanager" ) var _ Factory = (*defaultFactory)(nil) type defaultFactory struct { ctx context.Context formatter Formatter platformFormatter Formatter writer io.Writer file *os.File filePath string platformWriter PlatformWriter needObservable bool level Level subscriber *observable.Subscriber[Entry] observer *observable.Observer[Entry] startAccess sync.Mutex started atomic.Bool pendingEntries []pendingEntry } type pendingEntry struct { ctx context.Context level Level tag string message string timestamp time.Time } func NewDefaultFactory( ctx context.Context, formatter Formatter, writer io.Writer, filePath string, platformWriter PlatformWriter, needObservable bool, ) ObservableFactory { factory := &defaultFactory{ ctx: ctx, formatter: formatter, platformFormatter: Formatter{ BaseTime: formatter.BaseTime, DisableLineBreak: true, }, writer: writer, filePath: filePath, platformWriter: platformWriter, needObservable: needObservable, level: LevelTrace, subscriber: observable.NewSubscriber[Entry](128), } /*if platformWriter != nil { factory.platformFormatter.DisableColors = platformWriter.DisableColors() }*/ if needObservable { factory.observer = observable.NewObserver[Entry](factory.subscriber, 64) } return factory } func (f *defaultFactory) Start() error { var err error if f.filePath != "" { logFile, openErr := filemanager.OpenFile(f.ctx, f.filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) if openErr != nil { err = openErr } else { f.writer = logFile f.file = logFile } } f.startAccess.Lock() pendingEntries := f.pendingEntries f.pendingEntries = nil f.started.Store(true) f.startAccess.Unlock() for _, entry := range pendingEntries { f.output(entry.ctx, entry.level, entry.tag, entry.message, entry.timestamp) } return err } func (f *defaultFactory) Close() error { f.startAccess.Lock() f.pendingEntries = nil f.startAccess.Unlock() return common.Close( common.PtrOrNil(f.file), f.subscriber, ) } func (f *defaultFactory) Level() Level { return f.level } func (f *defaultFactory) SetLevel(level Level) { f.level = level } func (f *defaultFactory) Logger() ContextLogger { return f.NewLogger("") } func (f *defaultFactory) NewLogger(tag string) ContextLogger { return &observableLogger{f, tag} } func (f *defaultFactory) Subscribe() (subscription observable.Subscription[Entry], done <-chan struct{}, err error) { return f.observer.Subscribe() } func (f *defaultFactory) UnSubscribe(sub observable.Subscription[Entry]) { f.observer.UnSubscribe(sub) } func (f *defaultFactory) output(ctx context.Context, level Level, tag string, message string, timestamp time.Time) { if f.needObservable { formatted, formattedSimple := f.formatter.FormatWithSimple(ctx, level, tag, message, timestamp) if level <= f.level { if level == LevelPanic { panic(formatted) } f.writer.Write([]byte(formatted)) if level == LevelFatal { os.Exit(1) } } f.subscriber.Emit(Entry{level, formattedSimple}) } else if level <= f.level { formatted := f.formatter.Format(ctx, level, tag, message, timestamp) if level == LevelPanic { panic(formatted) } f.writer.Write([]byte(formatted)) if level == LevelFatal { os.Exit(1) } } if f.platformWriter != nil { f.platformWriter.WriteMessage(level, f.platformFormatter.Format(ctx, level, tag, message, timestamp)) } } var _ ContextLogger = (*observableLogger)(nil) type observableLogger struct { *defaultFactory tag string } func (l *observableLogger) Log(ctx context.Context, level Level, args []any) { level = OverrideLevelFromContext(level, ctx) if level > l.level && l.platformWriter == nil && !l.needObservable { return } nowTime := time.Now() message := F.ToString(args...) if !l.started.Load() && level != LevelFatal && level != LevelPanic { l.startAccess.Lock() if !l.started.Load() { l.pendingEntries = append(l.pendingEntries, pendingEntry{ctx, level, l.tag, message, nowTime}) l.startAccess.Unlock() return } l.startAccess.Unlock() } l.output(ctx, level, l.tag, message, nowTime) } func (l *observableLogger) Trace(args ...any) { l.TraceContext(context.Background(), args...) } func (l *observableLogger) Debug(args ...any) { l.DebugContext(context.Background(), args...) } func (l *observableLogger) Info(args ...any) { l.InfoContext(context.Background(), args...) } func (l *observableLogger) Warn(args ...any) { l.WarnContext(context.Background(), args...) } func (l *observableLogger) Error(args ...any) { l.ErrorContext(context.Background(), args...) } func (l *observableLogger) Fatal(args ...any) { l.FatalContext(context.Background(), args...) } func (l *observableLogger) Panic(args ...any) { l.PanicContext(context.Background(), args...) } func (l *observableLogger) TraceContext(ctx context.Context, args ...any) { l.Log(ctx, LevelTrace, args) } func (l *observableLogger) DebugContext(ctx context.Context, args ...any) { l.Log(ctx, LevelDebug, args) } func (l *observableLogger) InfoContext(ctx context.Context, args ...any) { l.Log(ctx, LevelInfo, args) } func (l *observableLogger) WarnContext(ctx context.Context, args ...any) { l.Log(ctx, LevelWarn, args) } func (l *observableLogger) ErrorContext(ctx context.Context, args ...any) { l.Log(ctx, LevelError, args) } func (l *observableLogger) FatalContext(ctx context.Context, args ...any) { l.Log(ctx, LevelFatal, args) } func (l *observableLogger) PanicContext(ctx context.Context, args ...any) { l.Log(ctx, LevelPanic, args) }