Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.

Commit c3beec2

Browse files
committed
log: add child process tracing
Add 'ChildProcess()' to 'log.TraceLogger()', with a corresponding implementation in the Trace2 logger. 'ChildProcess()' works somewhat similarly to 'Region()', writing a "child_start" log during its main execution and returning two functions to write "child_ready" and "child_exit" logs. The main difference from 'Region()', though, is that the returned functions are meant to be invoked directly at the appropriate times in the process execution (rather than deferred). Add the child process logging to 'CommandExecutor.Run()'. Signed-off-by: Victoria Dye <[email protected]>
1 parent 79a7633 commit c3beec2

File tree

4 files changed

+61
-2
lines changed

4 files changed

+61
-2
lines changed

internal/cmd/command.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,15 @@ func (c *commandExecutor) applyOptions(ctx context.Context, cmd *exec.Cmd, setti
5858
}
5959

6060
func (c *commandExecutor) runCmd(ctx context.Context, cmd *exec.Cmd) (int, error) {
61+
childReady, childExit := c.logger.ChildProcess(ctx, cmd)
6162
err := cmd.Start()
63+
childReady(err)
6264
if err != nil {
6365
return -1, c.logger.Errorf(ctx, "command failed to start: %w", err)
6466
}
6567

6668
err = cmd.Wait()
69+
childExit()
6770
_, isExitError := err.(*exec.ExitError)
6871

6972
// If the command succeeded, or ran to completion but returned a nonzero

internal/log/logger.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"os/exec"
78
"runtime/debug"
89
)
910

@@ -13,6 +14,7 @@ type loggedError error
1314

1415
type TraceLogger interface {
1516
Region(ctx context.Context, category string, label string) (context.Context, func())
17+
ChildProcess(ctx context.Context, cmd *exec.Cmd) (func(error), func())
1618
LogCommand(ctx context.Context, commandName string) context.Context
1719
Error(ctx context.Context, err error) error
1820
Errorf(ctx context.Context, format string, a ...any) error

internal/log/trace2.go

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"os/exec"
78
"path"
89
"path/filepath"
910
"runtime"
1011
"strconv"
12+
"sync/atomic"
1113
"time"
1214

1315
"github.com/google/uuid"
@@ -41,7 +43,8 @@ type trace2Region struct {
4143
}
4244

4345
type Trace2 struct {
44-
logger *zap.Logger
46+
logger *zap.Logger
47+
lastChildId int32
4548
}
4649

4750
func getTrace2OutputPaths(envKey string) []string {
@@ -104,7 +107,8 @@ func createTrace2ZapLogger() *zap.Logger {
104107

105108
func NewTrace2() traceLoggerInternal {
106109
return &Trace2{
107-
logger: createTrace2ZapLogger(),
110+
logger: createTrace2ZapLogger(),
111+
lastChildId: -1,
108112
}
109113
}
110114

@@ -229,6 +233,47 @@ func (t *Trace2) Region(ctx context.Context, category string, label string) (con
229233
}
230234
}
231235

236+
func (t *Trace2) ChildProcess(ctx context.Context, cmd *exec.Cmd) (func(error), func()) {
237+
var startTime time.Time
238+
_, sharedFields := t.sharedFields(ctx)
239+
240+
// Get the child id by atomically incrementing the lastChildId
241+
childId := atomic.AddInt32(&t.lastChildId, 1)
242+
t.logger.Debug("child_start", sharedFields.with(
243+
zap.Int32("child_id", childId),
244+
zap.String("child_class", "?"),
245+
zap.Bool("use_shell", false),
246+
zap.Strings("argv", cmd.Args),
247+
)...)
248+
249+
childReady := func(execError error) {
250+
ready := zap.String("ready", "ready")
251+
if execError != nil {
252+
ready = zap.String("ready", "error")
253+
}
254+
t.logger.Debug("child_ready", sharedFields.with(
255+
zap.Int32("child_id", childId),
256+
zap.Int("pid", cmd.Process.Pid),
257+
ready,
258+
zap.Strings("argv", cmd.Args),
259+
)...)
260+
}
261+
262+
childExit := func() {
263+
t.logger.Debug("child_exit", sharedFields.with(
264+
zap.Int32("child_id", childId),
265+
zap.Int("pid", cmd.ProcessState.Pid()),
266+
zap.Int("code", cmd.ProcessState.ExitCode()),
267+
zap.Duration("t_rel", time.Since(startTime)),
268+
)...)
269+
}
270+
271+
// Approximate the process runtime by starting the timer now
272+
startTime = time.Now()
273+
274+
return childReady, childExit
275+
}
276+
232277
func (t *Trace2) LogCommand(ctx context.Context, commandName string) context.Context {
233278
ctx, sharedFields := t.sharedFields(ctx)
234279

internal/testhelpers/mocks.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package testhelpers
33
import (
44
"context"
55
"fmt"
6+
"os/exec"
67
"os/user"
78
"runtime"
89

@@ -62,6 +63,14 @@ func (l *MockTraceLogger) Region(ctx context.Context, category string, label str
6263
return mockWithDefault(fnArgs, 0, ctx), mockWithDefault(fnArgs, 1, func() {})
6364
}
6465

66+
func (l *MockTraceLogger) ChildProcess(ctx context.Context, cmd *exec.Cmd) (func(error), func()) {
67+
fnArgs := mock.Arguments{}
68+
if methodIsMocked(&l.Mock) {
69+
fnArgs = l.Called(ctx, cmd)
70+
}
71+
return mockWithDefault(fnArgs, 0, func(error) {}), mockWithDefault(fnArgs, 1, func() {})
72+
}
73+
6574
func (l *MockTraceLogger) LogCommand(ctx context.Context, commandName string) context.Context {
6675
fnArgs := mock.Arguments{}
6776
if methodIsMocked(&l.Mock) {

0 commit comments

Comments
 (0)