docs/content/en/project/contributing/contributing-error.md
Meshery pervasively uses MeshKit as a golang and infrastructure management-specific library in all of its components. MeshKit helps populate error messages with a uniform and useful set of informative attributes.
To help with creating error codes, MeshKit contains a tool that analyzes, verifies and updates error codes in Meshery source code trees. It extracts error details into a file that can be used for publishing all error code references on the Meshery [error codes reference page]({{< ref "reference/references/error-codes.md" >}}). The objective to create this was to avoid centralized handling of error codes and automating everything
In order to create a Meshery error object, you will need to create a custom wrapper object for the native golang error. This can be done from the <a href="https://github.com/meshery/meshkit/tree/master/errors">MeshKit Error</a> package.
This tool will create a couple of files, one of them is designed to be used to generate the error reference on the Meshery Documentation website. The file errorutil_analyze_summary.json contains a summary of the analysis, notably lists of duplicates etc.
make error analyzes your code and returns any warnings to be aware of.Use the errors.New() function to create a new instance of the error object and pass situation-specific attributes as function arguments. These attributes are:
{{< code code=errors.New(ErrExampleCode, errors.Alert, []string{"<short-description>"}, []string{"<long-description>"}, []string{"<probable-cause>"}, []string{"<suggested remediation>"}) >}}
In this example we are creating an Error for being unable to marshal JSON
{{< code code=`var ( // Error code ErrMarshalCode= "replace_me"
//Static errors (for example)
ErrExample = errors.New(ErrExampleCode, errors.Alert, []string{"<short-description>"}, []string{"<long-description>"}, []string{"<probable-cause>"}, []string{"<suggested remediation>"})
)
// Dynamic errors //Error Name func ErrMarshal(err error, obj string) error { return errors.New(ErrMarshalCode, errors.Alert, []string{"Unable to marshal the : ", obj}, []string{err.Error()}, []string{}, []string{}) }` >}}
Old
{{< code code=bd, err := json.Marshal(providers) if err != nil { http.Error(w, "unable to marshal the providers", http.StatusInternalServerError) return } >}}
New
{{< code code=bd, err := json.Marshal(providers) if err != nil { marshalErr := ErrMarshal(err, "providers") h.log.Error(marshalErr) writeMeshkitError(w, marshalErr, http.StatusInternalServerError) return } >}}
http.Error is rejected by CI in the ./server module: it writes a plain-text
body and strips the MeshKit code, severity, and remediation that clients parse.
See [HTTP Error Response Contract]({{< ref "project/contributing/error-contract.md" >}})
for the response shape, the writeMeshkitError / writeJSONError helpers, and
how to choose the status code.
There already exists an interface for logger in MeshKit.
{{% alert color="warning" title="WARNING" %}}
To enforce the use of meshkit errors, the meshkit logger reads its structured fields - code, severity, probable cause, suggested remediation - straight off a meshkit error. A plain Go error still logs, but every one of those fields renders as None, leaving the operator a bare message with no code to look up and no remediation to follow. Wrap an error in a meshkit error before logging it. See: meshkit#119 for more insight.
{{% /alert %}}
{{< code code=type Logger struct { log logger.Handler } >}}
{{< code code=logrus.Debugf("meshLocationURL: %s", meshLocationURL) >}}
{{< code code=l.log.Debug("meshLocationURL: ", meshLocationURL) >}}
{{< code code=logrus.Errorf("error marshaling data: %v.", err) >}}
{{< code code=l.log.Error(ErrMarshal(err, obj)) >}}
{{< code code=`package main
import ( "fmt" "os"
meshkitErrors "github.com/meshery/meshkit/errors"
"github.com/meshery/meshkit/logger"
"github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
var ( // CI will replace test_code with new error code ErrOpeningFileCode = "test_code" )
func main() { logLevel := viper.GetInt("LOG_LEVEL") if viper.GetBool("DEBUG") { logLevel = int(logrus.DebugLevel) } log, err := logger.New("test", logger.Options{ Format: logger.SyslogLogFormat, LogLevel: logLevel, }) if err != nil { fmt.Println(err) os.Exit(1) }
// logging meshkit error
err = openFileWithMeshkitError("some.txt")
if err != nil {
log.Error(err)
}
// OUTPUT
// ERRO[2021-11-10T17:31:28+05:30] open some.txt: no such file or directory
// app=test code=1001 probable-cause="empty string passed as argument .file with this name doesn't exist"
// severity=2 short-description="unable to open file" suggested-remediation="pass a non-empty string as
// filename .create file before opening it"
// logging non meshkit error
err = openFile("some.txt")
if err != nil {
log.Error(err)
}
// OUTPUT
// ERRO[2024-07-01T19:09:09+05:30] open some.txt: no such file or directory
// app=test code= probable-cause= severity=0 short-description= suggested-remediation=
}
// this returns a non meshkit error func openFile(name string) error { _, err := os.Open(name) return err }
// this returns a meshkit error func openFileWithMeshkitError(name string) error { _, err := os.Open(name) return ErrOpeningFile(err) }
func ErrOpeningFile(err error) error { return meshkitErrors.New(ErrOpeningFileCode, meshkitErrors.Alert, []string{"unable to open file"}, []string{err.Error()}, []string{"empty string passed as argument ", "file with this name doesn't exist"}, []string{"pass a non-empty string as filename ", "create file before opening it"}) }` >}}
make error regenerates codes for the server but skips mesheryctl. The two components keep the same contract in different files.
mesheryctl: take the next code from mesheryctl/helpers/component_info.json (next_error_code) and bump that value in the same commit.server/helpers/component_info.json. errorutil refuses to run at all - "next_error_code is lower than or equal to highest used code" - until next_error_code is bumped past every code you added, so bump it in the same commit..github/workflows/error-codes-updater.yaml re-runs errorutil on every pull request and fails it if the analysis reports anything.
Name each constant <BuilderFuncName>Code - errorutil keys the export off that pairing. Adding a constant longer than the block's current widest name makes gofmt realign the entire error.go const block, so prefer a shorter name over a 300-line whitespace diff.
server/helpers/errorutil_errors_export.json is gitignored, but the reference data at docs/data/errorref/meshery-server_errors_export.json is tracked. Regenerate it with the jq --slurpfile wrapper the workflow uses, or the published error reference silently omits the new codes.
Only utils.Log.Error(err) renders a MeshKit error's code, cause and remediation; cobra's default print shows just the message. In mesheryctl commands, log the structured error for the user and return it for the exit path.