From 6469164579d893b6b26b37ac6eb8ffe42e5b8899 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Mon, 5 May 2025 23:07:59 +0200 Subject: [PATCH 1/7] try fix write read service mismatch --- sungrow.go | 51 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/sungrow.go b/sungrow.go index d6f28dc..9affea1 100644 --- a/sungrow.go +++ b/sungrow.go @@ -201,17 +201,8 @@ func (s *Sungrow) Send(service string, params map[string]any, v any) error { return errors.New("not connected") } - m := map[string]any{ - "lang": "zh_cn", - "token": s.token, - "service": service, - } - for k, v := range params { - m[k] = v - } for { - s.log.Trace().Any("m", m).Msg("message") - resp, err := s.send(m) + resp, err := s.send(service, params) if err != nil { return err } @@ -221,8 +212,6 @@ func (s *Sungrow) Send(service string, params map[string]any, v any) error { d = string(resp.Data) } - s.log.Trace().EmbedObject(resp).Msg("response") - if resp.Code == 1 { if v == nil { return nil @@ -236,7 +225,7 @@ func (s *Sungrow) Send(service string, params map[string]any, v any) error { switch resp.Code { case 100, 104, 106: - // add a reconnect function with back-off + // FIXME: add a reconnect function with back-off s.log.Info().Str("host", s.Host).Msg("reconnecting") err = s.Connect() default: @@ -258,26 +247,48 @@ var responseCodesToBeDropped = []int{ 103, } -func (s *Sungrow) send(m map[string]any) (Response, error) { - s.log.Trace().Msg("Sungrow.send()") +func (s *Sungrow) send(service string, params map[string]any) (*Response, error) { + s.log.Trace().Str("service", service).Any("params", params).Msg("Sungrow.send()") + + m := map[string]any{ + "lang": "zh_cn", + "token": s.token, + "service": service, + } + for k, v := range params { + m[k] = v + } s.mu.Lock() defer s.mu.Unlock() + s.log.Trace().Any("m", m).Msg("write message") if err := s.ws.WriteJSON(m); err != nil { - return Response{}, err + return nil, err } var r Response for { if err := s.ws.ReadJSON(&r); err != nil { - return Response{}, err + return nil, err } + s.log.Trace().EmbedObject(r).Msg("read message") - if !slices.Contains(responseCodesToBeDropped, r.Code) { - return r, nil + if slices.Contains(responseCodesToBeDropped, r.Code) { + s.log.Debug().Msg("message dropped due to code") + continue + } + + var sd struct { + Service string `json:"service"` + } + if err := json.Unmarshal(r.Data, &sd); err != nil { + return nil, err + } else if sd.Service != service { + s.log.Debug().Msg("response dropped due to service mismatch") + continue } - s.log.Debug().EmbedObject(r).Msg("message dropped") + return &r, nil } } From 39ff227e9fd2f8ed41be3c67d41d51fd770eb3f4 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Mon, 5 May 2025 23:16:28 +0200 Subject: [PATCH 2/7] revert extract tls config --- sungrow.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sungrow.go b/sungrow.go index 9affea1..f51ddca 100644 --- a/sungrow.go +++ b/sungrow.go @@ -71,14 +71,14 @@ func (s *Sungrow) Connect() error { } log.Info().Msg("connecting") - var tcc *tls.Config - if _, ok := s.c.Transport.(*http.Transport); ok { - tcc = s.c.Transport.(*http.Transport).TLSClientConfig - } else { - // FIXME: this also needs to be configurable - tcc = &tls.Config{} - } - dialer := websocket.Dialer{TLSClientConfig: tcc} + // var tcc *tls.Config + // if _, ok := s.c.Transport.(*http.Transport); ok { + // tcc = s.c.Transport.(*http.Transport).TLSClientConfig + // } else { + // // FIXME: this also needs to be configurable + // tcc = &tls.Config{} + // } + dialer := websocket.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} u := url.URL{Scheme: "wss", Host: s.Host, Path: "/ws/home/overview"} ws, _, err := dialer.Dial(u.String(), nil) if err != nil { From c57ec5031c2e740c56c3f89ab9ffc2afb3e3eb75 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Mon, 5 May 2025 23:21:44 +0200 Subject: [PATCH 3/7] only check service if we have data --- sungrow.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/sungrow.go b/sungrow.go index f51ddca..c2e88ab 100644 --- a/sungrow.go +++ b/sungrow.go @@ -279,14 +279,16 @@ func (s *Sungrow) send(service string, params map[string]any) (*Response, error) continue } - var sd struct { - Service string `json:"service"` - } - if err := json.Unmarshal(r.Data, &sd); err != nil { - return nil, err - } else if sd.Service != service { - s.log.Debug().Msg("response dropped due to service mismatch") - continue + if len(r.Data) > 0 { + var sd struct { + Service string `json:"service"` + } + if err := json.Unmarshal(r.Data, &sd); err != nil { + return nil, err + } else if sd.Service != service { + s.log.Debug().Msg("response dropped due to service mismatch") + continue + } } return &r, nil From 66fc87c9ce8ac0af6bbe9c5bfa27e2bd8cb88570 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Tue, 6 May 2025 00:01:14 +0200 Subject: [PATCH 4/7] progress --- internal/serve/main.go | 32 -------------------------------- internal/serve/server.go | 2 +- internal/serve/static/index.html | 4 ++++ redgiant.go | 8 ++++++-- sungrow.go | 25 +++++++++++++++---------- 5 files changed, 26 insertions(+), 45 deletions(-) delete mode 100644 internal/serve/main.go diff --git a/internal/serve/main.go b/internal/serve/main.go deleted file mode 100644 index 596d7e0..0000000 --- a/internal/serve/main.go +++ /dev/null @@ -1,32 +0,0 @@ -package serve - -import ( - "os" - "time" - - "github.com/pmeier/redgiant" - "github.com/pmeier/redgiant/internal/config" - - "github.com/rs/zerolog" -) - -func Run(c config.Config) error { - logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger().Level(c.LogLevel) - - sg := redgiant.NewSungrow(c.Sungrow.Host, c.Sungrow.Username, c.Sungrow.Password, redgiant.WithLogger(logger)) - rg := redgiant.NewRedgiant(sg, redgiant.WithLogger(logger)) - - if err := rg.Connect(); err != nil { - return err - } - defer rg.Close() - - s := newServer(rg, logger) - if err := s.Start(c.Host, c.Port, 5*time.Second); err != nil { - return err - } - - select {} - - return nil -} diff --git a/internal/serve/server.go b/internal/serve/server.go index 5de02be..d2991e8 100644 --- a/internal/serve/server.go +++ b/internal/serve/server.go @@ -29,7 +29,7 @@ func newServer(rg *redgiant.Redgiant, logger zerolog.Logger) *Server { e.HidePort = true e.Debug = true - s := &Server{Echo: e, log: logger} + s := &Server{Echo: e, rg: rg, log: logger} routeFuncs := []routeFunc{ wrapBasicRouteFunc(health.HealthRouteFunc), diff --git a/internal/serve/static/index.html b/internal/serve/static/index.html index 226a49b..faf6f57 100644 --- a/internal/serve/static/index.html +++ b/internal/serve/static/index.html @@ -3,6 +3,10 @@ redgiant +
diff --git a/redgiant.go b/redgiant.go index 4094e53..08686ec 100644 --- a/redgiant.go +++ b/redgiant.go @@ -38,8 +38,10 @@ func (rg *Redgiant) Close() { } func (rg *Redgiant) About() (About, error) { + rg.log.Trace().Msg("Redgiant.About()") + type Data struct { - Measurements []RealMeasurement `json:"list"` + Measurements []sungrowRealMeasurement `json:"list"` } var d Data if err := rg.sg.Get("/about/list", nil, &d); err != nil { @@ -48,7 +50,7 @@ func (rg *Redgiant) About() (About, error) { ms := map[string]string{} for _, m := range d.Measurements { - ms[m.I18NCode] = m.Value + ms[m.DataName] = m.DataValue } return About{ @@ -60,6 +62,8 @@ func (rg *Redgiant) About() (About, error) { } func (rg *Redgiant) State() (State, error) { + rg.log.Trace().Msg("Redgiant.State()") + var s sungrowState if err := rg.sg.Send("state", nil, &s); err != nil { return State{}, err diff --git a/sungrow.go b/sungrow.go index c2e88ab..2996f7a 100644 --- a/sungrow.go +++ b/sungrow.go @@ -57,7 +57,7 @@ func NewSungrow(host string, username string, password string, opts ...OptFunc) Timeout: time.Second * 60, }), }, opts...)...) - return &Sungrow{Host: host, Username: username, Password: password, log: o.Logger} + return &Sungrow{Host: host, Username: username, Password: password, c: o.HTTPClient, log: o.Logger} } func (s *Sungrow) Connect() error { @@ -71,14 +71,14 @@ func (s *Sungrow) Connect() error { } log.Info().Msg("connecting") - // var tcc *tls.Config - // if _, ok := s.c.Transport.(*http.Transport); ok { - // tcc = s.c.Transport.(*http.Transport).TLSClientConfig - // } else { - // // FIXME: this also needs to be configurable - // tcc = &tls.Config{} - // } - dialer := websocket.Dialer{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + var tcc *tls.Config + if _, ok := s.c.Transport.(*http.Transport); ok { + tcc = s.c.Transport.(*http.Transport).TLSClientConfig + } else { + // FIXME: this also needs to be configurable + tcc = &tls.Config{} + } + dialer := websocket.Dialer{TLSClientConfig: tcc} u := url.URL{Scheme: "wss", Host: s.Host, Path: "/ws/home/overview"} ws, _, err := dialer.Dial(u.String(), nil) if err != nil { @@ -178,7 +178,10 @@ func (s *Sungrow) Get(path string, params map[string]string, v any) error { } u.RawQuery = q.Encode() - s.log.Trace().Str("url", u.String()).Str("query", u.Query().Encode()).Msg("request") + s.mu.Lock() + defer s.mu.Unlock() + + s.log.Trace().Str("url", u.String()).Msg("request") r, err := s.c.Get(u.String()) if err != nil { @@ -191,6 +194,8 @@ func (s *Sungrow) Get(path string, params map[string]string, v any) error { return err } + s.log.Trace().EmbedObject(resp).Msg("response") + return json.Unmarshal(resp.Data, v) } From 2203767df03dacc184b988283480db2fb21bf127 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Tue, 6 May 2025 00:02:19 +0200 Subject: [PATCH 5/7] fix --- internal/serve/serve.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 internal/serve/serve.go diff --git a/internal/serve/serve.go b/internal/serve/serve.go new file mode 100644 index 0000000..596d7e0 --- /dev/null +++ b/internal/serve/serve.go @@ -0,0 +1,32 @@ +package serve + +import ( + "os" + "time" + + "github.com/pmeier/redgiant" + "github.com/pmeier/redgiant/internal/config" + + "github.com/rs/zerolog" +) + +func Run(c config.Config) error { + logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout}).With().Timestamp().Logger().Level(c.LogLevel) + + sg := redgiant.NewSungrow(c.Sungrow.Host, c.Sungrow.Username, c.Sungrow.Password, redgiant.WithLogger(logger)) + rg := redgiant.NewRedgiant(sg, redgiant.WithLogger(logger)) + + if err := rg.Connect(); err != nil { + return err + } + defer rg.Close() + + s := newServer(rg, logger) + if err := s.Start(c.Host, c.Port, 5*time.Second); err != nil { + return err + } + + select {} + + return nil +} From 0a95a298e01ec22a23f2def0289cc671392e83b8 Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Wed, 7 May 2025 08:55:41 +0200 Subject: [PATCH 6/7] improve --- internal/serve/api.go | 4 ++- sungrow.go | 61 ++++++++++++++++++++++++------------------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/internal/serve/api.go b/internal/serve/api.go index fade0ab..d485947 100644 --- a/internal/serve/api.go +++ b/internal/serve/api.go @@ -17,7 +17,9 @@ func getRouteFunc[P any, O any](path string, bindFunc func(echo.Context) (P, err o, err := outputFunc(s.rg, p) if err != nil { - return err + // FIXME tmp to restart the server + panic(err.Error()) + // return err } return c.JSON(http.StatusOK, o) } diff --git a/sungrow.go b/sungrow.go index 2996f7a..8da228a 100644 --- a/sungrow.go +++ b/sungrow.go @@ -178,25 +178,34 @@ func (s *Sungrow) Get(path string, params map[string]string, v any) error { } u.RawQuery = q.Encode() + r, err := s.get(u) + if err != nil { + return err + } + + return json.Unmarshal(r.Data, v) +} + +func (s *Sungrow) get(u url.URL) (*Response, error) { s.mu.Lock() defer s.mu.Unlock() - s.log.Trace().Str("url", u.String()).Msg("request") + s.log.Trace().Str("u", u.String()).Msg("Sungrow.get()") r, err := s.c.Get(u.String()) if err != nil { - return err + return nil, err } defer r.Body.Close() var resp Response if err := json.NewDecoder(r.Body).Decode(&resp); err != nil { - return err + return nil, err } s.log.Trace().EmbedObject(resp).Msg("response") - return json.Unmarshal(resp.Data, v) + return &resp, nil } func (s *Sungrow) Send(service string, params map[string]any, v any) error { @@ -206,8 +215,17 @@ func (s *Sungrow) Send(service string, params map[string]any, v any) error { return errors.New("not connected") } + m := map[string]any{ + "lang": "zh_cn", + "token": s.token, + "service": service, + } + for k, v := range params { + m[k] = v + } + for { - resp, err := s.send(service, params) + resp, err := s.send(service, m) if err != nil { return err } @@ -218,7 +236,7 @@ func (s *Sungrow) Send(service string, params map[string]any, v any) error { } if resp.Code == 1 { - if v == nil { + if service == "ping" { return nil } @@ -243,31 +261,17 @@ func (s *Sungrow) Send(service string, params map[string]any, v any) error { } } -// Generally, there is a 1-to-1 correspondence between sent and received messages. -// However, some messages are produced by the inverter without a corresponding one. -// These messages have to be dropped. var responseCodesToBeDropped = []int{ - // This code indicates that the session timed out, - // but this only applies to the native web UI. + // The session of the web UI timed out 103, } -func (s *Sungrow) send(service string, params map[string]any) (*Response, error) { - s.log.Trace().Str("service", service).Any("params", params).Msg("Sungrow.send()") - - m := map[string]any{ - "lang": "zh_cn", - "token": s.token, - "service": service, - } - for k, v := range params { - m[k] = v - } - +func (s *Sungrow) send(service string, m map[string]any) (*Response, error) { s.mu.Lock() defer s.mu.Unlock() - s.log.Trace().Any("m", m).Msg("write message") + s.log.Trace().Str("service", service).Any("m", m).Msg("Sungrow.send()") + if err := s.ws.WriteJSON(m); err != nil { return nil, err } @@ -279,19 +283,22 @@ func (s *Sungrow) send(service string, params map[string]any) (*Response, error) } s.log.Trace().EmbedObject(r).Msg("read message") + // Generally, there is a 1-to-1 correspondence between sent and received messages. + // However, some messages are produced by the inverter without a corresponding one. + // These messages have to be dropped. if slices.Contains(responseCodesToBeDropped, r.Code) { - s.log.Debug().Msg("message dropped due to code") + s.log.Debug().Str("reason", "code").Int("code", r.Code).Msg("message dropped") continue } - if len(r.Data) > 0 { + if service != "ping" { var sd struct { Service string `json:"service"` } if err := json.Unmarshal(r.Data, &sd); err != nil { return nil, err } else if sd.Service != service { - s.log.Debug().Msg("response dropped due to service mismatch") + s.log.Debug().Str("reason", "service mismatch").Str("write", service).Str("read", sd.Service).Msg("response dropped due to service mismatch") continue } } From 6a90f9d62262c1020d8015e09ea4b8f50a8c335a Mon Sep 17 00:00:00 2001 From: Philip Meier Date: Sat, 10 May 2025 20:59:03 +0200 Subject: [PATCH 7/7] dirty --- gen.go | 3 + gen_zerr.go | 137 ++++++++++++++++++ http/http.go | 22 ++- internal/errors/errors.go | 42 ++++++ internal/errors/zerr.go | 286 ++++++++++++++++++++++++++++++++++++++ internal/serve/server.go | 37 ++++- main/main.go | 55 +++++++- sungrow.go | 1 + 8 files changed, 568 insertions(+), 15 deletions(-) create mode 100644 gen.go create mode 100644 gen_zerr.go create mode 100644 internal/errors/errors.go create mode 100644 internal/errors/zerr.go 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 }