Files
act_runner/DEVELOPMENT.md
Lunny Xiao 470969dc74 chore(deps): update actionslib to v1.4.1 (#1313)
actionslib v1.4.1 no longer logs through logrus, so the workflow model no longer needs the logrus bridge. The bridge stays for docker/cli and moby/go-archive, which still log through logrus's global logger; the comment now says so.

actionslib now logs through slog's global logger, which the runner did not capture. A new slog bridge forwards it to the process-wide logger, so its messages keep the runner's level, format and secret masking.

Assisted-by: Codet
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/runner/pulls/1313
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2026-10-14 12:09:48 +00:00

3.2 KiB

Development

Job log line format

Gitea stores one log row per line and its web UI decodes the payload, so getting the encoding wrong never fails a test here, it only shows up in the browser.

A row cannot contain a real newline. FormatLog rewrites \n to a literal backslash-n and truncates at 64 KiB on a byte boundary.

The payload of a line starting with a recognised prefix is decoded, with the escape set depending on the prefix:

prefix decodes
##[error] ##[warning] ##[notice] ##[debug] ##[group] ##[endgroup] ##[add-matcher] %25 %0D %0A %3B %5D
::error:: ::warning:: ::notice:: ::debug:: (with or without key=value properties), ::group:: ::endgroup:: ::add-matcher:: %25 %0D %0A
##[command] [command], or no recognised prefix nothing

Rules

  • Emitting a command line? Escape the payload with runner.EscapeCommandData. One escaper covers both forms: it escapes % first, so a literal %3B becomes %253B that the extra ##[…] rules cannot match, and a raw ; or ] is never decoded. It is also what makes multi-line work, \n becomes %0A and the UI turns it back into a line break.
  • Forwarding a command from step output? Leave the payload alone, it arrived escaped and is decoded once. Decoding here double-decodes and destroys multi-line.
  • No prefix? Do not escape, and split multi-line values into one row each.
  • Interpolating a secret? Masking runs after escaping, so AppendSecretMasker registers the encoded forms too.
  • Command properties also escape %3A and %2C, which the UI never decodes, so the reporter decodes exactly those two when folding a location into an annotation.

Logging

Log through act/common/logging; the linter rejects importing log/slog or logrus elsewhere. A job's logger comes from common.Logger(ctx); code with no job uses the package functions (logging.Infof, logging.With). Both take printf-style messages, and With("key", value) adds fields.

  • The process-wide logger masks the secrets of every live task, the job logger masks its own. Going around either leaks them.
  • Libraries that log through logrus's or slog's global logger are forwarded to the process-wide one, so they follow its level and masking too.
  • That forwarding only works while logrus's global logger is left as logging configures it. A dependency that calls logrus.SetOutput or SetLevel breaks it, so check logging.LogrusBridgeIntact() when adding or upgrading one.
  • Tests read what was logged with logtest.NewLogger(), or logtest.SetDefault(t) for the package functions.

Built-in actions

Each lives in internal/pkg/action/<name> and is registered in builtinActions in act/runner/step_action_builtin.go.

End-to-end compatibility tests

make test-e2e runs the runner against E2E_GITEA_IMAGE. It defaults to the nightly image. It requires Docker and is excluded from make test. CI runs stable and nightly variants in parallel.

The suite shares one Gitea and regular runner. Cache and ephemeral scenarios use isolated repository runners. A run path is <workflow>@<ref> and a log row is <timestamp>Z <payload>.