diff --git a/gen.go b/gen.go new file mode 100644 index 0000000..fcc1885 --- /dev/null +++ b/gen.go @@ -0,0 +1,3 @@ +package main + +//go:generate go run gen_zerr.go diff --git a/gen_zerr.go b/gen_zerr.go new file mode 100644 index 0000000..d30175a --- /dev/null +++ b/gen_zerr.go @@ -0,0 +1,137 @@ +//go:build ignore + +package main + +import ( + "errors" + "fmt" + "os" + "reflect" + "slices" + "strings" + + "github.com/rs/zerolog" +) + +var excludedMethods []string = []string{ + // not propagated + "AnErr", + "Err", + "Discard", + "Enabled", + "GetCtx", + // handled manually + "Msg", + "MsgFunc", + "Msgf", + "Send", +} + +func main() { + logger := zerolog.New(os.Stdout) + e := logger.Error() + + r := reflect.TypeOf(e) + fmt.Println(r.NumMethod()) + + mes := []*Method{} + for i := range r.NumMethod() { + m := r.Method(i) + if slices.Contains(excludedMethods, m.Name) { + continue + } + + me, _ := parseMethod(m) + // fmt.Printf("%+v\n", me) + mes = append(mes, me) + } + + f, err := os.Create("./internal/errors/zerr.go") + if err != nil { + panic(err.Error()) + } + defer f.Close() + + f.WriteString("package errors\n") + + for _, me := range mes { + fmt.Println(me.RequiredImports) + ins := []string{} + for i, pn := range me.ParameterNames { + ins = append(ins, fmt.Sprintf("%s %s", pn, me.ParameterTypes[i])) + } + f.WriteString(fmt.Sprintf(methodProto, me.Name, strings.Join(ins, ", "), strings.Join(me.ParameterNames, ", "))) + } + +} + +type Method struct { + Name string + ParameterNames []string + ParameterTypes []string + RequiredImports []string +} + +var methodProto = ` +func (err *Error) %[1]s(%[2]s) *Error { + err.e.%[1]s(%[3]s) + return err +} +` + +var moduleProto = ` +package errors + +import ( +%s +) + +%s +` + +func parseMethod(m reflect.Method) (*Method, error) { + if m.Type.NumOut() != 1 { + // FIXME panic if return type of method is not *zerolog.Event + // || m.Type.Out(0) != nil + return nil, errors.New("oops") + } + + ni := m.Type.NumIn() - 1 + + var pns []string + switch ni { + case 0: + case 1: + pns = append(pns, "value") + case 2: + pns = append(pns, "key", "value") + default: + pns = append(pns, "key") + for i := range ni - 1 { + pns = append(pns, fmt.Sprintf("value%d", i+1)) + } + } + + pts := []string{} + ris := []string{} + for i := range ni { + in := m.Type.In(i + 1) + pts = append(pts, in.Name()) + if ri := in.PkgPath(); ri != "" { + ris = append(ris, ri) + } + } + + // b := strings.Builder{} + // b.WriteString(fmt.Sprintf("func (err *zerr.Error) %s() *zerr.Error {}", m.Name)) + // return b.String() + // m.Func.FieldByIndex([]int{0}) + + // return fmt.Sprint(m.Name, " ", m.Type) + + return &Method{Name: m.Name, ParameterNames: pns, ParameterTypes: pts, RequiredImports: slices.Compact(ris)}, nil +} + +func Foo(string, int) { + +} diff --git a/http/http.go b/http/http.go index b97c40d..69560ec 100644 --- a/http/http.go +++ b/http/http.go @@ -3,14 +3,13 @@ package http import ( "crypto/tls" "encoding/json" - "errors" "fmt" - "io" "net/http" "net/url" "time" "github.com/pmeier/redgiant" + "github.com/pmeier/redgiant/internal/errors" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) @@ -34,18 +33,17 @@ func NewRedgiant(host string, port uint, opts ...redgiant.OptFunc) *Redgiant { return &Redgiant{host: fmt.Sprintf("%s:%d", host, port), c: o.HTTPClient, log: o.Logger} } -func assertResponseSuccessful(r *http.Response) error { +func assertRequestSuccessful(r *http.Response) *errors.Error { if r.StatusCode >= 200 && r.StatusCode < 300 { return nil } - var msg string - if c, _ := io.ReadAll(r.Body); len(c) == 0 { - msg = r.Status - } else { - msg = string(c) - } - return errors.New(msg) + err := errors.New("request failed").HTTPRedacted(false) + // Int("code", r.StatusCode) + // if c, _ := io.ReadAll(r.Body); len(c) == 0 { + // err.Bytes("content", c) + // } + return err } func (rg *Redgiant) Health() error { @@ -57,7 +55,7 @@ func (rg *Redgiant) Health() error { } defer r.Body.Close() - return assertResponseSuccessful(r) + return assertRequestSuccessful(r) } func (rg *Redgiant) getAPI(endpoint string, query url.Values, v any) error { @@ -73,7 +71,7 @@ func (rg *Redgiant) getAPI(endpoint string, query url.Values, v any) error { } defer r.Body.Close() - if err := assertResponseSuccessful(r); err != nil { + if err := assertRequestSuccessful(r); err != nil { return err } diff --git a/internal/errors/errors.go b/internal/errors/errors.go new file mode 100644 index 0000000..dad0f81 --- /dev/null +++ b/internal/errors/errors.go @@ -0,0 +1,42 @@ +package errors + +import ( + "bytes" + "net/http" + + "github.com/rs/zerolog" +) + +type Error struct { + StatusCode int + Redacted bool + e *zerolog.Event + msg string + b *bytes.Buffer + text string +} + +func New(msg string) *Error { + var b bytes.Buffer + l := zerolog.New(&b) + return &Error{StatusCode: http.StatusInternalServerError, Redacted: true, e: l.Log(), msg: msg, b: &b} +} + +func (e *Error) Error() string { + if e.text == "" { + e.e.Msg(e.msg) + e.text = e.b.String() + } + + return e.text +} + +func (err *Error) HTTPStatusCode(statusCode int) *Error { + err.StatusCode = statusCode + return err +} + +func (err *Error) HTTPRedacted(redacted bool) *Error { + err.Redacted = redacted + return err +} diff --git a/internal/errors/zerr.go b/internal/errors/zerr.go new file mode 100644 index 0000000..831f7b4 --- /dev/null +++ b/internal/errors/zerr.go @@ -0,0 +1,286 @@ +package errors + +func (err *Error) Any(key string, value ) *Error { + err.e.Any(key, value) + return err +} + +func (err *Error) Array(key string, value LogArrayMarshaler) *Error { + err.e.Array(key, value) + return err +} + +func (err *Error) Bool(key string, value bool) *Error { + err.e.Bool(key, value) + return err +} + +func (err *Error) Bools(key string, value ) *Error { + err.e.Bools(key, value) + return err +} + +func (err *Error) Bytes(key string, value ) *Error { + err.e.Bytes(key, value) + return err +} + +func (err *Error) Caller(value ) *Error { + err.e.Caller(value) + return err +} + +func (err *Error) CallerSkipFrame(value int) *Error { + err.e.CallerSkipFrame(value) + return err +} + +func (err *Error) Ctx(value Context) *Error { + err.e.Ctx(value) + return err +} + +func (err *Error) Dict(key string, value ) *Error { + err.e.Dict(key, value) + return err +} + +func (err *Error) Dur(key string, value Duration) *Error { + err.e.Dur(key, value) + return err +} + +func (err *Error) Durs(key string, value ) *Error { + err.e.Durs(key, value) + return err +} + +func (err *Error) EmbedObject(value LogObjectMarshaler) *Error { + err.e.EmbedObject(value) + return err +} + +func (err *Error) Errs(key string, value ) *Error { + err.e.Errs(key, value) + return err +} + +func (err *Error) Fields(value ) *Error { + err.e.Fields(value) + return err +} + +func (err *Error) Float32(key string, value float32) *Error { + err.e.Float32(key, value) + return err +} + +func (err *Error) Float64(key string, value float64) *Error { + err.e.Float64(key, value) + return err +} + +func (err *Error) Floats32(key string, value ) *Error { + err.e.Floats32(key, value) + return err +} + +func (err *Error) Floats64(key string, value ) *Error { + err.e.Floats64(key, value) + return err +} + +func (err *Error) Func(value ) *Error { + err.e.Func(value) + return err +} + +func (err *Error) Hex(key string, value ) *Error { + err.e.Hex(key, value) + return err +} + +func (err *Error) IPAddr(key string, value IP) *Error { + err.e.IPAddr(key, value) + return err +} + +func (err *Error) IPPrefix(key string, value IPNet) *Error { + err.e.IPPrefix(key, value) + return err +} + +func (err *Error) Int(key string, value int) *Error { + err.e.Int(key, value) + return err +} + +func (err *Error) Int16(key string, value int16) *Error { + err.e.Int16(key, value) + return err +} + +func (err *Error) Int32(key string, value int32) *Error { + err.e.Int32(key, value) + return err +} + +func (err *Error) Int64(key string, value int64) *Error { + err.e.Int64(key, value) + return err +} + +func (err *Error) Int8(key string, value int8) *Error { + err.e.Int8(key, value) + return err +} + +func (err *Error) Interface(key string, value ) *Error { + err.e.Interface(key, value) + return err +} + +func (err *Error) Ints(key string, value ) *Error { + err.e.Ints(key, value) + return err +} + +func (err *Error) Ints16(key string, value ) *Error { + err.e.Ints16(key, value) + return err +} + +func (err *Error) Ints32(key string, value ) *Error { + err.e.Ints32(key, value) + return err +} + +func (err *Error) Ints64(key string, value ) *Error { + err.e.Ints64(key, value) + return err +} + +func (err *Error) Ints8(key string, value ) *Error { + err.e.Ints8(key, value) + return err +} + +func (err *Error) MACAddr(key string, value HardwareAddr) *Error { + err.e.MACAddr(key, value) + return err +} + +func (err *Error) Object(key string, value LogObjectMarshaler) *Error { + err.e.Object(key, value) + return err +} + +func (err *Error) RawCBOR(key string, value ) *Error { + err.e.RawCBOR(key, value) + return err +} + +func (err *Error) RawJSON(key string, value ) *Error { + err.e.RawJSON(key, value) + return err +} + +func (err *Error) Stack() *Error { + err.e.Stack() + return err +} + +func (err *Error) Str(key string, value string) *Error { + err.e.Str(key, value) + return err +} + +func (err *Error) Stringer(key string, value Stringer) *Error { + err.e.Stringer(key, value) + return err +} + +func (err *Error) Stringers(key string, value ) *Error { + err.e.Stringers(key, value) + return err +} + +func (err *Error) Strs(key string, value ) *Error { + err.e.Strs(key, value) + return err +} + +func (err *Error) Time(key string, value Time) *Error { + err.e.Time(key, value) + return err +} + +func (err *Error) TimeDiff(key string, value1 Time, value2 Time) *Error { + err.e.TimeDiff(key, value1, value2) + return err +} + +func (err *Error) Times(key string, value ) *Error { + err.e.Times(key, value) + return err +} + +func (err *Error) Timestamp() *Error { + err.e.Timestamp() + return err +} + +func (err *Error) Type(key string, value ) *Error { + err.e.Type(key, value) + return err +} + +func (err *Error) Uint(key string, value uint) *Error { + err.e.Uint(key, value) + return err +} + +func (err *Error) Uint16(key string, value uint16) *Error { + err.e.Uint16(key, value) + return err +} + +func (err *Error) Uint32(key string, value uint32) *Error { + err.e.Uint32(key, value) + return err +} + +func (err *Error) Uint64(key string, value uint64) *Error { + err.e.Uint64(key, value) + return err +} + +func (err *Error) Uint8(key string, value uint8) *Error { + err.e.Uint8(key, value) + return err +} + +func (err *Error) Uints(key string, value ) *Error { + err.e.Uints(key, value) + return err +} + +func (err *Error) Uints16(key string, value ) *Error { + err.e.Uints16(key, value) + return err +} + +func (err *Error) Uints32(key string, value ) *Error { + err.e.Uints32(key, value) + return err +} + +func (err *Error) Uints64(key string, value ) *Error { + err.e.Uints64(key, value) + return err +} + +func (err *Error) Uints8(key string, value ) *Error { + err.e.Uints8(key, value) + return err +} diff --git a/internal/serve/server.go b/internal/serve/server.go index d2991e8..428a6e8 100644 --- a/internal/serve/server.go +++ b/internal/serve/server.go @@ -2,12 +2,15 @@ package serve import ( "embed" + "encoding/json" "fmt" + "net/http" "time" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/pmeier/redgiant" + "github.com/pmeier/redgiant/internal/errors" "github.com/pmeier/redgiant/internal/health" "github.com/rs/zerolog" ) @@ -27,7 +30,6 @@ func newServer(rg *redgiant.Redgiant, logger zerolog.Logger) *Server { e := echo.New() e.HideBanner = true e.HidePort = true - e.Debug = true s := &Server{Echo: e, rg: rg, log: logger} @@ -60,6 +62,39 @@ func newServer(rg *redgiant.Redgiant, logger zerolog.Logger) *Server { }, })) + e.HTTPErrorHandler = func(err error, c echo.Context) { + if c.Response().Committed { + return + } + + var code int + var logData map[string]any + var httpData map[string]any + if rgerr, ok := err.(*errors.Error); ok { + code = rgerr.StatusCode + if err := json.Unmarshal([]byte(err.Error()), &logData); err != nil { + panic(err.Error()) + } + if !rgerr.Redacted { + httpData = logData + } + } else { + code = http.StatusInternalServerError + } + + if httpData == nil { + httpData = map[string]any{zerolog.MessageFieldName: http.StatusText(code)} + } + + e := logger.Error() + for k, v := range logData { + e.Any(k, v) + } + e.Send() + + c.JSON(code, httpData) + } + return s } diff --git a/main/main.go b/main/main.go index b701e7c..ab32568 100644 --- a/main/main.go +++ b/main/main.go @@ -1,7 +1,58 @@ package main -import "github.com/pmeier/redgiant/internal/cmd" +//go:generate go run ./gen/gen.go + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + "github.com/pmeier/redgiant/internal/errors" + "github.com/rs/zerolog" +) + +// import "github.com/pmeier/redgiant/internal/cmd" + +// func main() { +// cmd.Execute() +// } func main() { - cmd.Execute() + var err error = errors.New("oops").HTTPStatusCode(http.StatusBadGateway).HTTPRedacted(false) + // var err error = nerrors.New("heheheh") + + logger := zerolog.New(os.Stdout) + + var code int + var logData map[string]any + var httpData map[string]any + if rgerr, ok := err.(*errors.Error); ok { + code = rgerr.StatusCode + if err := json.Unmarshal([]byte(err.Error()), &logData); err != nil { + panic(err.Error()) + } + if !rgerr.Redacted { + httpData = logData + } + } else { + code = http.StatusInternalServerError + } + + if httpData == nil { + httpData = map[string]any{zerolog.MessageFieldName: http.StatusText(code)} + } + + e := logger.Error() + for k, v := range logData { + e.Any(k, v) + } + e.Send() + + if v, err := json.MarshalIndent(httpData, "", " "); err != nil { + panic(err.Error()) + } else { + fmt.Println(code, string(v)) + } + } diff --git a/sungrow.go b/sungrow.go index 8da228a..ba77ae1 100644 --- a/sungrow.go +++ b/sungrow.go @@ -227,6 +227,7 @@ func (s *Sungrow) Send(service string, params map[string]any, v any) error { for { resp, err := s.send(service, m) if err != nil { + // FIXME reconnect here return err }