diff --git a/.golangci.yml b/.golangci.yml index 9518d78b..d7e9038a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -40,6 +40,7 @@ linters: - intrange - loggercheck - misspell + - modernize - nilerr - noctx - nolintlint diff --git a/internal/bft/heartbeatmonitor_test.go b/internal/bft/heartbeatmonitor_test.go index 0013aaa7..d6e83b18 100644 --- a/internal/bft/heartbeatmonitor_test.go +++ b/internal/bft/heartbeatmonitor_test.go @@ -58,8 +58,8 @@ func TestHeartbeatMonitorLeader(t *testing.T) { vs.Store(bft.ViewSequence{ViewActive: true}) hm := bft.NewHeartbeatMonitor(scheduler, log, types.DefaultConfig.LeaderHeartbeatTimeout, types.DefaultConfig.LeaderHeartbeatCount, comm, 4, handler, vs, types.DefaultConfig.NumOfTicksBehindBeforeSyncing) - var heartBeatsSent uint32 - var heartBeatsSentUntilViewBecomesInactive uint32 + var heartBeatsSent atomic.Uint32 + var heartBeatsSentUntilViewBecomesInactive atomic.Uint32 var toWG1 sync.WaitGroup toWG1.Add(10) @@ -68,17 +68,17 @@ func TestHeartbeatMonitorLeader(t *testing.T) { comm.On("BroadcastConsensus", mock.AnythingOfType("*smartbftprotos.Message")).Run(func(args mock.Arguments) { msg := args[0].(*smartbftprotos.Message) view := msg.GetHeartBeat().GetView() - atomic.AddUint32(&heartBeatsSent, 1) + heartBeatsSent.Add(1) if uint64(10) == view { toWG1.Done() } else if uint64(20) == view { toWG2.Done() - totalHBsSent := atomic.LoadUint32(&heartBeatsSent) + totalHBsSent := heartBeatsSent.Load() if totalHBsSent == 20 { // View is stopped vs.Store(bft.ViewSequence{ViewActive: false, ProposalSeq: msg.GetHeartBeat().GetSeq()}) // Record HB number we sent so far - atomic.StoreUint32(&heartBeatsSentUntilViewBecomesInactive, totalHBsSent) + heartBeatsSentUntilViewBecomesInactive.Store(totalHBsSent) } } }).Return() @@ -94,7 +94,7 @@ func TestHeartbeatMonitorLeader(t *testing.T) { clock.advanceTime(10, scheduler) // Ensure we don't advance heartbeats any longer when view is inactive - require.Equal(t, atomic.LoadUint32(&heartBeatsSentUntilViewBecomesInactive), atomic.LoadUint32(&heartBeatsSent)) + require.Equal(t, heartBeatsSentUntilViewBecomesInactive.Load(), heartBeatsSent.Load()) hm.Close() } diff --git a/internal/bft/util_test.go b/internal/bft/util_test.go index 392da1cb..3eb1ae8b 100644 --- a/internal/bft/util_test.go +++ b/internal/bft/util_test.go @@ -159,9 +159,7 @@ func TestViewDataInFlightWhileStored(t *testing.T) { done := make(chan struct{}) var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { for i := 0; ; i++ { select { case <-done: @@ -173,7 +171,7 @@ func TestViewDataInFlightWhileStored(t *testing.T) { vc.InFlight.StorePrepares(0, 0) } } - }() + }) inconsistent := 0 for range 100000 { diff --git a/internal/bft/view_test.go b/internal/bft/view_test.go index 4092bee6..e7f6e02b 100644 --- a/internal/bft/view_test.go +++ b/internal/bft/view_test.go @@ -1483,21 +1483,21 @@ func newNetwork(t *testing.T, size int) testedNetwork { } type testedView struct { - offline uint32 + offline atomic.Uint32 deciderWG sync.WaitGroup *bft.View } func (tv *testedView) setOffline() { - atomic.StoreUint32(&tv.offline, 1) + tv.offline.Store(1) } func (tv *testedView) setOnline() { - atomic.StoreUint32(&tv.offline, 0) + tv.offline.Store(0) } func (tv *testedView) HandleMessage(sender uint64, m *protos.Message) { - if atomic.LoadUint32(&tv.offline) == uint32(1) { + if tv.offline.Load() == uint32(1) { return } tv.View.HandleMessage(sender, m) diff --git a/pkg/consensus/consensus.go b/pkg/consensus/consensus.go index fe01e589..e3df408a 100644 --- a/pkg/consensus/consensus.go +++ b/pkg/consensus/consensus.go @@ -64,7 +64,7 @@ type Consensus struct { consensusLock sync.RWMutex reconfigChan chan types.Reconfig - running uint64 + running atomic.Uint64 } func (c *Consensus) Complain(viewNum uint64, stopView bool) { @@ -99,7 +99,7 @@ func (c *Consensus) Sync() types.SyncResponse { // GetLeaderID returns the current leader ID or zero if Consensus is not running func (c *Consensus) GetLeaderID() uint64 { - if atomic.LoadUint64(&c.running) == 0 { + if c.running.Load() == 0 { return 0 } return c.controller.GetLeaderID() @@ -159,7 +159,7 @@ func (c *Consensus) Start() error { c.startComponents(view, seq, dec, c.Verifier.VerificationSequence(), true) - atomic.StoreUint64(&c.running, 1) + c.running.Store(1) return nil } @@ -167,7 +167,7 @@ func (c *Consensus) Start() error { func (c *Consensus) run() { defer func() { c.Logger.Infof("Exiting consensus run; ID: %d", c.Config.SelfID) - atomic.StoreUint64(&c.running, 0) + c.running.Store(0) c.Stop() }() diff --git a/test/basic_test.go b/test/basic_test.go index 97ab51a7..f7ead75f 100644 --- a/test/basic_test.go +++ b/test/basic_test.go @@ -950,12 +950,12 @@ func TestCatchingUpWithSyncAutonomous(t *testing.T) { nodes = append(nodes, n) } - var detectedSequenceGap uint32 + var detectedSequenceGap atomic.Uint32 baseLogger := nodes[3].Consensus.Logger.(*zap.SugaredLogger).Desugar() nodes[3].Consensus.Logger = baseLogger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { if strings.Contains(entry.Message, "Leader's sequence is 10 and ours is 1") { - atomic.StoreUint32(&detectedSequenceGap, 1) + detectedSequenceGap.Store(1) } return nil })).Sugar() @@ -1011,7 +1011,7 @@ func TestCatchingUpWithSyncAutonomous(t *testing.T) { } close(done) - require.Equal(t, uint32(0), atomic.LoadUint32(&detectedSequenceGap)) + require.Equal(t, uint32(0), detectedSequenceGap.Load()) } func TestSyncSameHeightPreservesDecisionsInView(t *testing.T) { @@ -1036,12 +1036,12 @@ func TestSyncSameHeightPreservesDecisionsInView(t *testing.T) { // Hook node 4's logger to detect: // 1. When sync is processed (so we know it's safe to submit more proposals) // 2. The bug symptom: "Expected decisions in view" validation failure - var syncProcessed uint32 + var syncProcessed atomic.Uint32 bugDetected := make(chan struct{}, 1) baseLogger := nodes[3].Consensus.Logger.(*zap.SugaredLogger).Desugar() nodes[3].Consensus.Logger = baseLogger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { if strings.Contains(entry.Message, "get msg from syncChan") { - atomic.StoreUint32(&syncProcessed, 1) + syncProcessed.Store(1) } if strings.Contains(entry.Message, "Expected decisions in view") { select { @@ -1089,7 +1089,7 @@ func TestSyncSameHeightPreservesDecisionsInView(t *testing.T) { // Wait for the sync to be processed by the controller's run loop. require.Eventually(t, func() bool { - return atomic.LoadUint32(&syncProcessed) == 1 + return syncProcessed.Load() == 1 }, 30*time.Second, 50*time.Millisecond, "Node 4 should process sync triggered by fake heartbeat") @@ -1334,9 +1334,9 @@ func TestLeaderCatchUpWithoutSync(t *testing.T) { restoredWG := sync.WaitGroup{} restoredWG.Add(1) - var blockCommits uint32 + var blockCommits atomic.Uint32 nodes[0].LoseMessages(func(msg *smartbftprotos.Message) bool { - return msg.GetCommit() != nil && atomic.LoadUint32(&blockCommits) == 1 + return msg.GetCommit() != nil && blockCommits.Load() == 1 }) baseLogger := nodes[0].logger.Desugar() nodes[0].logger = baseLogger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { @@ -1349,7 +1349,7 @@ func TestLeaderCatchUpWithoutSync(t *testing.T) { return nil })).Sugar() nodes[0].Setup() - atomic.StoreUint32(&blockCommits, 1) + blockCommits.Store(1) startNodes(nodes, network) @@ -1357,7 +1357,7 @@ func TestLeaderCatchUpWithoutSync(t *testing.T) { restartWG.Wait() nodes[0].RestartSync(false) - atomic.StoreUint32(&blockCommits, 0) + blockCommits.Store(0) restoredWG.Wait() data := make([]*AppRecord, 0) @@ -1405,9 +1405,9 @@ func TestLeaderProposeAfterRestartWithoutSync(t *testing.T) { contViewWG := sync.WaitGroup{} contViewWG.Add(2) - var blockCommits uint32 + var blockCommits atomic.Uint32 nodes[0].LoseMessages(func(msg *smartbftprotos.Message) bool { - return msg.GetCommit() != nil && atomic.LoadUint32(&blockCommits) == 1 + return msg.GetCommit() != nil && blockCommits.Load() == 1 }) baseLogger := nodes[0].logger.Desugar() nodes[0].logger = baseLogger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { @@ -1432,7 +1432,7 @@ func TestLeaderProposeAfterRestartWithoutSync(t *testing.T) { return nil })).Sugar() nodes[0].Setup() - atomic.StoreUint32(&blockCommits, 1) + blockCommits.Store(1) startNodes(nodes, network) @@ -1440,7 +1440,7 @@ func TestLeaderProposeAfterRestartWithoutSync(t *testing.T) { restartWG.Wait() nodes[0].RestartSync(false) - atomic.StoreUint32(&blockCommits, 0) + blockCommits.Store(0) restoredWG.Wait() nodes[0].Submit(Request{ID: "2", ClientID: "alice"}) @@ -1499,13 +1499,13 @@ func TestGradualStart(t *testing.T) { // add a second node n1 := newNode(uint64(2), network, t.Name(), testDir, true, 1) - atomic.AddUint64(&n1.verificationSeq, 1) + n1.verificationSeq.Add(1) if err := n1.Consensus.Start(); err != nil { n1.logger.Panicf("Consensus returned an error : %v", err) } - atomic.AddUint64(&n0.verificationSeq, 1) + n0.verificationSeq.And(1) n0.Restart() network.StartServe() @@ -1531,14 +1531,14 @@ func TestGradualStart(t *testing.T) { // add a third node n2 := newNode(uint64(3), network, t.Name(), testDir, true, 1) - atomic.AddUint64(&n2.verificationSeq, 2) + n2.verificationSeq.Add(2) if err := n2.Consensus.Start(); err != nil { n2.logger.Panicf("Consensus returned an error : %v", err) } - atomic.AddUint64(&n1.verificationSeq, 1) - atomic.AddUint64(&n0.verificationSeq, 1) + n1.verificationSeq.Add(1) + n0.verificationSeq.Add(1) n0.Restart() n1.Restart() @@ -1775,16 +1775,16 @@ func TestMigrateToBlacklistAndBackAgain(t *testing.T) { //nolint:tparallel // su nodes = append(nodes, n) } - var leaderRotationDisabled uint64 - var boundCommitSignatures uint64 + var leaderRotationDisabled atomic.Uint64 + var boundCommitSignatures atomic.Uint64 setupLogger := func(node *App) { node.logger = node.logger.Desugar().WithOptions(zap.Hooks(func(entry zapcore.Entry) error { if strings.Contains(entry.Message, "Bound 3 commit signatures to proposal") { - atomic.AddUint64(&boundCommitSignatures, 1) + boundCommitSignatures.Add(1) } if strings.Contains(entry.Message, "Leader rotation is disabled, will not bind signatures to proposals") { - atomic.AddUint64(&leaderRotationDisabled, 1) + leaderRotationDisabled.Add(1) } return nil })).Sugar() @@ -1804,8 +1804,8 @@ func TestMigrateToBlacklistAndBackAgain(t *testing.T) { //nolint:tparallel // su <-nodes[i].Delivered } - require.Equal(t, uint64(1), atomic.LoadUint64(&leaderRotationDisabled)) - require.Equal(t, uint64(0), atomic.LoadUint64(&boundCommitSignatures)) + require.Equal(t, uint64(1), leaderRotationDisabled.Load()) + require.Equal(t, uint64(0), boundCommitSignatures.Load()) nodes[0].Submit(Request{ID: "2", ClientID: "alice"}) @@ -1813,8 +1813,8 @@ func TestMigrateToBlacklistAndBackAgain(t *testing.T) { //nolint:tparallel // su <-nodes[i].Delivered } - require.Equal(t, uint64(2), atomic.LoadUint64(&leaderRotationDisabled)) - require.Equal(t, uint64(0), atomic.LoadUint64(&boundCommitSignatures)) + require.Equal(t, uint64(2), leaderRotationDisabled.Load()) + require.Equal(t, uint64(0), boundCommitSignatures.Load()) }) t.Run("Activate leader rotation", func(t *testing.T) { @@ -1834,8 +1834,8 @@ func TestMigrateToBlacklistAndBackAgain(t *testing.T) { //nolint:tparallel // su <-nodes[i].Delivered } - require.Equal(t, uint64(2), atomic.LoadUint64(&leaderRotationDisabled)) - require.Equal(t, uint64(1), atomic.LoadUint64(&boundCommitSignatures)) + require.Equal(t, uint64(2), leaderRotationDisabled.Load()) + require.Equal(t, uint64(1), boundCommitSignatures.Load()) nodes[0].Submit(Request{ID: "4", ClientID: "alice"}) @@ -1843,8 +1843,8 @@ func TestMigrateToBlacklistAndBackAgain(t *testing.T) { //nolint:tparallel // su <-nodes[i].Delivered } - require.Equal(t, uint64(2), atomic.LoadUint64(&leaderRotationDisabled)) - require.Equal(t, uint64(2), atomic.LoadUint64(&boundCommitSignatures)) + require.Equal(t, uint64(2), leaderRotationDisabled.Load()) + require.Equal(t, uint64(2), boundCommitSignatures.Load()) }) t.Run("Deactivate leader rotation", func(t *testing.T) { @@ -1864,8 +1864,8 @@ func TestMigrateToBlacklistAndBackAgain(t *testing.T) { //nolint:tparallel // su <-nodes[i].Delivered } - require.Equal(t, uint64(3), atomic.LoadUint64(&leaderRotationDisabled)) - require.Equal(t, uint64(2), atomic.LoadUint64(&boundCommitSignatures)) + require.Equal(t, uint64(3), leaderRotationDisabled.Load()) + require.Equal(t, uint64(2), boundCommitSignatures.Load()) nodes[0].Submit(Request{ID: "6", ClientID: "alice"}) @@ -1873,8 +1873,8 @@ func TestMigrateToBlacklistAndBackAgain(t *testing.T) { //nolint:tparallel // su <-nodes[i].Delivered } - require.Equal(t, uint64(4), atomic.LoadUint64(&leaderRotationDisabled)) - require.Equal(t, uint64(2), atomic.LoadUint64(&boundCommitSignatures)) + require.Equal(t, uint64(4), leaderRotationDisabled.Load()) + require.Equal(t, uint64(2), boundCommitSignatures.Load()) }) } @@ -1900,8 +1900,8 @@ func TestNodeInFlightFails(t *testing.T) { var inFlightCommit sync.WaitGroup inFlightCommit.Add(2) - var blockCommits uint32 - var blockCommitsForLastNode uint32 + var blockCommits atomic.Uint32 + var blockCommitsForLastNode atomic.Uint32 for i := 1; i <= numberOfNodes; i++ { n := newNode(uint64(i), network, t.Name(), testDir, false, 0) @@ -1917,7 +1917,7 @@ func TestNodeInFlightFails(t *testing.T) { id := n.ID n.Consensus.Logger = baseLogger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { - if strings.Contains(entry.Message, "collected 2 prepares") && atomic.LoadUint32(&blockCommits) == 1 { + if strings.Contains(entry.Message, "collected 2 prepares") && blockCommits.Load() == 1 { collectPreparesWG.Done() } @@ -1944,19 +1944,19 @@ func TestNodeInFlightFails(t *testing.T) { if msg.GetCommit() == nil { return false } - return atomic.LoadUint32(&blockCommits) == 1 + return blockCommits.Load() == 1 }) } // However the last node never receives any commits. nodes[len(nodes)-1].LoseMessages(func(msg *smartbftprotos.Message) bool { - return msg.GetCommit() != nil && atomic.LoadUint32(&blockCommitsForLastNode) == 1 + return msg.GetCommit() != nil && blockCommitsForLastNode.Load() == 1 }) startNodes(nodes, network) - atomic.StoreUint32(&blockCommits, 1) - atomic.StoreUint32(&blockCommitsForLastNode, 1) + blockCommits.Store(1) + blockCommitsForLastNode.Store(1) nodes[0].Submit(Request{ID: "first request", ClientID: "alice"}) @@ -1982,7 +1982,7 @@ func TestNodeInFlightFails(t *testing.T) { require.False(t, committedTooEarly.Load(), "should not have committed because first commit message should have been lost") // Re-enable commit messages to flow to all nodes but the last node - atomic.StoreUint32(&blockCommits, 0) + blockCommits.Store(0) // Disconnect leader to force view change nodes[0].Disconnect() @@ -2010,7 +2010,7 @@ func TestNodeInFlightFails(t *testing.T) { // Wait for in-flight view of view changer to time out timeoutExpiredWG.Wait() // Re-enable commits to flow to the last node - atomic.StoreUint32(&blockCommitsForLastNode, 0) + blockCommitsForLastNode.Store(0) // Ensure the last node failed changing the view failedViewChange.Wait() // However, it eventually syncs successfully and delivers the proposal @@ -2265,7 +2265,7 @@ func TestNodeInFlightThenViewChange(t *testing.T) { numberOfNodes := 4 nodes := make([]*App, 0) - var blockCommits uint32 + var blockCommits atomic.Uint32 for i := 1; i <= numberOfNodes; i++ { n := newNode(uint64(i), network, t.Name(), testDir, false, 0) @@ -2273,13 +2273,13 @@ func TestNodeInFlightThenViewChange(t *testing.T) { if msg.GetCommit() == nil { return false } - return atomic.LoadUint32(&blockCommits) == 1 + return blockCommits.Load() == 1 }) nodes = append(nodes, n) } startNodes(nodes, network) - atomic.StoreUint32(&blockCommits, 1) + blockCommits.Store(1) nodes[0].Submit(Request{ID: "first request", ClientID: "alice"}) @@ -2301,7 +2301,7 @@ func TestNodeInFlightThenViewChange(t *testing.T) { wg.Wait() require.False(t, committedTooEarly.Load(), "should not have committed because first commit message should have been lost") - atomic.StoreUint32(&blockCommits, 0) + blockCommits.Store(0) nodes[len(nodes)-1].Disconnect() // Last node in partition @@ -2352,7 +2352,7 @@ func TestNodeCommitTheRestPrepareAndCommittedNodeCrashesThenRecovers(t *testing. numberOfNodes := 4 nodes := make([]*App, 0) - var blockCommits uint32 + var blockCommits atomic.Uint32 for i := 1; i <= numberOfNodes; i++ { n := newNode(uint64(i), network, t.Name(), testDir, false, 0) @@ -2362,7 +2362,7 @@ func TestNodeCommitTheRestPrepareAndCommittedNodeCrashesThenRecovers(t *testing. if msg.GetCommit() == nil { return false } - return atomic.LoadUint32(&blockCommits) == 1 + return blockCommits.Load() == 1 }) } @@ -2370,7 +2370,7 @@ func TestNodeCommitTheRestPrepareAndCommittedNodeCrashesThenRecovers(t *testing. } startNodes(nodes, network) - atomic.StoreUint32(&blockCommits, 1) + blockCommits.Store(1) nodes[0].Submit(Request{ID: "first request", ClientID: "alice"}) @@ -2384,7 +2384,7 @@ func TestNodeCommitTheRestPrepareAndCommittedNodeCrashesThenRecovers(t *testing. nodes[0].Disconnect() // Unblock commits - atomic.StoreUint32(&blockCommits, 0) + blockCommits.Store(0) // Submit second request to all nodes to trigger view change nodes[1].Submit(Request{ID: "second request", ClientID: "alice"}) @@ -2434,7 +2434,7 @@ func TestNodePreparesTheRestInPartitionThenPartitionHeals(t *testing.T) { numberOfNodes := 4 nodes := make([]*App, 0) - var blockPrepares uint32 + var blockPrepares atomic.Uint32 for i := 1; i <= numberOfNodes; i++ { n := newNode(uint64(i), network, t.Name(), testDir, false, 0) @@ -2444,7 +2444,7 @@ func TestNodePreparesTheRestInPartitionThenPartitionHeals(t *testing.T) { if msg.GetPrepare() == nil { return false } - return atomic.LoadUint32(&blockPrepares) == 1 + return blockPrepares.Load() == 1 }) } @@ -2458,7 +2458,7 @@ func TestNodePreparesTheRestInPartitionThenPartitionHeals(t *testing.T) { baseLogger := nodes[0].logger.Desugar() nodes[0].logger = baseLogger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error { // Only make not of log appends if we are interested in blocking prepares - if atomic.LoadUint32(&blockPrepares) == 0 { + if blockPrepares.Load() == 0 { return nil } if strings.Contains(entry.Message, "LogRecord appended successfully") { @@ -2470,7 +2470,7 @@ func TestNodePreparesTheRestInPartitionThenPartitionHeals(t *testing.T) { startNodes(nodes, network) - atomic.StoreUint32(&blockPrepares, 1) + blockPrepares.Store(1) nodes[0].Submit(Request{ID: "first request", ClientID: "alice"}) @@ -2480,7 +2480,7 @@ func TestNodePreparesTheRestInPartitionThenPartitionHeals(t *testing.T) { // Disconnect node 0 nodes[0].Disconnect() - atomic.StoreUint32(&blockPrepares, 0) + blockPrepares.Store(0) // Submit second request to all nodes to trigger view change nodes[1].Submit(Request{ID: "second request", ClientID: "alice"}) diff --git a/test/test_app.go b/test/test_app.go index b495954b..1271f46f 100644 --- a/test/test_app.go +++ b/test/test_app.go @@ -62,7 +62,7 @@ type App struct { logger *zap.SugaredLogger metricsProvider metrics.Provider lastRecord lastRecord - verificationSeq uint64 + verificationSeq atomic.Uint64 clientsToRevoke []string // clients whose requests become invalid once a reconfig is delivered revokedClients sync.Map // revokeDuringPrune delays revoking the clients after a reconfig is delivered until the first request @@ -261,7 +261,7 @@ func (a *App) VerifySignature(_ types.Signature) error { // VerificationSequence returns the current verification sequence func (a *App) VerificationSequence() uint64 { - return atomic.LoadUint64(&a.verificationSeq) + return a.verificationSeq.Load() } // Sign signs on the given value @@ -281,7 +281,7 @@ func (a *App) SignProposal(_ types.Proposal, aux []byte) *types.Signature { // AssembleProposal assembles a new proposal from the given requests func (a *App) AssembleProposal(metadata []byte, requests [][]byte) types.Proposal { return types.Proposal{ - VerificationSequence: int64(atomic.LoadUint64(&a.verificationSeq)), + VerificationSequence: int64(a.verificationSeq.Load()), Payload: batch{Requests: requests}.toBytes(), Metadata: metadata, } @@ -349,7 +349,7 @@ func (a *App) revokeClients() { for _, client := range a.clientsToRevoke { a.revokedClients.Store(client, struct{}{}) } - atomic.AddUint64(&a.verificationSeq, 1) + a.verificationSeq.Add(1) } type committedBatches struct { diff --git a/test/view_split_test.go b/test/view_split_test.go index fb490b6a..226093a7 100644 --- a/test/view_split_test.go +++ b/test/view_split_test.go @@ -88,11 +88,11 @@ func (vt *viewTracker) hook(id uint64) func(zapcore.Entry) error { // after returns the remainder of msg once marker has been found, trimmed of anything that is not // part of the number that follows it. func after(msg, marker string) string { - i := strings.Index(msg, marker) - if i < 0 { + _, after, ok := strings.Cut(msg, marker) + if !ok { return "" } - rest := strings.TrimSpace(msg[i+len(marker):]) + rest := strings.TrimSpace(after) end := 0 for end < len(rest) && rest[end] >= '0' && rest[end] <= '9' { end++