RotateForScope swaps the sink to a per-scope file derived from the sink's origin path. Scope semantics are caller-defined; for the keploy-agent debug log this is the test set ID, and the resulting path is " / / " — e.g. an origin of "/keploy-host/agent-debug.log" with scope "test-
(scope string)
| 541 | // operation. The method is concurrency-safe: parallel calls serialize |
| 542 | // through the sink mutex. |
| 543 | func (s *DebugFileSink) RotateForScope(scope string) error { |
| 544 | if s == nil { |
| 545 | return nil |
| 546 | } |
| 547 | s.mu.Lock() |
| 548 | if scope == s.currentScope { |
| 549 | s.mu.Unlock() |
| 550 | return nil |
| 551 | } |
| 552 | prevScope := s.currentScope |
| 553 | prevOrigin := s.originPath |
| 554 | s.mu.Unlock() |
| 555 | |
| 556 | // Compute the target path before reacquiring the mutex so we can |
| 557 | // fail with no side effects if the origin path is unset. |
| 558 | if prevOrigin == "" { |
| 559 | return fmt.Errorf("debug file sink: origin path unset; cannot derive scoped path") |
| 560 | } |
| 561 | dir := filepath.Dir(prevOrigin) |
| 562 | base := filepath.Base(prevOrigin) |
| 563 | // Compute the absolute origin: when rotating away from a scoped |
| 564 | // file back to the unscoped one, prevOrigin is the scoped path, |
| 565 | // which sits one directory deeper than the original. Re-anchor by |
| 566 | // chopping prevScope off prevOrigin's tail. |
| 567 | originDir := dir |
| 568 | if prevScope != "" && filepath.Base(dir) == prevScope { |
| 569 | originDir = filepath.Dir(dir) |
| 570 | } |
| 571 | target := filepath.Join(originDir, scope, base) |
| 572 | if scope == "" { |
| 573 | target = filepath.Join(originDir, base) |
| 574 | } |
| 575 | if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil { |
| 576 | return fmt.Errorf("debug file sink: mkdir %q: %w", filepath.Dir(target), err) |
| 577 | } |
| 578 | newFile, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644) |
| 579 | if err != nil { |
| 580 | return fmt.Errorf("debug file sink: open %q: %w", target, err) |
| 581 | } |
| 582 | |
| 583 | s.mu.Lock() |
| 584 | if err := s.buffered.Sync(); err != nil { |
| 585 | s.mu.Unlock() |
| 586 | _ = newFile.Close() |
| 587 | return fmt.Errorf("debug file sink: flush before swap: %w", err) |
| 588 | } |
| 589 | s.capped.swap(zapcore.AddSync(newFile)) |
| 590 | s.originPath = target |
| 591 | s.currentScope = scope |
| 592 | s.mu.Unlock() |
| 593 | return nil |
| 594 | } |
| 595 | |
| 596 | // CurrentScope reports the scope currently in effect (last value |
| 597 | // passed to RotateForScope), or "" if no rotation has happened. |