detached 0ec9504

fix(broker): record client and broker lifecycle diagnostics

commit
0ec9504d637d788270ffae898df13693536a6fb8
author
brice · · 2026-09-23 08:36 UTC
parent
5c34fff
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +49 -7
M internal/app/broker_client.go +3 -0
@@ -4,6 +4,7 @@ import (
44 "context"
55 "errors"
66 "fmt"
7+ "log/slog"
78 "os"
89 "sync"
910 "time"
@@ -41,6 +42,7 @@ import (
4142 // to the process clock); the remaining fields are optional and keep the
4243 // supervisor defaults when they are left zero.
4344 type brokerClientConfig struct {
45+ Logger *slog.Logger
4446 Connector ports.BrokerConnector
4547 Terminal ports.Terminal
4648 Clock ports.Clock
@@ -277,6 +279,7 @@ func runBrokerClient(ctx context.Context, cfg brokerClientConfig) error {
277279 picker := client.NewPicker(clk, 0, attachmentEnv.TrueColor)
278280 presentation := &brokerClientPresentation{terminal: cfg.Terminal, picker: picker, ui: cfg.UI, onState: cfg.OnState, clock: clk}
279281 supervisor, err := client.NewSupervisor(client.SupervisorConfig{
282+ Logger: cfg.Logger,
280283 Connector: cfg.Connector,
281284 Terminal: cfg.Terminal,
282285 Clock: clk,
@@ -4,6 +4,7 @@ import (
4 "context"4 "context"
5 "errors"5 "errors"
6 "fmt"6 "fmt"
7+ "log/slog"
7 "os"8 "os"
8 "sync"9 "sync"
9 "time"10 "time"
@@ -41,6 +42,7 @@ import (
41 // to the process clock); the remaining fields are optional and keep the42 // to the process clock); the remaining fields are optional and keep the
42 // supervisor defaults when they are left zero.43 // supervisor defaults when they are left zero.
43 type brokerClientConfig struct {44 type brokerClientConfig struct {
45+ Logger *slog.Logger
44 Connector ports.BrokerConnector46 Connector ports.BrokerConnector
45 Terminal ports.Terminal47 Terminal ports.Terminal
46 Clock ports.Clock48 Clock ports.Clock
@@ -277,6 +279,7 @@ func runBrokerClient(ctx context.Context, cfg brokerClientConfig) error {
277 picker := client.NewPicker(clk, 0, attachmentEnv.TrueColor)279 picker := client.NewPicker(clk, 0, attachmentEnv.TrueColor)
278 presentation := &brokerClientPresentation{terminal: cfg.Terminal, picker: picker, ui: cfg.UI, onState: cfg.OnState, clock: clk}280 presentation := &brokerClientPresentation{terminal: cfg.Terminal, picker: picker, ui: cfg.UI, onState: cfg.OnState, clock: clk}
279 supervisor, err := client.NewSupervisor(client.SupervisorConfig{281 supervisor, err := client.NewSupervisor(client.SupervisorConfig{
282+ Logger: cfg.Logger,
280 Connector: cfg.Connector,283 Connector: cfg.Connector,
281 Terminal: cfg.Terminal,284 Terminal: cfg.Terminal,
282 Clock: clk,285 Clock: clk,
M internal/app/broker_offline.go +2 -2
@@ -56,7 +56,7 @@ const (
5656 // refused rather than absorbed.
5757 var brokerServePoolLimits = broker.PoolLimits{
5858 Physical: 64,
59- Clients: brokeripc.DefaultMaxClients,
59+ Clients: 2 * brokeripc.DefaultMaxClients,
6060 Streams: 256,
6161 StreamsPerClient: 64,
6262 Warm: brokerconfig.DefaultWarmTransports,
@@ -216,7 +216,7 @@ func runBrokerServeCommand(ctx context.Context, options brokerServeOptions) (ret
216216 func sandboxLogging(dir string) (*slog.Logger, io.Closer, error) {
217217 return logging.Setup(logging.Config{
218218 Dir: dir,
219- Component: logging.Daemon,
219+ Component: logging.Broker,
220220 Level: logging.EnvLevel(),
221221 MaxBytes: logging.DefaultMaxBytes,
222222 })
@@ -56,7 +56,7 @@ const (
56 // refused rather than absorbed.56 // refused rather than absorbed.
57 var brokerServePoolLimits = broker.PoolLimits{57 var brokerServePoolLimits = broker.PoolLimits{
58 Physical: 64,58 Physical: 64,
59- Clients: brokeripc.DefaultMaxClients,59+ Clients: 2 * brokeripc.DefaultMaxClients,
60 Streams: 256,60 Streams: 256,
61 StreamsPerClient: 64,61 StreamsPerClient: 64,
62 Warm: brokerconfig.DefaultWarmTransports,62 Warm: brokerconfig.DefaultWarmTransports,
@@ -216,7 +216,7 @@ func runBrokerServeCommand(ctx context.Context, options brokerServeOptions) (ret
216 func sandboxLogging(dir string) (*slog.Logger, io.Closer, error) {216 func sandboxLogging(dir string) (*slog.Logger, io.Closer, error) {
217 return logging.Setup(logging.Config{217 return logging.Setup(logging.Config{
218 Dir: dir,218 Dir: dir,
219- Component: logging.Daemon,219+ Component: logging.Broker,
220 Level: logging.EnvLevel(),220 Level: logging.EnvLevel(),
221 MaxBytes: logging.DefaultMaxBytes,221 MaxBytes: logging.DefaultMaxBytes,
222 })222 })
M internal/app/ui_driver.go +4 -2
@@ -230,7 +230,7 @@ func runHeadlessUIDriver(ctx context.Context, options uiDriverOptions) error {
230230 if err != nil {
231231 return err
232232 }
233- _, logCloser, err := configureLogging(logging.Client, false)
233+ log, logCloser, err := configureLogging(logging.Client, false)
234234 if err != nil {
235235 return err
236236 }
@@ -245,6 +245,7 @@ func runHeadlessUIDriver(ctx context.Context, options uiDriverOptions) error {
245245 attachmentEnv := terminalAttachmentEnvironment()
246246 attachmentEnv.Cwd = sessionEnv.Cwd
247247 return runUIDriverClient(ctx, brokerClientConfig{
248+ Logger: log,
248249 Connector: newProductionBrokerConnector(),
249250 Terminal: terminal,
250251 Clock: clk,
@@ -434,7 +435,7 @@ func runAttachWithOptions(ctx context.Context, intent uint8, name, remoteTarget
434435 }
435436 ctx, stop := signal.NotifyContext(ctx, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
436437 defer stop()
437- _, logCloser, err := configureLogging(logging.Client, false)
438+ log, logCloser, err := configureLogging(logging.Client, false)
438439 if err != nil {
439440 return err
440441 }
@@ -499,6 +500,7 @@ func runAttachWithOptions(ctx context.Context, intent uint8, name, remoteTarget
499500 attachmentEnv := terminalAttachmentEnvironment()
500501 attachmentEnv.Cwd = sessionEnv.Cwd
501502 return runBrokerClient(ctx, brokerClientConfig{
503+ Logger: log,
502504 Connector: withPreconnected(newProductionBrokerConnector(), preconnected),
503505 Terminal: terminal,
504506 Clock: clk,
@@ -230,7 +230,7 @@ func runHeadlessUIDriver(ctx context.Context, options uiDriverOptions) error {
230 if err != nil {230 if err != nil {
231 return err231 return err
232 }232 }
233- _, logCloser, err := configureLogging(logging.Client, false)233+ log, logCloser, err := configureLogging(logging.Client, false)
234 if err != nil {234 if err != nil {
235 return err235 return err
236 }236 }
@@ -245,6 +245,7 @@ func runHeadlessUIDriver(ctx context.Context, options uiDriverOptions) error {
245 attachmentEnv := terminalAttachmentEnvironment()245 attachmentEnv := terminalAttachmentEnvironment()
246 attachmentEnv.Cwd = sessionEnv.Cwd246 attachmentEnv.Cwd = sessionEnv.Cwd
247 return runUIDriverClient(ctx, brokerClientConfig{247 return runUIDriverClient(ctx, brokerClientConfig{
248+ Logger: log,
248 Connector: newProductionBrokerConnector(),249 Connector: newProductionBrokerConnector(),
249 Terminal: terminal,250 Terminal: terminal,
250 Clock: clk,251 Clock: clk,
@@ -434,7 +435,7 @@ func runAttachWithOptions(ctx context.Context, intent uint8, name, remoteTarget
434 }435 }
435 ctx, stop := signal.NotifyContext(ctx, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)436 ctx, stop := signal.NotifyContext(ctx, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
436 defer stop()437 defer stop()
437- _, logCloser, err := configureLogging(logging.Client, false)438+ log, logCloser, err := configureLogging(logging.Client, false)
438 if err != nil {439 if err != nil {
439 return err440 return err
440 }441 }
@@ -499,6 +500,7 @@ func runAttachWithOptions(ctx context.Context, intent uint8, name, remoteTarget
499 attachmentEnv := terminalAttachmentEnvironment()500 attachmentEnv := terminalAttachmentEnvironment()
500 attachmentEnv.Cwd = sessionEnv.Cwd501 attachmentEnv.Cwd = sessionEnv.Cwd
501 return runBrokerClient(ctx, brokerClientConfig{502 return runBrokerClient(ctx, brokerClientConfig{
503+ Logger: log,
502 Connector: withPreconnected(newProductionBrokerConnector(), preconnected),504 Connector: withPreconnected(newProductionBrokerConnector(), preconnected),
503 Terminal: terminal,505 Terminal: terminal,
504 Clock: clk,506 Clock: clk,
M internal/logging/logging.go +1 -0
@@ -16,6 +16,7 @@ type Component string
1616
1717 const (
1818 Daemon Component = "daemon"
19+ Broker Component = "broker"
1920 Client Component = "client"
2021 Stdio Component = "stdio"
2122 )
@@ -16,6 +16,7 @@ type Component string
16 16
17 const (17 const (
18 Daemon Component = "daemon"18 Daemon Component = "daemon"
19+ Broker Component = "broker"
19 Client Component = "client"20 Client Component = "client"
20 Stdio Component = "stdio"21 Stdio Component = "stdio"
21 )22 )
M internal/usecase/client/supervisor.go +39 -3
@@ -5,6 +5,7 @@ import (
55 "errors"
66 "fmt"
77 "io"
8+ "log/slog"
89 "math/rand/v2"
910 "reflect"
1011 "sync"
@@ -290,6 +291,8 @@ type SpinnerPresentation interface {
290291 }
291292
292293 type SupervisorConfig struct {
294+ // Logger receives bounded client lifecycle diagnostics. Optional.
295+ Logger *slog.Logger
293296 // Connector establishes the broker connection. Required.
294297 Connector ports.BrokerConnector
295298 // Terminal is the controlling terminal the supervisor owns. Required.
@@ -382,7 +385,8 @@ type AttachmentLifecycleAction struct {
382385 // Supervisor owns one autonomous client process: raw mode, one terminal input
383386 // lifetime, and the broker-connectivity lifecycle.
384387 type Supervisor struct {
385- cfg SupervisorConfig
388+ cfg SupervisorConfig
389+ logger *slog.Logger
386390
387391 // pendingPickerKey retains a commit observed while connecting. It is
388392 // revalidated against the adopted service just like a ready-phase commit.
@@ -465,8 +469,13 @@ func NewSupervisor(cfg SupervisorConfig) (*Supervisor, error) {
465469 } else if err := cfg.InitialNavigation.Validate(); err != nil {
466470 return nil, fmt.Errorf("vev: supervisor initial navigation: %w", err)
467471 }
472+ logger := cfg.Logger
473+ if logger == nil {
474+ logger = slog.New(slog.NewTextHandler(io.Discard, nil))
475+ }
468476 supervisor := &Supervisor{
469477 cfg: cfg,
478+ logger: logger,
470479 state: State{Presentation: PresentPicker, Connectivity: ConnectivityDisconnected},
471480 clientID: newClientID(),
472481 navigation: cfg.InitialNavigation,
@@ -732,9 +741,12 @@ func (s *Supervisor) retryFailure(ctx context.Context, input *terminalInputLifet
732741 // and normalized to a typed unavailable failure so the run loop never observes
733742 // an untyped cause.
734743 func (s *Supervisor) connectAttempt(ctx context.Context, generation uint64) supervisorAttempt {
744+ s.logger.Debug("client_broker_connect_begin", "generation", generation)
735745 service, err := s.cfg.Connector.Connect(ctx)
736746 if err != nil {
737- return supervisorAttempt{generation: generation, err: normalizeUnavailable(err)}
747+ failure := normalizeUnavailable(err)
748+ s.logger.Warn("client_broker_connect_failed", "generation", generation, "code", brokerErrorCode(failure), "error", failure, "cause", errors.Unwrap(failure))
749+ return supervisorAttempt{generation: generation, err: failure}
738750 }
739751 if supervisorNil(service) {
740752 return supervisorAttempt{generation: generation, err: ports.BrokerError{
@@ -745,7 +757,9 @@ func (s *Supervisor) connectAttempt(ctx context.Context, generation uint64) supe
745757 sub, err := service.Subscribe()
746758 if err != nil {
747759 _ = service.Close()
748- return supervisorAttempt{generation: generation, err: normalizeUnavailable(err)}
760+ failure := normalizeUnavailable(err)
761+ s.logger.Warn("client_broker_subscribe_failed", "generation", generation, "code", brokerErrorCode(failure), "error", failure, "cause", errors.Unwrap(failure))
762+ return supervisorAttempt{generation: generation, err: failure}
749763 }
750764 if supervisorNil(sub) {
751765 // A service that reports no subscription cannot ever become Ready, so it
@@ -766,6 +780,7 @@ func (s *Supervisor) connectAttempt(ctx context.Context, generation uint64) supe
766780 continue
767781 case <-service.Done():
768782 cause := normalizeUnavailable(service.Err())
783+ s.logger.Warn("client_broker_initial_publication_lost", "generation", generation, "code", brokerErrorCode(cause), "error", cause, "cause", errors.Unwrap(cause))
769784 sub.Close()
770785 _ = service.Close()
771786 return supervisorAttempt{generation: generation, err: cause}
@@ -1086,6 +1101,14 @@ func supervisorRetryable(err error) bool {
10861101 // policy) still selects the non-retryable path; every untyped failure, including
10871102 // a nil cause, becomes an unavailable BrokerError that preserves the original
10881103 // error as its local-only Cause.
1104+func brokerErrorCode(err error) string {
1105+ var typed ports.BrokerError
1106+ if errors.As(err, &typed) {
1107+ return typed.Code.String()
1108+ }
1109+ return "unknown"
1110+}
1111+
10891112 func normalizeUnavailable(err error) error {
10901113 if err == nil {
10911114 return ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "broker connection lost"}
@@ -1094,17 +1117,27 @@ func normalizeUnavailable(err error) error {
10941117 if errors.As(err, &typed) {
10951118 return err
10961119 }
1120+ var admission ports.BrokerAdmissionError
1121+ if errors.As(err, &admission) {
1122+ return err
1123+ }
10971124 return ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "broker connection lost", Cause: err}
10981125 }
10991126
11001127 // notify surfaces a connectivity failure through the optional notifier.
11011128 func (s *Supervisor) notifyLifecycle(kind LifecycleNoticeKind) {
1129+ if s != nil {
1130+ s.logger.Debug("client_lifecycle_notice", "kind", kind)
1131+ }
11021132 if s != nil && s.cfg.NotifyLifecycle != nil {
11031133 s.cfg.NotifyLifecycle(LifecycleNotice{Kind: kind})
11041134 }
11051135 }
11061136
11071137 func (s *Supervisor) notify(err error) {
1138+ if err != nil {
1139+ s.logger.Warn("client_connectivity_failure", "state", s.State().Connectivity.String(), "code", brokerErrorCode(err), "error", err, "cause", errors.Unwrap(err))
1140+ }
11081141 if s.cfg.Notify == nil || err == nil {
11091142 return
11101143 }
@@ -1130,6 +1163,9 @@ func (s *Supervisor) transition(event supervisorEvent) {
11301163 s.state = reduceSupervisor(s.state, event)
11311164 state := s.state
11321165 s.mu.Unlock()
1166+ if state.Presentation != previous || event.kind == supervisorBeginAttempt || event.kind == supervisorTransientFailure || event.kind == supervisorBrokerLoss {
1167+ s.logger.Debug("client_state_transition", "from", previous.String(), "to", state.Presentation.String(), "connectivity", state.Connectivity.String(), "attempt", state.Attempt, "generation", state.Generation, "error", event.err, "cause", errors.Unwrap(event.err))
1168+ }
11331169 if state.Presentation != previous {
11341170 s.publishPresentation(state.Presentation)
11351171 }
@@ -5,6 +5,7 @@ import (
5 "errors"5 "errors"
6 "fmt"6 "fmt"
7 "io"7 "io"
8+ "log/slog"
8 "math/rand/v2"9 "math/rand/v2"
9 "reflect"10 "reflect"
10 "sync"11 "sync"
@@ -290,6 +291,8 @@ type SpinnerPresentation interface {
290 }291 }
291 292
292 type SupervisorConfig struct {293 type SupervisorConfig struct {
294+ // Logger receives bounded client lifecycle diagnostics. Optional.
295+ Logger *slog.Logger
293 // Connector establishes the broker connection. Required.296 // Connector establishes the broker connection. Required.
294 Connector ports.BrokerConnector297 Connector ports.BrokerConnector
295 // Terminal is the controlling terminal the supervisor owns. Required.298 // Terminal is the controlling terminal the supervisor owns. Required.
@@ -382,7 +385,8 @@ type AttachmentLifecycleAction struct {
382 // Supervisor owns one autonomous client process: raw mode, one terminal input385 // Supervisor owns one autonomous client process: raw mode, one terminal input
383 // lifetime, and the broker-connectivity lifecycle.386 // lifetime, and the broker-connectivity lifecycle.
384 type Supervisor struct {387 type Supervisor struct {
385- cfg SupervisorConfig388+ cfg SupervisorConfig
389+ logger *slog.Logger
386 390
387 // pendingPickerKey retains a commit observed while connecting. It is391 // pendingPickerKey retains a commit observed while connecting. It is
388 // revalidated against the adopted service just like a ready-phase commit.392 // revalidated against the adopted service just like a ready-phase commit.
@@ -465,8 +469,13 @@ func NewSupervisor(cfg SupervisorConfig) (*Supervisor, error) {
465 } else if err := cfg.InitialNavigation.Validate(); err != nil {469 } else if err := cfg.InitialNavigation.Validate(); err != nil {
466 return nil, fmt.Errorf("vev: supervisor initial navigation: %w", err)470 return nil, fmt.Errorf("vev: supervisor initial navigation: %w", err)
467 }471 }
472+ logger := cfg.Logger
473+ if logger == nil {
474+ logger = slog.New(slog.NewTextHandler(io.Discard, nil))
475+ }
468 supervisor := &Supervisor{476 supervisor := &Supervisor{
469 cfg: cfg,477 cfg: cfg,
478+ logger: logger,
470 state: State{Presentation: PresentPicker, Connectivity: ConnectivityDisconnected},479 state: State{Presentation: PresentPicker, Connectivity: ConnectivityDisconnected},
471 clientID: newClientID(),480 clientID: newClientID(),
472 navigation: cfg.InitialNavigation,481 navigation: cfg.InitialNavigation,
@@ -732,9 +741,12 @@ func (s *Supervisor) retryFailure(ctx context.Context, input *terminalInputLifet
732 // and normalized to a typed unavailable failure so the run loop never observes741 // and normalized to a typed unavailable failure so the run loop never observes
733 // an untyped cause.742 // an untyped cause.
734 func (s *Supervisor) connectAttempt(ctx context.Context, generation uint64) supervisorAttempt {743 func (s *Supervisor) connectAttempt(ctx context.Context, generation uint64) supervisorAttempt {
744+ s.logger.Debug("client_broker_connect_begin", "generation", generation)
735 service, err := s.cfg.Connector.Connect(ctx)745 service, err := s.cfg.Connector.Connect(ctx)
736 if err != nil {746 if err != nil {
737- return supervisorAttempt{generation: generation, err: normalizeUnavailable(err)}747+ failure := normalizeUnavailable(err)
748+ s.logger.Warn("client_broker_connect_failed", "generation", generation, "code", brokerErrorCode(failure), "error", failure, "cause", errors.Unwrap(failure))
749+ return supervisorAttempt{generation: generation, err: failure}
738 }750 }
739 if supervisorNil(service) {751 if supervisorNil(service) {
740 return supervisorAttempt{generation: generation, err: ports.BrokerError{752 return supervisorAttempt{generation: generation, err: ports.BrokerError{
@@ -745,7 +757,9 @@ func (s *Supervisor) connectAttempt(ctx context.Context, generation uint64) supe
745 sub, err := service.Subscribe()757 sub, err := service.Subscribe()
746 if err != nil {758 if err != nil {
747 _ = service.Close()759 _ = service.Close()
748- return supervisorAttempt{generation: generation, err: normalizeUnavailable(err)}760+ failure := normalizeUnavailable(err)
761+ s.logger.Warn("client_broker_subscribe_failed", "generation", generation, "code", brokerErrorCode(failure), "error", failure, "cause", errors.Unwrap(failure))
762+ return supervisorAttempt{generation: generation, err: failure}
749 }763 }
750 if supervisorNil(sub) {764 if supervisorNil(sub) {
751 // A service that reports no subscription cannot ever become Ready, so it765 // A service that reports no subscription cannot ever become Ready, so it
@@ -766,6 +780,7 @@ func (s *Supervisor) connectAttempt(ctx context.Context, generation uint64) supe
766 continue780 continue
767 case <-service.Done():781 case <-service.Done():
768 cause := normalizeUnavailable(service.Err())782 cause := normalizeUnavailable(service.Err())
783+ s.logger.Warn("client_broker_initial_publication_lost", "generation", generation, "code", brokerErrorCode(cause), "error", cause, "cause", errors.Unwrap(cause))
769 sub.Close()784 sub.Close()
770 _ = service.Close()785 _ = service.Close()
771 return supervisorAttempt{generation: generation, err: cause}786 return supervisorAttempt{generation: generation, err: cause}
@@ -1086,6 +1101,14 @@ func supervisorRetryable(err error) bool {
1086 // policy) still selects the non-retryable path; every untyped failure, including1101 // policy) still selects the non-retryable path; every untyped failure, including
1087 // a nil cause, becomes an unavailable BrokerError that preserves the original1102 // a nil cause, becomes an unavailable BrokerError that preserves the original
1088 // error as its local-only Cause.1103 // error as its local-only Cause.
1104+func brokerErrorCode(err error) string {
1105+ var typed ports.BrokerError
1106+ if errors.As(err, &typed) {
1107+ return typed.Code.String()
1108+ }
1109+ return "unknown"
1110+}
1111+
1089 func normalizeUnavailable(err error) error {1112 func normalizeUnavailable(err error) error {
1090 if err == nil {1113 if err == nil {
1091 return ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "broker connection lost"}1114 return ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "broker connection lost"}
@@ -1094,17 +1117,27 @@ func normalizeUnavailable(err error) error {
1094 if errors.As(err, &typed) {1117 if errors.As(err, &typed) {
1095 return err1118 return err
1096 }1119 }
1120+ var admission ports.BrokerAdmissionError
1121+ if errors.As(err, &admission) {
1122+ return err
1123+ }
1097 return ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "broker connection lost", Cause: err}1124 return ports.BrokerError{Code: ports.BrokerErrorUnavailable, Text: "broker connection lost", Cause: err}
1098 }1125 }
1099 1126
1100 // notify surfaces a connectivity failure through the optional notifier.1127 // notify surfaces a connectivity failure through the optional notifier.
1101 func (s *Supervisor) notifyLifecycle(kind LifecycleNoticeKind) {1128 func (s *Supervisor) notifyLifecycle(kind LifecycleNoticeKind) {
1129+ if s != nil {
1130+ s.logger.Debug("client_lifecycle_notice", "kind", kind)
1131+ }
1102 if s != nil && s.cfg.NotifyLifecycle != nil {1132 if s != nil && s.cfg.NotifyLifecycle != nil {
1103 s.cfg.NotifyLifecycle(LifecycleNotice{Kind: kind})1133 s.cfg.NotifyLifecycle(LifecycleNotice{Kind: kind})
1104 }1134 }
1105 }1135 }
1106 1136
1107 func (s *Supervisor) notify(err error) {1137 func (s *Supervisor) notify(err error) {
1138+ if err != nil {
1139+ s.logger.Warn("client_connectivity_failure", "state", s.State().Connectivity.String(), "code", brokerErrorCode(err), "error", err, "cause", errors.Unwrap(err))
1140+ }
1108 if s.cfg.Notify == nil || err == nil {1141 if s.cfg.Notify == nil || err == nil {
1109 return1142 return
1110 }1143 }
@@ -1130,6 +1163,9 @@ func (s *Supervisor) transition(event supervisorEvent) {
1130 s.state = reduceSupervisor(s.state, event)1163 s.state = reduceSupervisor(s.state, event)
1131 state := s.state1164 state := s.state
1132 s.mu.Unlock()1165 s.mu.Unlock()
1166+ if state.Presentation != previous || event.kind == supervisorBeginAttempt || event.kind == supervisorTransientFailure || event.kind == supervisorBrokerLoss {
1167+ s.logger.Debug("client_state_transition", "from", previous.String(), "to", state.Presentation.String(), "connectivity", state.Connectivity.String(), "attempt", state.Attempt, "generation", state.Generation, "error", event.err, "cause", errors.Unwrap(event.err))
1168+ }
1133 if state.Presentation != previous {1169 if state.Presentation != previous {
1134 s.publishPresentation(state.Presentation)1170 s.publishPresentation(state.Presentation)
1135 }1171 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 12

Tags 11

↑↓ moveenter openesc close