Merge pull request #4952 from mikix/json-exit

Format exit errors as JSON if requested
This commit is contained in:
Michael Eischer 2024-08-15 20:19:38 +00:00 committed by GitHub
commit 7462471c6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 91 additions and 26 deletions

View file

@ -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

View file

@ -25,17 +25,19 @@ Exit status is 1 if there was any error.
Run: func(_ *cobra.Command, _ []string) { Run: func(_ *cobra.Command, _ []string) {
if globalOptions.JSON { if globalOptions.JSON {
type jsonVersion struct { type jsonVersion struct {
Version string `json:"version"` MessageType string `json:"message_type"` // version
GoVersion string `json:"go_version"` Version string `json:"version"`
GoOS string `json:"go_os"` GoVersion string `json:"go_version"`
GoArch string `json:"go_arch"` GoOS string `json:"go_os"`
GoArch string `json:"go_arch"`
} }
jsonS := jsonVersion{ jsonS := jsonVersion{
Version: version, MessageType: "version",
GoVersion: runtime.Version(), Version: version,
GoOS: runtime.GOOS, GoVersion: runtime.Version(),
GoArch: runtime.GOARCH, GoOS: runtime.GOOS,
GoArch: runtime.GOARCH,
} }
err := json.NewEncoder(globalOptions.stdout).Encode(jsonS) err := json.NewEncoder(globalOptions.stdout).Encode(jsonS)

View file

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"context" "context"
"encoding/json"
"fmt" "fmt"
"log" "log"
"os" "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() { func main() {
tweakGoGC() tweakGoGC()
// install custom global logger into a buffer, if an error occurs // install custom global logger into a buffer, if an error occurs
@ -131,21 +156,22 @@ func main() {
err = nil err = nil
} }
var exitMessage string
switch { switch {
case restic.IsAlreadyLocked(err): 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: case err == ErrInvalidSourceData:
fmt.Fprintf(os.Stderr, "Warning: %v\n", err) exitMessage = fmt.Sprintf("Warning: %v", err)
case errors.IsFatal(err): case errors.IsFatal(err):
fmt.Fprintf(os.Stderr, "%v\n", err) exitMessage = err.Error()
case err != nil: case err != nil:
fmt.Fprintf(os.Stderr, "%+v\n", err) exitMessage = fmt.Sprintf("%+v", err)
if logBuffer.Len() > 0 { 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) sc := bufio.NewScanner(logBuffer)
for sc.Scan() { for sc.Scan() {
fmt.Fprintln(os.Stderr, sc.Text()) exitMessage += fmt.Sprintln(sc.Text())
} }
} }
} }
@ -165,5 +191,9 @@ func main() {
default: default:
exitCode = 1 exitCode = 1
} }
if exitCode != 0 {
printExitError(exitCode, exitMessage)
}
Exit(exitCode) Exit(exitCode)
} }

View file

@ -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. 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 Output formats
-------------- --------------
Currently only the output on ``stdout`` is JSON formatted. Errors printed on ``stderr`` Commands print their main JSON output on ``stdout``.
are still printed as plain text messages. The generated JSON output uses one of the The generated JSON output uses one of the following two formats.
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 Single JSON document
^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
@ -136,6 +157,8 @@ Status
Error Error
^^^^^ ^^^^^
These errors are printed on ``stderr``.
+----------------------+-------------------------------------------+ +----------------------+-------------------------------------------+
| ``message_type`` | Always "error" | | ``message_type`` | Always "error" |
+----------------------+-------------------------------------------+ +----------------------+-------------------------------------------+
@ -542,6 +565,8 @@ Status
Error Error
^^^^^ ^^^^^
These errors are printed on ``stderr``.
+----------------------+-------------------------------------------+ +----------------------+-------------------------------------------+
| ``message_type`` | Always "error" | | ``message_type`` | Always "error" |
+----------------------+-------------------------------------------+ +----------------------+-------------------------------------------+
@ -691,12 +716,14 @@ version
The version command returns a single JSON object. The version command returns a single JSON object.
+----------------+--------------------+ +------------------+--------------------+
| ``version`` | restic version | | ``message_type`` | Always "version" |
+----------------+--------------------+ +------------------+--------------------+
| ``go_version`` | Go compile version | | ``version`` | restic version |
+----------------+--------------------+ +------------------+--------------------+
| ``go_os`` | Go OS | | ``go_version`` | Go compile version |
+----------------+--------------------+ +------------------+--------------------+
| ``go_arch`` | Go architecture | | ``go_os`` | Go OS |
+----------------+--------------------+ +------------------+--------------------+
| ``go_arch`` | Go architecture |
+------------------+--------------------+