Compare commits

...

2 Commits

Author SHA1 Message Date
mixeme 2b0cdb57f4 runner/logfile: write duration line to log header (T2.3)
Pass durationMS to writeRunLog and include it as 'duration' in the
log header for persistence.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-06-24 08:11:05 +03:00
mixeme 42d73b9eb0 domain/record, runner: add DurationMS to RunRecord (T2.1, T2.2)
Add DurationMS int64 field to RunRecord and measure wall-clock
start→finish in RunJob; StartOnly jobs record 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 08:09:59 +03:00
3 changed files with 26 additions and 20 deletions
+1
View File
@@ -12,4 +12,5 @@ type RunRecord struct {
Detail string `yaml:"detail"` Detail string `yaml:"detail"`
LogFile string `yaml:"log_file,omitempty"` LogFile string `yaml:"log_file,omitempty"`
Output string `yaml:"output,omitempty"` Output string `yaml:"output,omitempty"`
DurationMS int64 `yaml:"duration_ms,omitempty"`
} }
+3 -3
View File
@@ -11,7 +11,7 @@ import (
"gitea.mixdep.ru/mix/gosentry/src/domain" "gitea.mixdep.ru/mix/gosentry/src/domain"
) )
func writeRunLog(logsDir string, job domain.Job, trigger string, state string, detail string, output string, started time.Time) string { func writeRunLog(logsDir string, job domain.Job, trigger string, state string, detail string, output string, durationMS int64, started time.Time) string {
if strings.TrimSpace(logsDir) == "" { if strings.TrimSpace(logsDir) == "" {
return "" return ""
} }
@@ -23,8 +23,8 @@ func writeRunLog(logsDir string, job domain.Job, trigger string, state string, d
// avoid characters that are invalid on Windows or awkward on shells. // avoid characters that are invalid on Windows or awkward on shells.
fileName := started.Format("20060102-150405") + "_" + sanitizeFileName(job.Name) + ".log" fileName := started.Format("20060102-150405") + "_" + sanitizeFileName(job.Name) + ".log"
path := filepath.Join(logsDir, fileName) path := filepath.Join(logsDir, fileName)
content := fmt.Sprintf("time: %s\njob_id: %d\njob_name: %s\ntrigger: %s\nstate: %s\ndetail: %s\ncommand: %s\narguments: %s\nstart_only: %t\n\n%s\n", content := fmt.Sprintf("time: %s\njob_id: %d\njob_name: %s\ntrigger: %s\nstate: %s\ndetail: %s\nduration: %d\ncommand: %s\narguments: %s\nstart_only: %t\n\n%s\n",
started.Format("2006-01-02 15:04:05"), job.ID, job.Name, trigger, state, detail, job.Command, logArguments(job.Arguments), job.StartOnly, output) started.Format("2006-01-02 15:04:05"), job.ID, job.Name, trigger, state, detail, durationMS, job.Command, logArguments(job.Arguments), job.StartOnly, output)
if err := os.WriteFile(path, []byte(content), 0o644); err != nil { if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
return "" return ""
} }
+6 -1
View File
@@ -27,9 +27,12 @@ func RunJob(ctx context.Context, job *domain.Job, trigger string, logsDir string
var output string var output string
var state string var state string
var detail string var detail string
var durationMS int64
if job.StartOnly { if job.StartOnly {
invocation := jobInvocation(context.Background(), *job) invocation := jobInvocation(context.Background(), *job)
state, detail, output = startJobOnly(invocation, *job, started) state, detail, output = startJobOnly(invocation, *job, started)
// StartOnly jobs don't wait for process exit, so no meaningful duration.
durationMS = 0
} else { } else {
var stdoutBuf strings.Builder var stdoutBuf strings.Builder
var stderrBuf strings.Builder var stderrBuf strings.Builder
@@ -44,13 +47,14 @@ func RunJob(ctx context.Context, job *domain.Job, trigger string, logsDir string
err := command.Run() err := command.Run()
duration := time.Since(started).Round(time.Millisecond) duration := time.Since(started).Round(time.Millisecond)
durationMS = duration.Milliseconds()
output = formatOutput(stdoutBuf.String(), stderrBuf.String()) output = formatOutput(stdoutBuf.String(), stderrBuf.String())
state, detail = runStateDetail(err, runCtx.Err(), duration) state, detail = runStateDetail(err, runCtx.Err(), duration)
} }
now := time.Now() now := time.Now()
timestamp := now.Format("2006-01-02 15:04:05") timestamp := now.Format("2006-01-02 15:04:05")
logFile := writeRunLog(logsDir, *job, trigger, state, detail, output, now) logFile := writeRunLog(logsDir, *job, trigger, state, detail, output, durationMS, now)
// The runner is now pure with respect to the job: it returns a RunRecord and // The runner is now pure with respect to the job: it returns a RunRecord and
// lets the caller fold that record into the job's JobRuntime. Run state no // lets the caller fold that record into the job's JobRuntime. Run state no
@@ -64,6 +68,7 @@ func RunJob(ctx context.Context, job *domain.Job, trigger string, logsDir string
Detail: detail, Detail: detail,
LogFile: logFile, LogFile: logFile,
Output: output, Output: output,
DurationMS: durationMS,
} }
} }