How to write MeshKit compatible errors
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. 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 MeshKit Error 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.
Conventions to follow while creating errors
- Errors names and codes are namespaced to components, i.e. they need to be unique within a component, which is verified by this tool.
- Errors are not to be reused across components and modules.
- Error codes are not to be set as integer. CI will take care of updating Error codes from a string to an integer.
- Running
make error
analyzes your code and returns any warnings to be aware of. - Capitalize the first letter of the every error description.
- Using errors.NewDefault(β¦) is deprecated. This tool emits a warning if its use is detected.
- Use errors.New(β¦) from MeshKit to create actual errors with all the details. This is often done in a factory function. It is important that the error code variable is used here, not a literal. Specify detailed descriptions, probable causes, and remedies. They need to be string literals, call expressions are ignored. This tool extracts this information from the code and exports it. For the Code argument in the errors.New use the same Error name and append a βCodeβ after it. e.g error name : ErrApplyManifest then the error code is ErrApplyManifestCode
- Set the value to any string, like βreplace_meβ (no convention here), e.g. ErrApplyManifestCode = βreplace_meβ.
- By convention, error codes and the factory functions live in files called error.go. The tool checks all files, but updates only error.go files.
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
- Short Description
- Long Description
- Probable Cause
- Suggested Remediation
Syntax
errors.New(ErrExampleCode, errors.Alert, []string{"<short-description>"}, []string{"<long-description>"}, []string{"<probable-cause>"}, []string{"<suggested remediation>"})
Example
In this example we are creating an Error for being unable to marshal JSON
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{}) }
Replacing old Error Codes
Old
bd, err := json.Marshal(providers) if err != nil { http.Error(w, "unable to marshal the providers", http.StatusInternalServerError) return }
New
bd, err := json.Marshal(providers) if err != nil { obj := "provider" http.Error(w, ErrMarshal(err, obj).Error(), http.StatusInternalServerError) return }
Replacing logrus
There already exists an interface for logger in MeshKit.
WARNING
To enforce the use of meshkit errors, meshkit logger was designed such that it only works with meshkit errors. If a non-meshkit error is logged through the logger, it would panic and kill the process. See: meshkit#119 for more insight.Defining a Logger
type Logger struct { log logger.Handler }
Debug
Old
logrus.Debugf("meshLocationURL: %s", meshLocationURL)
New
l.log.Debug("meshLocationURL: ", meshLocationURL)
Error
Old
logrus.Errorf("error marshaling data: %v.", err)
New
l.log.Error(ErrMarshal(err, obj))
A small program using meshkit errors and logger
package main import ( "fmt" "os" meshkitErrors "github.com/layer5io/meshkit/errors" "github.com/layer5io/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"}) }
Suggested Reading
- Build & Release (CI) - Details of Meshery's build and release strategy.
- Contributing to Meshery Adapters - How to contribute to Meshery Adapters
- Contributing to Meshery CLI - How to contribute to Meshery Command Line Interface.
- Contributing to Meshery's End-to-End Tests using Cypress - How to contribute to End-to-End Tests using Cypress.
- Contributing to Meshery Docker Extension - How to contribute to Meshery Docker Extension
- Contributing to Meshery Docs - How to contribute to Meshery Docs.
- Contributing to Meshery using git - How to contribute to Meshery using git
- Meshery CLI Contributing Guidelines - Design principles and code conventions.
- Contributing to Model Components - How to contribute to Meshery Model Components
- Contributing to Model Relationships - How to contribute to Meshery Models Relationships, Policies...
- Contributing to Models - How to contribute to Meshery Models, Components, Relationships, Policies...
- Contributing to Meshery Server Events - Guide is to help backend contributors send server events using Golang.
- Contributing to Meshery UI - Notification Center - How to contribute to the Notification Center in Meshery's web-based UI.
- Contributing to Meshery UI - Sistent - How to contribute to the Meshery's web-based UI using sistent design system.
- Contributing to Meshery's End-to-End Tests - How to contribute to End-to-End Tests using Playwright.
- Contributing to Meshery UI - How to contribute to Meshery UI (web-based user interface).
- Contributing to Meshery Server - How to contribute to Meshery Server
- Setting up Meshery Development Environment on Windows - How to set up Meshery Development Environment on Windows