package box import ( "context" "fmt" "io" "os" "runtime/debug" "time" "github.com/sagernet/sing-box/adapter" boxCertificate "github.com/sagernet/sing-box/adapter/certificate" "github.com/sagernet/sing-box/adapter/endpoint" "github.com/sagernet/sing-box/adapter/inbound" "github.com/sagernet/sing-box/adapter/outbound" boxService "github.com/sagernet/sing-box/adapter/service" "github.com/sagernet/sing-box/common/certificate" "github.com/sagernet/sing-box/common/dialer" "github.com/sagernet/sing-box/common/dnstrack" "github.com/sagernet/sing-box/common/httpclient" "github.com/sagernet/sing-box/common/taskmonitor" "github.com/sagernet/sing-box/common/tls" "github.com/sagernet/sing-box/common/trafficcontrol" "github.com/sagernet/sing-box/common/urltest" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/dns" "github.com/sagernet/sing-box/experimental" "github.com/sagernet/sing-box/experimental/cachefile" "github.com/sagernet/sing-box/experimental/deprecated" "github.com/sagernet/sing-box/log" "github.com/sagernet/sing-box/option" "github.com/sagernet/sing-box/protocol/direct" "github.com/sagernet/sing-box/route" "github.com/sagernet/sing/common" E "github.com/sagernet/sing/common/exceptions" F "github.com/sagernet/sing/common/format" "github.com/sagernet/sing/common/ntp" "github.com/sagernet/sing/service" "github.com/sagernet/sing/service/pause" ) var _ adapter.SimpleLifecycle = (*Box)(nil) type Box struct { createdAt time.Time logFactory log.Factory logger log.ContextLogger network *route.NetworkManager endpoint *endpoint.Manager inbound *inbound.Manager outbound *outbound.Manager service *boxService.Manager certificateProvider *boxCertificate.Manager dnsTransport *dns.TransportManager dnsRouter *dns.Router connection *route.ConnectionManager router *route.Router httpClientService adapter.LifecycleService internalService []adapter.LifecycleService done chan struct{} } type Options struct { option.Options Context context.Context PlatformLogWriter log.PlatformWriter // lx:begin shaterd_logsink // DefaultLogWriter, when non-nil, is where the box log goes while // options.Log.Output is unset — replacing log.New's nil->os.Stderr default. // The shater daemon passes its long-lived logsink here (via // engine.SetDefaultLogWriter) so every Apply-swapped box writes into the // same reconfigurable sink instead of raw stderr. It takes precedence over // the platform-interface io.Discard fallback below: an embedder that // supplies a writer wants the log, explicitly. DefaultLogWriter io.Writer // lx:end } func Context( ctx context.Context, inboundRegistry adapter.InboundRegistry, outboundRegistry adapter.OutboundRegistry, endpointRegistry adapter.EndpointRegistry, dnsTransportRegistry adapter.DNSTransportRegistry, serviceRegistry adapter.ServiceRegistry, certificateProviderRegistry adapter.CertificateProviderRegistry, ) context.Context { if service.FromContext[option.InboundOptionsRegistry](ctx) == nil || service.FromContext[adapter.InboundRegistry](ctx) == nil { ctx = service.ContextWith[option.InboundOptionsRegistry](ctx, inboundRegistry) ctx = service.ContextWith[adapter.InboundRegistry](ctx, inboundRegistry) } if service.FromContext[option.OutboundOptionsRegistry](ctx) == nil || service.FromContext[adapter.OutboundRegistry](ctx) == nil { ctx = service.ContextWith[option.OutboundOptionsRegistry](ctx, outboundRegistry) ctx = service.ContextWith[adapter.OutboundRegistry](ctx, outboundRegistry) } if service.FromContext[option.EndpointOptionsRegistry](ctx) == nil || service.FromContext[adapter.EndpointRegistry](ctx) == nil { ctx = service.ContextWith[option.EndpointOptionsRegistry](ctx, endpointRegistry) ctx = service.ContextWith[adapter.EndpointRegistry](ctx, endpointRegistry) } if service.FromContext[adapter.DNSTransportRegistry](ctx) == nil { ctx = service.ContextWith[option.DNSTransportOptionsRegistry](ctx, dnsTransportRegistry) ctx = service.ContextWith[adapter.DNSTransportRegistry](ctx, dnsTransportRegistry) } if service.FromContext[adapter.ServiceRegistry](ctx) == nil { ctx = service.ContextWith[option.ServiceOptionsRegistry](ctx, serviceRegistry) ctx = service.ContextWith[adapter.ServiceRegistry](ctx, serviceRegistry) } if service.FromContext[adapter.CertificateProviderRegistry](ctx) == nil { ctx = service.ContextWith[option.CertificateProviderOptionsRegistry](ctx, certificateProviderRegistry) ctx = service.ContextWith[adapter.CertificateProviderRegistry](ctx, certificateProviderRegistry) } return ctx } func New(options Options) (*Box, error) { createdAt := time.Now() ctx := options.Context if ctx == nil { ctx = context.Background() } ctx = service.ContextWithDefaultRegistry(ctx) endpointRegistry := service.FromContext[adapter.EndpointRegistry](ctx) inboundRegistry := service.FromContext[adapter.InboundRegistry](ctx) outboundRegistry := service.FromContext[adapter.OutboundRegistry](ctx) dnsTransportRegistry := service.FromContext[adapter.DNSTransportRegistry](ctx) serviceRegistry := service.FromContext[adapter.ServiceRegistry](ctx) certificateProviderRegistry := service.FromContext[adapter.CertificateProviderRegistry](ctx) if endpointRegistry == nil { return nil, E.New("missing endpoint registry in context") } if inboundRegistry == nil { return nil, E.New("missing inbound registry in context") } if outboundRegistry == nil { return nil, E.New("missing outbound registry in context") } if dnsTransportRegistry == nil { return nil, E.New("missing DNS transport registry in context") } if serviceRegistry == nil { return nil, E.New("missing service registry in context") } if certificateProviderRegistry == nil { return nil, E.New("missing certificate provider registry in context") } ctx = pause.WithDefaultManager(ctx) experimentalOptions := common.PtrValueOrDefault(options.Experimental) err := applyDebugOptions(common.PtrValueOrDefault(experimentalOptions.Debug)) if err != nil { return nil, err } var needCacheFile bool var needClashAPI bool var needV2RayAPI bool if experimentalOptions.CacheFile != nil && experimentalOptions.CacheFile.Enabled || options.PlatformLogWriter != nil { needCacheFile = true } // lx:begin lx_command // A platform log writer (always set on Android/libbox) historically forced // needClashAPI, because the Clash server was the only log/traffic observer. // With with_clash_api dropped (lx), that turned every Android start into a // fatal "clash api is not included in this build". Split the concern: the // platform writer needs *observability* (Observable log factory + traffic // manager), NOT the Clash server specifically. Only an explicit clash_api // config block now creates the Clash server; the native CommandClient // (SubscribeLog / SubscribeConnections) serves the platform client instead. if experimentalOptions.ClashAPI != nil { needClashAPI = true } if experimentalOptions.V2RayAPI != nil && experimentalOptions.V2RayAPI.Listen != "" { needV2RayAPI = true } needAPIService := common.Any(options.Services, func(it option.Service) bool { return it.Type == C.TypeAPI }) // Observability (log stream + connection/traffic tracking) is required by the // platform client and by either API surface, independent of the Clash server. needObservable := needClashAPI || needAPIService || options.PlatformLogWriter != nil // lx:end lx_command if service.PtrFromContext[urltest.HistoryStorage](ctx) == nil { ctx = service.ContextWithPtr(ctx, urltest.NewHistoryStorage()) } platformInterface := service.FromContext[adapter.PlatformInterface](ctx) var defaultLogWriter io.Writer if platformInterface != nil { defaultLogWriter = io.Discard } // lx:begin shaterd_logsink — an explicitly-supplied default writer wins. if options.DefaultLogWriter != nil { defaultLogWriter = options.DefaultLogWriter } // lx:end logFactory, err := log.New(log.Options{ Context: ctx, Options: common.PtrValueOrDefault(options.Log), Observable: needObservable, // lx: was needClashAPI || needAPIService DefaultWriter: defaultLogWriter, BaseTime: createdAt, PlatformWriter: options.PlatformLogWriter, }) if err != nil { return nil, E.Cause(err, "create log factory") } service.MustRegister[log.Factory](ctx, logFactory) var internalServices []adapter.LifecycleService routeOptions := common.PtrValueOrDefault(options.Route) certificateOptions := common.PtrValueOrDefault(options.Certificate) if C.IsAndroid || certificateOptions.Store != "" && certificateOptions.Store != C.CertificateStoreSystem || len(certificateOptions.Certificate) > 0 || len(certificateOptions.CertificatePath) > 0 || len(certificateOptions.CertificateDirectoryPath) > 0 { certificateStore, err := certificate.NewStore(logFactory.NewLogger("certificate"), certificateOptions) if err != nil { return nil, err } service.MustRegister[adapter.CertificateStore](ctx, certificateStore) internalServices = append(internalServices, certificateStore) } dnsOptions := common.PtrValueOrDefault(options.DNS) endpointManager := endpoint.NewManager(logFactory.NewLogger("endpoint"), endpointRegistry) inboundManager := inbound.NewManager(logFactory.NewLogger("inbound"), inboundRegistry, endpointManager) outboundManager := outbound.NewManager(logFactory.NewLogger("outbound"), outboundRegistry, endpointManager, routeOptions.Final) dnsTransportManager := dns.NewTransportManager(logFactory.NewLogger("dns/transport"), dnsTransportRegistry, outboundManager, dnsOptions.Final) serviceManager := boxService.NewManager(logFactory.NewLogger("service"), serviceRegistry) certificateProviderManager := boxCertificate.NewManager(logFactory.NewLogger("certificate-provider"), certificateProviderRegistry) service.MustRegister[adapter.EndpointManager](ctx, endpointManager) service.MustRegister[adapter.InboundManager](ctx, inboundManager) service.MustRegister[adapter.OutboundManager](ctx, outboundManager) service.MustRegister[adapter.DNSTransportManager](ctx, dnsTransportManager) service.MustRegister[adapter.ServiceManager](ctx, serviceManager) service.MustRegister[adapter.CertificateProviderManager](ctx, certificateProviderManager) dnsRouter, err := dns.NewRouter(ctx, logFactory, dnsOptions) if err != nil { return nil, E.Cause(err, "initialize DNS router") } service.MustRegister[adapter.DNSRouter](ctx, dnsRouter) service.MustRegister[adapter.DNSRuleSetUpdateValidator](ctx, dnsRouter) networkManager, err := route.NewNetworkManager(ctx, logFactory.NewLogger("network"), routeOptions, dnsOptions) if err != nil { return nil, E.Cause(err, "initialize network manager") } service.MustRegister[adapter.NetworkManager](ctx, networkManager) connectionManager := route.NewConnectionManager(logFactory.NewLogger("connection")) service.MustRegister[adapter.ConnectionManager](ctx, connectionManager) // Must register after ConnectionManager: the Apple HTTP engine's proxy bridge reads it from the context when Manager.Start resolves the default client. httpClientManager := httpclient.NewManager(ctx, logFactory.NewLogger("httpclient"), options.HTTPClients, routeOptions.DefaultHTTPClient) service.MustRegister[adapter.HTTPClientManager](ctx, httpClientManager) httpClientService := adapter.LifecycleService(httpClientManager) router := route.NewRouter(ctx, logFactory, routeOptions, dnsOptions) service.MustRegister[adapter.Router](ctx, router) service.MustRegister[adapter.ReachabilityInvalidator](ctx, router) // lx: SPEC 020 — event points invalidate the reachable cache err = router.Initialize(routeOptions.Rules, routeOptions.RuleSet) if err != nil { return nil, E.Cause(err, "initialize router") } // lx: shater feedback #2 — build the connection/traffic tracker UNCONDITIONALLY // (it was gated on needObservable). The shater control-plane subscribes to this // Manager's in-process connection-event stream (SubscribeEvents) to attribute // per-device DESTINATION DOMAINS ("which client hit which host" — DPI-style). // box.New historically created the tracker only under needObservable (clash-api / // api service / platform log writer), and the shater generator emits NONE of those // on the router, so the tracker — and therefore every connection event — would // never exist (the same dead-stream situation the DNS query manager was in before // it was pre-registered in the engine ctx). Unlike the DNS manager this one CANNOT // be pre-built in the engine ctx: it needs the outboundManager that only exists // here inside box.New, and box.New reuses the engine's shared registry, so the // aggregator reads it back via service.PtrFromContext (engine.ConnManager). The // pointer changes on every Apply swap; the aggregator resubscribes on pointer // identity (mirroring the DNS loop's box-owned fallback). // // Why this is safe / opens NO port: the Clash server is still gated on // needClashAPI below — this branch creates only the pure in-process observable. // The observable's Emit is non-blocking (drops when nobody is draining, see // sing/common/observable), so an UNSUBSCRIBED tracker costs only the per-connection // byte counters + bounded connection map that clash already imposes — no hot-path // stall. This is the identical construct upstream builds whenever clash API is on. trafficManager := trafficcontrol.NewManager(outboundManager) service.MustRegisterPtr(ctx, trafficManager) router.AppendTracker(trafficManager) internalServices = append(internalServices, trafficManager) if needObservable { // lx: was needClashAPI || needAPIService — platform writer needs the DNS stream too // lx: SPEC 018 — DNS query stream. Registered as *dnstrack.Manager so the dns // client (service.PtrFromContext in dns/client_log.go — pairs with MustRegisterPtr; // FromContext[*T] keys on **T and silently returns nil, the §180 dead-stream bug) // emits structured, process-attributed query events the command server exposes as // SubscribeDNSQueries. Only the DNS manager stays gated on needObservable: the // engine pre-registers a STABLE *dnstrack.Manager in its own ctx, so on the router // (needObservable=false) this branch is skipped and that stable manager is used; // the traffic manager above was moved out of this block so it is always present. dnsQueryManager := dnstrack.NewManager() service.MustRegisterPtr(ctx, dnsQueryManager) internalServices = append(internalServices, dnsQueryManager) } ntpOptions := common.PtrValueOrDefault(options.NTP) var timeService *tls.TimeServiceWrapper if ntpOptions.Enabled { timeService = new(tls.TimeServiceWrapper) service.MustRegister[ntp.TimeService](ctx, timeService) } for i, transportOptions := range dnsOptions.Servers { var tag string if transportOptions.Tag != "" { tag = transportOptions.Tag } else { tag = F.ToString(i) } err = dnsTransportManager.Create( ctx, logFactory.NewLogger(F.ToString("dns/", transportOptions.Type, "[", tag, "]")), tag, transportOptions.Type, transportOptions.Options, ) if err != nil { return nil, E.Cause(err, "initialize DNS server[", i, "]") } } err = dnsRouter.Initialize(dnsOptions.Rules) if err != nil { return nil, E.Cause(err, "initialize dns router") } for i, endpointOptions := range options.Endpoints { var tag string if endpointOptions.Tag != "" { tag = endpointOptions.Tag } else { tag = F.ToString(i) } endpointCtx := ctx if tag != "" { // TODO: remove this endpointCtx = adapter.WithContext(endpointCtx, &adapter.InboundContext{ Outbound: tag, }) } err = endpointManager.Create( endpointCtx, router, logFactory.NewLogger(F.ToString("endpoint/", endpointOptions.Type, "[", tag, "]")), tag, endpointOptions.Type, endpointOptions.Options, ) if err != nil { return nil, E.Cause(err, "initialize endpoint[", i, "]") } } for i, inboundOptions := range options.Inbounds { var tag string if inboundOptions.Tag != "" { tag = inboundOptions.Tag } else { tag = F.ToString(i) } err = inboundManager.Create( ctx, router, logFactory.NewLogger(F.ToString("inbound/", inboundOptions.Type, "[", tag, "]")), tag, inboundOptions.Type, inboundOptions.Options, ) if err != nil { return nil, E.Cause(err, "initialize inbound[", i, "]") } } for i, serviceOptions := range options.Services { var tag string if serviceOptions.Tag != "" { tag = serviceOptions.Tag } else { tag = F.ToString(i) } err = serviceManager.Create( ctx, logFactory.NewLogger(F.ToString("service/", serviceOptions.Type, "[", tag, "]")), tag, serviceOptions.Type, serviceOptions.Options, ) if err != nil { return nil, E.Cause(err, "initialize service[", i, "]") } } for i, outboundOptions := range options.Outbounds { var tag string if outboundOptions.Tag != "" { tag = outboundOptions.Tag } else { tag = F.ToString(i) } outboundCtx := ctx if tag != "" { // TODO: remove this outboundCtx = adapter.WithContext(outboundCtx, &adapter.InboundContext{ Outbound: tag, }) } err = outboundManager.Create( outboundCtx, router, logFactory.NewLogger(F.ToString("outbound/", outboundOptions.Type, "[", tag, "]")), tag, outboundOptions.Type, outboundOptions.Options, ) if err != nil { return nil, E.Cause(err, "initialize outbound[", i, "]") } } for i, certificateProviderOptions := range options.CertificateProviders { var tag string if certificateProviderOptions.Tag != "" { tag = certificateProviderOptions.Tag } else { tag = F.ToString(i) } err = certificateProviderManager.Create( ctx, logFactory.NewLogger(F.ToString("certificate-provider/", certificateProviderOptions.Type, "[", tag, "]")), tag, certificateProviderOptions.Type, certificateProviderOptions.Options, ) if err != nil { return nil, E.Cause(err, "initialize certificate provider[", i, "]") } } outboundManager.Initialize(func() (adapter.Outbound, error) { return direct.NewOutbound( ctx, router, logFactory.NewLogger("outbound/direct"), "direct", option.DirectOutboundOptions{}, ) }) dnsTransportManager.Initialize(func() (adapter.DNSTransport, error) { return dnsTransportRegistry.CreateDNSTransport( ctx, logFactory.NewLogger("dns/local"), "local", C.DNSTypeLocal, &option.LocalDNSServerOptions{}, ) }) httpClientManager.Initialize(func() (*httpclient.ManagedTransport, error) { deprecated.Report(ctx, deprecated.OptionImplicitDefaultHTTPClient) var httpClientOptions option.HTTPClientOptions httpClientOptions.DefaultOutbound = true return httpclient.NewTransport(ctx, logFactory.NewLogger("httpclient"), "", httpClientOptions) }) if platformInterface != nil { err = platformInterface.Initialize(networkManager) if err != nil { return nil, E.Cause(err, "initialize platform interface") } } if needCacheFile { cacheFile := cachefile.New(ctx, logFactory.NewLogger("cache-file"), common.PtrValueOrDefault(experimentalOptions.CacheFile)) service.MustRegister[adapter.CacheFile](ctx, cacheFile) internalServices = append(internalServices, cacheFile) } if needClashAPI { clashAPIOptions := common.PtrValueOrDefault(experimentalOptions.ClashAPI) clashAPIOptions.ModeList = experimental.CalculateClashModeList(options.Options) clashServer, err := experimental.NewClashServer(ctx, logFactory.(log.ObservableFactory), clashAPIOptions) if err != nil { return nil, E.Cause(err, "create clash-server") } service.MustRegister[adapter.ClashServer](ctx, clashServer) internalServices = append(internalServices, clashServer) } if needV2RayAPI { v2rayServer, err := experimental.NewV2RayServer(logFactory.NewLogger("v2ray-api"), common.PtrValueOrDefault(experimentalOptions.V2RayAPI)) if err != nil { return nil, E.Cause(err, "create v2ray-server") } if v2rayServer.StatsService() != nil { router.AppendTracker(v2rayServer.StatsService()) internalServices = append(internalServices, v2rayServer) service.MustRegister[adapter.V2RayServer](ctx, v2rayServer) } } if ntpOptions.Enabled { ntpDialer, err := dialer.New(ctx, ntpOptions.DialerOptions, ntpOptions.ServerIsDomain()) if err != nil { return nil, E.Cause(err, "create NTP service") } ntpService := ntp.NewService(ntp.Options{ Context: ctx, Dialer: ntpDialer, Logger: logFactory.NewLogger("ntp"), Server: ntpOptions.ServerOptions.Build(), Interval: time.Duration(ntpOptions.Interval), WriteToSystem: ntpOptions.WriteToSystem, }) timeService.TimeService = ntpService internalServices = append(internalServices, adapter.NewLifecycleService(ntpService, "ntp service")) } return &Box{ network: networkManager, endpoint: endpointManager, inbound: inboundManager, outbound: outboundManager, dnsTransport: dnsTransportManager, service: serviceManager, certificateProvider: certificateProviderManager, dnsRouter: dnsRouter, connection: connectionManager, router: router, httpClientService: httpClientService, createdAt: createdAt, logFactory: logFactory, logger: logFactory.Logger(), internalService: internalServices, done: make(chan struct{}), }, nil } func (s *Box) PreStart() error { err := s.preStart() if err != nil { // TODO: remove catch error defer func() { v := recover() if v != nil { println(err.Error()) debug.PrintStack() panic("panic on early close: " + fmt.Sprint(v)) } }() s.Close() return err } s.logger.Info("sing-box pre-started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") return nil } func (s *Box) Start() error { err := s.start() if err != nil { // TODO: remove catch error defer func() { v := recover() if v != nil { println(err.Error()) debug.PrintStack() println("panic on early start: " + fmt.Sprint(v)) } }() s.Close() return err } s.logger.Info("sing-box started (", F.Seconds(time.Since(s.createdAt).Seconds()), "s)") return nil } func (s *Box) preStart() error { monitor := taskmonitor.New(s.logger, C.StartTimeout) monitor.Start("start logger") err := s.logFactory.Start() monitor.Finish() if err != nil { return E.Cause(err, "start logger") } err = adapter.StartNamed(s.logger, adapter.StartStateInitialize, s.internalService) // cache-file clash-api v2ray-api if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStateInitialize, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.inbound, s.endpoint, s.service, s.certificateProvider) if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStateStart, s.outbound, s.dnsTransport, s.network, s.connection) if err != nil { return err } err = adapter.StartNamed(s.logger, adapter.StartStateStart, []adapter.LifecycleService{s.httpClientService}) if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStateStart, s.router, s.dnsRouter) if err != nil { return err } return nil } func (s *Box) start() error { err := s.preStart() if err != nil { return err } err = adapter.StartNamed(s.logger, adapter.StartStateStart, s.internalService) if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStateStart, s.endpoint) if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStateStart, s.certificateProvider) if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStateStart, s.inbound, s.service) if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStatePostStart, s.outbound, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.endpoint, s.certificateProvider, s.inbound, s.service) if err != nil { return err } err = adapter.StartNamed(s.logger, adapter.StartStatePostStart, s.internalService) if err != nil { return err } err = adapter.Start(s.logger, adapter.StartStateStarted, s.network, s.dnsTransport, s.dnsRouter, s.connection, s.router, s.outbound, s.endpoint, s.certificateProvider, s.inbound, s.service) if err != nil { return err } err = adapter.StartNamed(s.logger, adapter.StartStateStarted, s.internalService) if err != nil { return err } return nil } func (s *Box) Close() error { select { case <-s.done: return os.ErrClosed default: close(s.done) } var err error for _, closeItem := range []struct { name string service adapter.Lifecycle }{ {"service", s.service}, {"inbound", s.inbound}, {"certificate-provider", s.certificateProvider}, {"endpoint", s.endpoint}, {"outbound", s.outbound}, {"router", s.router}, {"connection", s.connection}, {"dns-router", s.dnsRouter}, {"dns-transport", s.dnsTransport}, {"network", s.network}, } { done := adapter.LogElapsed(s.logger, "close ", closeItem.name) err = E.Append(err, closeItem.service.Close(), func(err error) error { return E.Cause(err, "close ", closeItem.name) }) done() } if s.httpClientService != nil { s.logger.Trace("close ", s.httpClientService.Name()) startTime := time.Now() err = E.Append(err, s.httpClientService.Close(), func(err error) error { return E.Cause(err, "close ", s.httpClientService.Name()) }) s.logger.Trace("close ", s.httpClientService.Name(), " completed (", F.Seconds(time.Since(startTime).Seconds()), "s)") } for _, lifecycleService := range s.internalService { done := adapter.LogElapsed(s.logger, "close ", lifecycleService.Name()) err = E.Append(err, lifecycleService.Close(), func(err error) error { return E.Cause(err, "close ", lifecycleService.Name()) }) done() } done := adapter.LogElapsed(s.logger, "close logger") err = E.Append(err, s.logFactory.Close(), func(err error) error { return E.Cause(err, "close logger") }) done() return err } func (s *Box) Network() adapter.NetworkManager { return s.network } func (s *Box) Router() adapter.Router { return s.router } func (s *Box) Inbound() adapter.InboundManager { return s.inbound } func (s *Box) Outbound() adapter.OutboundManager { return s.outbound } func (s *Box) Endpoint() adapter.EndpointManager { return s.endpoint } func (s *Box) LogFactory() log.Factory { return s.logFactory }