diff --git a/box.go b/box.go index fa4393ca..dbba6389 100644 --- a/box.go +++ b/box.go @@ -9,7 +9,6 @@ import ( "time" "github.com/sagernet/sing-box/adapter" - C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/libbox/platform" "github.com/sagernet/sing-box/inbound" @@ -31,18 +30,25 @@ type Box struct { outbounds []adapter.Outbound logFactory log.Factory logger log.ContextLogger - logFile *os.File preServices map[string]adapter.Service postServices map[string]adapter.Service done chan struct{} } -func New(ctx context.Context, options option.Options, platformInterface platform.Interface) (*Box, error) { - createdAt := time.Now() +type Options struct { + option.Options + Context context.Context + PlatformInterface platform.Interface +} +func New(options Options) (*Box, error) { + ctx := options.Context + if ctx == nil { + ctx = context.Background() + } + createdAt := time.Now() experimentalOptions := common.PtrValueOrDefault(options.Experimental) applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) - var needClashAPI bool var needV2RayAPI bool if experimentalOptions.ClashAPI != nil && experimentalOptions.ClashAPI.ExternalController != "" { @@ -51,60 +57,20 @@ func New(ctx context.Context, options option.Options, platformInterface platform if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } - - logOptions := common.PtrValueOrDefault(options.Log) - - var logFactory log.Factory - var observableLogFactory log.ObservableFactory - var logFile *os.File - var logWriter io.Writer - if logOptions.Disabled { - observableLogFactory = log.NewNOPFactory() - logFactory = observableLogFactory - } else { - switch logOptions.Output { - case "": - if platformInterface != nil { - logWriter = io.Discard - } else { - logWriter = os.Stdout - } - case "stderr": - logWriter = os.Stderr - case "stdout": - logWriter = os.Stdout - default: - var err error - logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) - if err != nil { - return nil, err - } - logWriter = logFile - } - logFormatter := log.Formatter{ - BaseTime: createdAt, - DisableColors: logOptions.DisableColor || logFile != nil, - DisableTimestamp: !logOptions.Timestamp && logFile != nil, - FullTimestamp: logOptions.Timestamp, - TimestampFormat: "-0700 2006-01-02 15:04:05", - } - if needClashAPI { - observableLogFactory = log.NewObservableFactory(logFormatter, logWriter, platformInterface) - logFactory = observableLogFactory - } else { - logFactory = log.NewFactory(logFormatter, logWriter, platformInterface) - } - if logOptions.Level != "" { - logLevel, err := log.ParseLevel(logOptions.Level) - if err != nil { - return nil, E.Cause(err, "parse log level") - } - logFactory.SetLevel(logLevel) - } else { - logFactory.SetLevel(log.LevelTrace) - } + var defaultLogWriter io.Writer + if options.PlatformInterface != nil { + defaultLogWriter = io.Discard + } + logFactory, err := log.New(log.Options{ + Options: common.PtrValueOrDefault(options.Log), + Observable: needClashAPI, + DefaultWriter: defaultLogWriter, + BaseTime: createdAt, + PlatformWriter: options.PlatformInterface, + }) + if err != nil { + return nil, E.Cause(err, "create log factory") } - router, err := route.NewRouter( ctx, logFactory, @@ -112,7 +78,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform common.PtrValueOrDefault(options.DNS), common.PtrValueOrDefault(options.NTP), options.Inbounds, - platformInterface, + options.PlatformInterface, ) if err != nil { return nil, E.Cause(err, "parse route options") @@ -132,7 +98,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), inboundOptions, - platformInterface, + options.PlatformInterface, ) if err != nil { return nil, E.Cause(err, "parse inbound[", i, "]") @@ -169,7 +135,7 @@ func New(ctx context.Context, options option.Options, platformInterface platform preServices := make(map[string]adapter.Service) postServices := make(map[string]adapter.Service) if needClashAPI { - clashServer, err := experimental.NewClashServer(router, observableLogFactory, common.PtrValueOrDefault(options.Experimental.ClashAPI)) + clashServer, err := experimental.NewClashServer(router, logFactory.(log.ObservableFactory), common.PtrValueOrDefault(options.Experimental.ClashAPI)) if err != nil { return nil, E.Cause(err, "create clash api server") } @@ -191,7 +157,6 @@ func New(ctx context.Context, options option.Options, platformInterface platform createdAt: createdAt, logFactory: logFactory, logger: logFactory.Logger(), - logFile: logFile, preServices: preServices, postServices: postServices, done: make(chan struct{}), @@ -330,11 +295,6 @@ func (s *Box) Close() error { return E.Cause(err, "close log factory") }) } - if s.logFile != nil { - errors = E.Append(errors, s.logFile.Close(), func(err error) error { - return E.Cause(err, "close log file") - }) - } return errors } diff --git a/cmd/sing-box/cmd_check.go b/cmd/sing-box/cmd_check.go index c147c009..1beab954 100644 --- a/cmd/sing-box/cmd_check.go +++ b/cmd/sing-box/cmd_check.go @@ -31,7 +31,10 @@ func check() error { return err } ctx, cancel := context.WithCancel(context.Background()) - instance, err := box.New(ctx, options, nil) + instance, err := box.New(box.Options{ + Context: ctx, + Options: options, + }) if err == nil { instance.Close() } diff --git a/cmd/sing-box/cmd_run.go b/cmd/sing-box/cmd_run.go index 0ee8b892..9e296aa6 100644 --- a/cmd/sing-box/cmd_run.go +++ b/cmd/sing-box/cmd_run.go @@ -127,7 +127,10 @@ func create() (*box.Box, context.CancelFunc, error) { options.Log.DisableColor = true } ctx, cancel := context.WithCancel(context.Background()) - instance, err := box.New(ctx, options, nil) + instance, err := box.New(box.Options{ + Context: ctx, + Options: options, + }) if err != nil { cancel() return nil, nil, E.Cause(err, "create service") diff --git a/cmd/sing-box/cmd_tools.go b/cmd/sing-box/cmd_tools.go index 34bc66c1..460a50cd 100644 --- a/cmd/sing-box/cmd_tools.go +++ b/cmd/sing-box/cmd_tools.go @@ -1,8 +1,6 @@ package main import ( - "context" - "github.com/sagernet/sing-box" E "github.com/sagernet/sing/common/exceptions" N "github.com/sagernet/sing/common/network" @@ -27,7 +25,7 @@ func createPreStartedClient() (*box.Box, error) { if err != nil { return nil, err } - instance, err := box.New(context.Background(), options, nil) + instance, err := box.New(box.Options{Options: options}) if err != nil { return nil, E.Cause(err, "create service") } diff --git a/experimental/libbox/config.go b/experimental/libbox/config.go index 94df3dea..3a2f1ad9 100644 --- a/experimental/libbox/config.go +++ b/experimental/libbox/config.go @@ -26,7 +26,10 @@ func CheckConfig(configContent string) error { } ctx, cancel := context.WithCancel(context.Background()) defer cancel() - instance, err := box.New(ctx, options, nil) + instance, err := box.New(box.Options{ + Context: ctx, + Options: options, + }) if err == nil { instance.Close() } diff --git a/experimental/libbox/service.go b/experimental/libbox/service.go index 891d21ab..1bc24446 100644 --- a/experimental/libbox/service.go +++ b/experimental/libbox/service.go @@ -30,7 +30,11 @@ func NewService(configContent string, platformInterface PlatformInterface) (*Box return nil, err } ctx, cancel := context.WithCancel(context.Background()) - instance, err := box.New(ctx, options, &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()}) + instance, err := box.New(box.Options{ + Context: ctx, + Options: options, + PlatformInterface: &platformInterfaceWrapper{platformInterface, platformInterface.UseProcFS()}, + }) if err != nil { cancel() return nil, E.Cause(err, "create service") diff --git a/log/default.go b/log/default.go index b469c07a..1784b67f 100644 --- a/log/default.go +++ b/log/default.go @@ -49,6 +49,10 @@ func (f *simpleFactory) NewLogger(tag string) ContextLogger { return &simpleLogger{f, tag} } +func (f *simpleFactory) Close() error { + return nil +} + var _ ContextLogger = (*simpleLogger)(nil) type simpleLogger struct { diff --git a/log/factory.go b/log/factory.go index cdc184c5..739aa0f2 100644 --- a/log/factory.go +++ b/log/factory.go @@ -15,6 +15,7 @@ type Factory interface { SetLevel(level Level) Logger() ContextLogger NewLogger(tag string) ContextLogger + Close() error } type ObservableFactory interface { diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..21f1e41d --- /dev/null +++ b/log/log.go @@ -0,0 +1,110 @@ +package log + +import ( + "io" + "os" + "time" + + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common" + E "github.com/sagernet/sing/common/exceptions" +) + +type factoryWithFile struct { + Factory + file *os.File +} + +func (f *factoryWithFile) Close() error { + return common.Close( + f.Factory, + common.PtrOrNil(f.file), + ) +} + +type observableFactoryWithFile struct { + ObservableFactory + file *os.File +} + +func (f *observableFactoryWithFile) Close() error { + return common.Close( + f.ObservableFactory, + common.PtrOrNil(f.file), + ) +} + +type Options struct { + Options option.LogOptions + Observable bool + DefaultWriter io.Writer + BaseTime time.Time + PlatformWriter io.Writer +} + +func New(options Options) (Factory, error) { + logOptions := options.Options + + if logOptions.Disabled { + return NewNOPFactory(), nil + } + + var logFile *os.File + var logWriter io.Writer + + switch logOptions.Output { + case "": + logWriter = options.DefaultWriter + if logWriter == nil { + logWriter = os.Stderr + } + case "stderr": + logWriter = os.Stderr + case "stdout": + logWriter = os.Stdout + default: + var err error + logFile, err = os.OpenFile(C.BasePath(logOptions.Output), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + if err != nil { + return nil, err + } + logWriter = logFile + } + logFormatter := Formatter{ + BaseTime: options.BaseTime, + DisableColors: logOptions.DisableColor || logFile != nil, + DisableTimestamp: !logOptions.Timestamp && logFile != nil, + FullTimestamp: logOptions.Timestamp, + TimestampFormat: "-0700 2006-01-02 15:04:05", + } + var factory Factory + if options.Observable { + factory = NewObservableFactory(logFormatter, logWriter, options.PlatformWriter) + } else { + factory = NewFactory(logFormatter, logWriter, options.PlatformWriter) + } + if logOptions.Level != "" { + logLevel, err := ParseLevel(logOptions.Level) + if err != nil { + return nil, E.Cause(err, "parse log level") + } + factory.SetLevel(logLevel) + } else { + factory.SetLevel(LevelTrace) + } + if logFile != nil { + if options.Observable { + factory = &observableFactoryWithFile{ + ObservableFactory: factory.(ObservableFactory), + file: logFile, + } + } else { + factory = &factoryWithFile{ + Factory: factory, + file: logFile, + } + } + } + return factory, nil +} diff --git a/log/nop.go b/log/nop.go index a189e0fb..06f0b872 100644 --- a/log/nop.go +++ b/log/nop.go @@ -72,6 +72,10 @@ func (f *nopFactory) FatalContext(ctx context.Context, args ...any) { func (f *nopFactory) PanicContext(ctx context.Context, args ...any) { } +func (f *nopFactory) Close() error { + return nil +} + func (f *nopFactory) Subscribe() (subscription observable.Subscription[Entry], done <-chan struct{}, err error) { return nil, nil, os.ErrInvalid }