diff --git a/changelog/unreleased/issue-4948 b/changelog/unreleased/issue-4948 new file mode 100644 index 000000000..3fd350d0d --- /dev/null +++ b/changelog/unreleased/issue-4948 @@ -0,0 +1,6 @@ +Enhancement: Format exit errors as JSON with --json + +Restic now prints any exit error messages as JSON when requested. + +https://github.com/restic/restic/issues/4948 +https://github.com/restic/restic/pull/4952 diff --git a/cmd/restic/cmd_version.go b/cmd/restic/cmd_version.go index cd32e2470..daf984a85 100644 --- a/cmd/restic/cmd_version.go +++ b/cmd/restic/cmd_version.go @@ -25,17 +25,19 @@ Exit status is 1 if there was any error. Run: func(_ *cobra.Command, _ []string) { if globalOptions.JSON { type jsonVersion struct { - Version string `json:"version"` - GoVersion string `json:"go_version"` - GoOS string `json:"go_os"` - GoArch string `json:"go_arch"` + MessageType string `json:"message_type"` // version + Version string `json:"version"` + GoVersion string `json:"go_version"` + GoOS string `json:"go_os"` + GoArch string `json:"go_arch"` } jsonS := jsonVersion{ - Version: version, - GoVersion: runtime.Version(), - GoOS: runtime.GOOS, - GoArch: runtime.GOARCH, + MessageType: "version", + Version: version, + GoVersion: runtime.Version(), + GoOS: runtime.GOOS, + GoArch: runtime.GOARCH, } err := json.NewEncoder(globalOptions.stdout).Encode(jsonS) diff --git a/cmd/restic/main.go b/cmd/restic/main.go index 5818221a5..fda53ca0e 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "encoding/json" "fmt" "log" "os" @@ -102,6 +103,30 @@ func tweakGoGC() { } } +func printExitError(code int, message string) { + if globalOptions.JSON { + type jsonExitError struct { + MessageType string `json:"message_type"` // exit_error + Code int `json:"code"` + Message string `json:"message"` + } + + jsonS := jsonExitError{ + MessageType: "exit_error", + Code: code, + Message: message, + } + + err := json.NewEncoder(globalOptions.stderr).Encode(jsonS) + if err != nil { + Warnf("JSON encode failed: %v\n", err) + return + } + } else { + fmt.Fprintf(globalOptions.stderr, "%v\n", message) + } +} + func main() { tweakGoGC() // install custom global logger into a buffer, if an error occurs @@ -131,21 +156,22 @@ func main() { err = nil } + var exitMessage string switch { case restic.IsAlreadyLocked(err): - fmt.Fprintf(os.Stderr, "%v\nthe `unlock` command can be used to remove stale locks\n", err) + exitMessage = fmt.Sprintf("%v\nthe `unlock` command can be used to remove stale locks", err) case err == ErrInvalidSourceData: - fmt.Fprintf(os.Stderr, "Warning: %v\n", err) + exitMessage = fmt.Sprintf("Warning: %v", err) case errors.IsFatal(err): - fmt.Fprintf(os.Stderr, "%v\n", err) + exitMessage = err.Error() case err != nil: - fmt.Fprintf(os.Stderr, "%+v\n", err) + exitMessage = fmt.Sprintf("%+v", err) if logBuffer.Len() > 0 { - fmt.Fprintf(os.Stderr, "also, the following messages were logged by a library:\n") + exitMessage += "also, the following messages were logged by a library:\n" sc := bufio.NewScanner(logBuffer) for sc.Scan() { - fmt.Fprintln(os.Stderr, sc.Text()) + exitMessage += fmt.Sprintln(sc.Text()) } } } @@ -165,5 +191,9 @@ func main() { default: exitCode = 1 } + + if exitCode != 0 { + printExitError(exitCode, exitMessage) + } Exit(exitCode) } diff --git a/doc/075_scripting.rst b/doc/075_scripting.rst index fa7fa1b6e..90bf111d7 100644 --- a/doc/075_scripting.rst +++ b/doc/075_scripting.rst @@ -83,12 +83,33 @@ JSON output of most restic commands are documented here. list of allowed values is documented may be extended at any time. +Exit errors +----------- + +Fatal errors will result in a final JSON message on ``stderr`` before the process exits. +It will hold the error message and the exit code. + +.. note:: + Some errors cannot be caught and reported this way, + such as Go runtime errors or command line parsing errors. + ++----------------------+-------------------------------------------+ +| ``message_type`` | Always "exit_error" | ++----------------------+-------------------------------------------+ +| ``code`` | Exit code (see above chart) | ++----------------------+-------------------------------------------+ +| ``error`` | Error message | ++----------------------+-------------------------------------------+ + Output formats -------------- -Currently only the output on ``stdout`` is JSON formatted. Errors printed on ``stderr`` -are still printed as plain text messages. The generated JSON output uses one of the -following two formats. +Commands print their main JSON output on ``stdout``. +The generated JSON output uses one of the following two formats. + +.. note:: + Not all messages and errors have been converted to JSON yet. + Feel free to submit a pull request! Single JSON document ^^^^^^^^^^^^^^^^^^^^ @@ -136,6 +157,8 @@ Status Error ^^^^^ +These errors are printed on ``stderr``. + +----------------------+-------------------------------------------+ | ``message_type`` | Always "error" | +----------------------+-------------------------------------------+ @@ -542,6 +565,8 @@ Status Error ^^^^^ +These errors are printed on ``stderr``. + +----------------------+-------------------------------------------+ | ``message_type`` | Always "error" | +----------------------+-------------------------------------------+ @@ -691,12 +716,14 @@ version The version command returns a single JSON object. -+----------------+--------------------+ -| ``version`` | restic version | -+----------------+--------------------+ -| ``go_version`` | Go compile version | -+----------------+--------------------+ -| ``go_os`` | Go OS | -+----------------+--------------------+ -| ``go_arch`` | Go architecture | -+----------------+--------------------+ ++------------------+--------------------+ +| ``message_type`` | Always "version" | ++------------------+--------------------+ +| ``version`` | restic version | ++------------------+--------------------+ +| ``go_version`` | Go compile version | ++------------------+--------------------+ +| ``go_os`` | Go OS | ++------------------+--------------------+ +| ``go_arch`` | Go architecture | ++------------------+--------------------+