MySQL: Datasource health check error message improvements (#96906)

* updated mysql health check error messages

* fix betterer error

* fix lint errors

* renamed moreDetailsLink to errorDetailsLink

* update

* added i18n locale files
This commit is contained in:
Sriram
2024-11-22 21:58:35 +00:00
committed by GitHub
parent 94262fd095
commit ea3bc8f253
6 changed files with 180 additions and 32 deletions
+40 -3
View File
@@ -4,6 +4,9 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
"strings"
"github.com/go-sql-driver/mysql"
"github.com/grafana/grafana-plugin-sdk-go/backend"
@@ -14,8 +17,8 @@ func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckH
err := e.db.Ping()
if err != nil {
logCheckHealthError(ctx, e.dsInfo, err, e.log)
if req.PluginContext.User.Role == "Admin" {
return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: err.Error()}, nil
if strings.EqualFold(req.PluginContext.User.Role, "Admin") {
return ErrToHealthCheckResult(err)
}
var driverErr *mysql.MySQLError
if errors.As(err, &driverErr) {
@@ -26,7 +29,41 @@ func (e *DataSourceHandler) CheckHealth(ctx context.Context, req *backend.CheckH
return &backend.CheckHealthResult{Status: backend.HealthStatusOk, Message: "Database Connection OK"}, nil
}
func logCheckHealthError(ctx context.Context, dsInfo DataSourceInfo, err error, logger log.Logger) {
// ErrToHealthCheckResult converts error into user friendly health check message
// This should be called with non nil error. If the err parameter is empty, we will send Internal Server Error
func ErrToHealthCheckResult(err error) (*backend.CheckHealthResult, error) {
if err == nil {
return &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: "Internal Server Error"}, nil
}
res := &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: err.Error()}
details := map[string]string{}
var opErr *net.OpError
if errors.As(err, &opErr) {
res.Message = "Network error: Failed to connect to the server"
if opErr != nil && opErr.Err != nil {
res.Message += fmt.Sprintf(". Error message: %s", opErr.Err.Error())
}
details["verboseMessage"] = err.Error()
details["errorDetailsLink"] = "https://grafana.com/docs/grafana/latest/datasources/mysql/#configure-the-data-source"
}
var driverErr *mysql.MySQLError
if errors.As(err, &driverErr) {
res.Message = "Database error: Failed to connect to the MySQL server"
if driverErr != nil && driverErr.Number > 0 {
res.Message += fmt.Sprintf(". MySQL error number: %d", driverErr.Number)
}
details["verboseMessage"] = err.Error()
details["errorDetailsLink"] = "https://dev.mysql.com/doc/mysql-errors/8.4/en/"
}
detailBytes, marshalErr := json.Marshal(details)
if marshalErr != nil {
return res, nil
}
res.JSONDetails = detailBytes
return res, nil
}
func logCheckHealthError(_ context.Context, dsInfo DataSourceInfo, err error, logger log.Logger) {
configSummary := map[string]any{
"config_url_length": len(dsInfo.URL),
"config_user_length": len(dsInfo.User),
@@ -0,0 +1,60 @@
package sqleng
import (
"errors"
"net"
"testing"
"github.com/go-sql-driver/mysql"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestErrToHealthCheckResult(t *testing.T) {
tests := []struct {
name string
err error
want *backend.CheckHealthResult
}{
{
name: "without error",
want: &backend.CheckHealthResult{Status: backend.HealthStatusError, Message: "Internal Server Error"},
},
{
name: "network error",
err: errors.Join(errors.New("foo"), &net.OpError{Op: "read", Net: "tcp", Err: errors.New("some op")}),
want: &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Network error: Failed to connect to the server. Error message: some op",
JSONDetails: []byte(`{"errorDetailsLink":"https://grafana.com/docs/grafana/latest/datasources/mysql/#configure-the-data-source","verboseMessage":"foo\nread tcp: some op"}`),
},
},
{
name: "db error",
err: errors.Join(errors.New("foo"), &mysql.MySQLError{Number: uint16(1045), Message: "Access denied for user"}),
want: &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "Database error: Failed to connect to the MySQL server. MySQL error number: 1045",
JSONDetails: []byte(`{"errorDetailsLink":"https://dev.mysql.com/doc/mysql-errors/8.4/en/","verboseMessage":"foo\nError 1045: Access denied for user"}`),
},
},
{
name: "regular error",
err: errors.New("internal server error"),
want: &backend.CheckHealthResult{
Status: backend.HealthStatusError,
Message: "internal server error",
JSONDetails: []byte(`{}`),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ErrToHealthCheckResult(tt.err)
require.Nil(t, err)
assert.Equal(t, string(tt.want.JSONDetails), string(got.JSONDetails))
require.Equal(t, tt.want, got)
})
}
}