diff --git a/.github/workflows/go-test.yml b/.github/workflows/go-test.yml index 033c6e7..4895a38 100644 --- a/.github/workflows/go-test.yml +++ b/.github/workflows/go-test.yml @@ -4,7 +4,7 @@ on: push: branches: - master - - fasthttp + jobs: build: diff --git a/.gitlab-ci.yaml b/.gitlab-ci.yaml index 1d9f6a0..c6a65fe 100644 --- a/.gitlab-ci.yaml +++ b/.gitlab-ci.yaml @@ -4,7 +4,7 @@ workflow: - if: $CI_MERGE_REQUEST_IID - if: $CI_COMMIT_TAG -image: docker.frafos.net/go-ci:1.19 +image: docker.frafos.net/go-ci:1.21 stages: - test diff --git a/.golangci.yml b/.golangci.yml index ced93c9..ae7424d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,6 +3,16 @@ linters-settings: cyclop: max-complexity: 15 # package-average: 0.0 + depguard: + rules: + ioutil: + deny: + - pkg: "io/ioutl" + desc: use io or os instead + logging: + deny: + - pkg: "log" + desc: not allowed use log/slog instead funlen: lines: 90 statements: 50 @@ -24,9 +34,6 @@ linters-settings: - shadow misspell: locale: US - maligned: - # print struct with more effective memory layout or not, false by default - suggest-new: true revive: ignore-generated-header: true severity: warning @@ -41,7 +48,7 @@ linters-settings: - c webfmwk.Context - t testing.T - e error - + linters: # please, do not use `enable-all`: it's deprecated and will be removed soon. # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint @@ -55,7 +62,7 @@ linters: - contextcheck - cyclop - decorder - - depguard + # - depguard - dogsled - dupl - dupword diff --git a/CHANGELOG.md b/CHANGELOG.md index 128a6da..8d17361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantipush c Versioning](https://semver.org/spec/v2.0.0.html). -## [5.3.1] (upcoming) +## [6.0.1] (upcoming) ### Added -### Channged +### Changed ### Fixed ### Removed +## [6.0.0] (Mon Aug 28 17:27:46 2023) + +### Added +- http2 support +- slog support + +### Changed +- bumped toolchain to go 1.21 + +### Removed +- old logger interface and implementation + +### Fixed + ## [5.3.0] (Wed Jul 5 13:3136 2023) ### Added diff --git a/TODOs.org b/TODOs.org index e541c7c..1673bce 100644 --- a/TODOs.org +++ b/TODOs.org @@ -7,8 +7,8 @@ # to archive to file: C-c C-x C-a # open archive sibling: C-c C-tab -* TODO v5 [6/11] [54%] -DEADLINE: <2023-06-11 Sun> +* TODO v5 [9/11] [81%] +DEADLINE: <2023-07-28 Fri> ** DONE [#A] http2 [2/2] [100%] CLOSED: [2023-07-07 Fri 14:30] DEADLINE: <2023-07-07 Fri> @@ -17,16 +17,16 @@ CLOSED: [2023-07-06 Thu 13:21] *** DONE stream payload CLOSED: [2023-07-07 Fri 14:30] -** IDEA read header for translator -DEADLINE: <2024-05-11 Sat> +** CANCELLED read header for translator +CLOSED: [2023-07-24 Mon 11:06] DEADLINE: <2024-05-11 Sat> ** TODO update the doc -DEADLINE: <2023-05-18 Thu> -** TODO run some fuzzing -DEADLINE: <2023-05-13 Sat> -** TODO run some benchmark -DEADLINE: <2023-05-13 Sat> +DEADLINE: <2023-07-28 Fri> +** CANCELLED run some fuzzing +CLOSED: [2023-07-24 Mon 11:06] DEADLINE: <2023-05-13 Sat> +** CANCELLED run some benchmark +CLOSED: [2023-07-24 Mon 11:07] DEADLINE: <2023-05-13 Sat> ** WIP coverage [1/9] -DEADLINE: <2023-05-12 Fri> +DEADLINE: <2023-07-28 Fri> - [ ] test CORS - [ ] test DumpRoutes - [ ] test ctrl+c diff --git a/context.go b/context.go index 5a13e56..24ffa31 100644 --- a/context.go +++ b/context.go @@ -4,9 +4,9 @@ import ( "context" "encoding/json" "errors" + "log/slog" "net/http" - "github.com/burgesQ/log" validator "github.com/go-playground/validator/v10" "github.com/gorilla/schema" "github.com/valyala/fasthttp" @@ -37,11 +37,11 @@ type ( // ContextLogger interface implement the context Logger needs. ContextLogger interface { - // SetLogger set the logger of the ctx. - SetLogger(logger log.Log) Context + // SetStructuredLogger set context' structured logger. + SetStructuredLogger(logger *slog.Logger) Context - // GetLogger return the logger of the ctx. - GetLogger() log.Log + // GetStructuredLogger return context' structured logger. + GetStructuredLogger() *slog.Logger } // Context interface implement the context used in this project. @@ -70,8 +70,8 @@ type ( // It hold the data used by the request icontext struct { *fasthttp.RequestCtx - log log.Log - ctx context.Context //nolint:containedctx + slog *slog.Logger + ctx context.Context //nolint:containedctx } ) @@ -91,26 +91,26 @@ func (c *icontext) GetVar(key string) string { return v } -// GetQuery implement Context +// GetQuery implement Context. func (c *icontext) GetQuery() *fasthttp.Args { return c.QueryArgs() } -// GetFastContext implement Context +// GetFastContext implement Context. func (c *icontext) GetFastContext() *fasthttp.RequestCtx { return c.RequestCtx } -// SetLogger implement Context -func (c *icontext) SetLogger(logger log.Log) Context { - c.log = logger +// SetStructuredLogger implement Context. +func (c *icontext) SetStructuredLogger(logger *slog.Logger) Context { + c.slog = logger return c } // GetLogger implement Context -func (c *icontext) GetLogger() log.Log { - return c.log +func (c *icontext) GetStructuredLogger() *slog.Logger { + return c.slog } // GetContext implement Context @@ -124,7 +124,7 @@ func (c *icontext) FetchContent(dest interface{}) ErrorHandled { b := c.PostBody() if e := json.Unmarshal(b, &dest); e != nil { - c.log.Errorf("fetching payload: %s", e.Error()) + c.slog.Error("fetching payload", "error", e) return errUnprocessablePayload } @@ -136,7 +136,7 @@ func (c *icontext) FetchContent(dest interface{}) ErrorHandled { // this implemtation use validator to anotate & check struct func (c *icontext) Validate(dest interface{}) ErrorHandled { if e := validate.Struct(dest); e != nil { - c.log.Errorf("validating : %s", e.Error()) + c.slog.Error("validating form or query param", "error", e) var ev validator.ValidationErrors @@ -172,7 +172,7 @@ func (c *icontext) DecodeQP(dest interface{}) ErrorHandled { }) if e := decoder.Decode(dest, m); e != nil { - c.log.Errorf("validating qp : %s", e.Error()) + c.slog.Error("validating query params", "error", e) return NewUnprocessable(NewErrorFromError(e)) } diff --git a/context_test.go b/context_test.go index c70db03..53c93da 100644 --- a/context_test.go +++ b/context_test.go @@ -2,11 +2,11 @@ package webfmwk import ( "context" + "log/slog" "net/http" "testing" "github.com/burgesQ/gommon/webtest" - "github.com/burgesQ/webfmwk/v6/log" "github.com/stretchr/testify/assert" ) @@ -16,8 +16,6 @@ var ( ) func TestParam(t *testing.T) { - log.SetLogLevel(log.LogDebug) - wrapperGet(t, "/test/{id}", "/test/tutu", func(c Context) error { id := c.GetVar("id") if id != "tutu" { @@ -35,11 +33,11 @@ func TestParam(t *testing.T) { func TestLogger(t *testing.T) { var ( c = icontext{} - logger = log.GetLogger() + logger = slog.Default() ) - c.SetLogger(logger) - assert.True(t, logger == c.GetLogger(), "context logger should be the setted one") + c.SetStructuredLogger(logger) + assert.True(t, logger == c.GetStructuredLogger(), "context logger should be the setted one") // assert.True(t, logger == GetLogger(), "default logger should be the setted one") } diff --git a/go.mod b/go.mod index 7dddf1f..9055bc4 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,9 @@ module github.com/burgesQ/webfmwk/v6 -go 1.19 +go 1.21 require ( github.com/burgesQ/gommon v1.2.4 - github.com/burgesQ/log v0.0.0-20230331103157-ecfef450adac github.com/dgrr/http2 v0.3.5 github.com/fasthttp/router v1.4.19 github.com/go-playground/locales v0.14.1 diff --git a/go.sum b/go.sum index 6276a6e..1c59072 100644 --- a/go.sum +++ b/go.sum @@ -3,8 +3,6 @@ github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/ github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/burgesQ/gommon v1.2.4 h1:KpPzDjtaSZ20Tas75siJNASglDINXG0N20HoGKhsTbY= github.com/burgesQ/gommon v1.2.4/go.mod h1:JXuiXVcuwJ/gxtjb9Lnpp0zDIdCDASVW0uv6cWy0COs= -github.com/burgesQ/log v0.0.0-20230331103157-ecfef450adac h1:Fz/u0vMnu9tUhlydXudpmdg57UgBroPwa+FhKgGKFbk= -github.com/burgesQ/log v0.0.0-20230331103157-ecfef450adac/go.mod h1:0mqbA+jgzeuo2IcjkgOHi/NKCmjWVrkLS/7DEmAkCng= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -16,6 +14,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= diff --git a/handler.go b/handler.go index 5689127..ef9d553 100644 --- a/handler.go +++ b/handler.go @@ -2,6 +2,7 @@ package webfmwk import ( "bytes" + "log/slog" "github.com/segmentio/encoding/json" "github.com/valyala/fasthttp" @@ -40,7 +41,7 @@ func GetIPFromRequest(fc *fasthttp.RequestCtx) string { func handleHandlerError(next HandlerFunc) HandlerFunc { return HandlerFunc(func(c Context) error { if e := next(c); e != nil { - c.GetLogger().Errorf("catched from handler (%T) : %s", e, e.Error()) + c.GetStructuredLogger().Error("catch'd from handler", "error", e) HandleError(c, e) } @@ -71,8 +72,8 @@ func contentIsJSON(next HandlerFunc) HandlerFunc { func handleNotFound(c Context) error { fc := c.GetFastContext() - c.GetLogger().Infof("[!] 404 reached for [%s] %s %s", - GetIPFromRequest(fc), fc.Method(), fc.RequestURI()) + c.GetStructuredLogger().Info("[!] 404 reached", slog.Group("request", + "ip", GetIPFromRequest(fc), "method", fc.Method(), "uri", fc.RequestURI())) return c.JSONNotFound(json.RawMessage(`{"status":404,"message":"not found"}`)) } @@ -80,8 +81,8 @@ func handleNotFound(c Context) error { func handleNotAllowed(c Context) error { fc := c.GetFastContext() - c.GetLogger().Infof("[!] 405 reached for [%s] %s %s", - GetIPFromRequest(fc), fc.Method(), fc.RequestURI()) + c.GetStructuredLogger().Info("[!] 405 reached", slog.Group("request", + "ip", GetIPFromRequest(fc), "method", fc.Method(), "uri", fc.RequestURI())) return c.JSONMethodNotAllowed(json.RawMessage(`{"status":405,"message":"method not allowed"}`)) } diff --git a/handler/recover/recover.go b/handler/recover/recover.go index 00f0500..8680e13 100644 --- a/handler/recover/recover.go +++ b/handler/recover/recover.go @@ -18,7 +18,7 @@ func Handler(next webfmwk.HandlerFunc) webfmwk.HandlerFunc { webfmwk.HandleError(c, e) default: - c.GetLogger().Errorf("catched %T %#v", e, e) + c.GetStructuredLogger().Error("catched exit", "error", e) _ = c.JSONInternalError(webfmwk.NewError( fmt.Sprintf("internal error: %T %v", e, e))) } diff --git a/handler/logging/logging.go b/handler/slogging/logging.go similarity index 65% rename from handler/logging/logging.go rename to handler/slogging/logging.go index 75b91bc..5cab6b8 100644 --- a/handler/logging/logging.go +++ b/handler/slogging/logging.go @@ -1,6 +1,7 @@ -package logging +package slogging import ( + "log/slog" "strconv" "time" "unicode/utf8" @@ -30,10 +31,13 @@ func Handler(next webfmwk.HandlerFunc) webfmwk.HandlerFunc { } c.SetHeader(HeaderRequestID, rid) - lg := c.GetLogger().SetPrefix("[" + rid + "]: ") + lg := c.GetStructuredLogger().With("request id", rid, slog.Group("request", + "ip", webfmwk.GetIPFromRequest(fc), + "method", fc.Method(), + "uri", fc.RequestURI())) + c.SetStructuredLogger(lg) - c.SetLogger(lg) - lg.Infof("--> %q [%s]%s ", webfmwk.GetIPFromRequest(fc), fc.Method(), fc.RequestURI()) + lg.Info("--> new request") e := next(c) elapsed := time.Since(start) @@ -42,13 +46,16 @@ func Handler(next webfmwk.HandlerFunc) webfmwk.HandlerFunc { if utf8.Valid(content) { if l > _limitOutput { - lg.Debugf(">%s<", content[:_limitOutput]) + lg.Debug("trunkated response", "body", + content[:_limitOutput], + "lim", _limitOutput) } else { - lg.Debugf(">%s<", content) + lg.Debug("full response", "body", content) } } - lg.Infof("<-- [%d]: took %s", fc.Response.Header.StatusCode(), elapsed) + lg.Info("<-- request done", "code", fc.Response.Header.StatusCode(), + "elapsed", elapsed) return e }) diff --git a/handler/logging/logging_test.go b/handler/slogging/logging_test.go similarity index 97% rename from handler/logging/logging_test.go rename to handler/slogging/logging_test.go index 559cbb7..e72b41d 100644 --- a/handler/logging/logging_test.go +++ b/handler/slogging/logging_test.go @@ -1,4 +1,4 @@ -package logging +package slogging import ( "encoding/json" diff --git a/log/level.go b/log/level.go deleted file mode 100644 index 6dde8f9..0000000 --- a/log/level.go +++ /dev/null @@ -1,44 +0,0 @@ -package log - -// Level hold the logging level -type Level uint8 - -// Following contant hold the possible log levels -const ( - LogErr Level = iota - LogWarning - LogInfo - LogDebug - LogPrint -) - -var ( - _l2s = [...]string{ - LogErr: "error", - LogWarning: "warning", - LogInfo: "info", - LogDebug: "debug", - LogPrint: "print", - } - _out = map[Level]string{ - LogErr: "! ERR : ", - LogWarning: "* WARN : ", - LogInfo: "+ INFO : ", - LogDebug: "- DBG : ", - LogPrint: "", - } -) - -func (l Level) String() string { - return _l2s[l] -} - -// SetLogLevel set the global logger log level -func SetLogLevel(level Level) (ok bool) { - if level >= LogErr && level <= LogDebug { - _lg.level = level - ok = true - } - - return -} diff --git a/log/level_test.go b/log/level_test.go deleted file mode 100644 index aa5778a..0000000 --- a/log/level_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package log - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestLevel(t *testing.T) { - var l Level - require.Equal(t, "error", l.String()) - - SetLogLevel(LogWarning) - require.Equal(t, LogWarning, _lg.level) - SetLogLevel(LogErr) -} diff --git a/log/log.go b/log/log.go deleted file mode 100644 index 61aa61e..0000000 --- a/log/log.go +++ /dev/null @@ -1,71 +0,0 @@ -// Package log implement the Log interface used by the webfmwk -package log - -import ( - "fmt" - - qlog "github.com/burgesQ/log" -) - -type ( - logger struct { - prefix string - level Level - } -) - -// default internal logger -var _lg = logger{level: LogErr, prefix: ""} - -// GetLogger return an struct fullfilling the Log interface -func GetLogger() qlog.Log { return _lg } - -// SetPrefix implement the LogPrefix interface -func (l logger) SetPrefix(prefix string) qlog.Log { - return logger{level: l.level, prefix: prefix} -} - -// GetPrefix implement the LogPrefix interface -func (l logger) GetPrefix() string { - return l.prefix -} - -func (l *logger) logContentf(level Level, format string, v ...interface{}) { - if level <= l.level || level == LogPrint { - //nolint: forbidigo - fmt.Printf("%s"+l.prefix+format+"\n", append([]interface{}{ - _out[level], - }, v...)...) - } -} - -// Printf implement the Log interface. -func (l logger) Printf(format string, v ...interface{}) { - l.logContentf(LogPrint, format, v...) -} - -// Debugf implement the Log interface. -func (l logger) Debugf(format string, v ...interface{}) { - l.logContentf(LogDebug, format, v...) -} - -// Infof implement the Log interface. -func (l logger) Infof(format string, v ...interface{}) { - l.logContentf(LogInfo, format, v...) -} - -// Warnf implement the Log interface. -func (l logger) Warnf(format string, v ...interface{}) { - l.logContentf(LogWarning, format, v...) -} - -// Errorf implement the Log interface. -func (l logger) Errorf(format string, v ...interface{}) { - l.logContentf(LogErr, format, v...) -} - -// Fatalf implement the Log interface. -func (l logger) Fatalf(format string, v ...interface{}) { - l.logContentf(LogErr, format, v...) - panic(fmt.Sprintf(format, v...)) -} diff --git a/options.go b/options.go index 9f8fcfe..0fa8aec 100644 --- a/options.go +++ b/options.go @@ -2,11 +2,11 @@ package webfmwk import ( "context" + "log/slog" "net/http" "sync" "time" - "github.com/burgesQ/log" "github.com/valyala/fasthttp" ) @@ -14,7 +14,7 @@ type ( // Option apply specific configuration to the server at init time // They are tu be used this way : // s := w.InitServer( - // webfmwk.WithLogger(log.GetLogger()), + // webfmwk.WithStructuredLogger(slog.Default()), // webfmwk.WithCtrlC(), // webfmwk.CheckIsUp(), // webfmwk.WithCORS(), @@ -89,7 +89,7 @@ func InitServer(opts ...Option) (*Server, error) { ctx: ctx, cancel: cancel, wg: &wg, - log: fetchLogger(), + slog: slog.Default(), isReady: make(chan bool), meta: getDefaultMeta(), } @@ -104,16 +104,16 @@ func InitServer(opts ...Option) (*Server, error) { func WithHTTP2() Option { return func(s *Server) { s.meta.http2 = true - s.log.Debugf("\t-- http2 enabled") + s.slog.Debug("\t-- http2 enabled") } } -// WithLogger set the server logger which implement the log.Log interface -// Try to set it the earliest posible. -func WithLogger(lg log.Log) Option { +// WithStructuredLogger set the server structured logger which derive from slog.Logger. +// Try to set it the earliest possible. +func WithStructuredLogger(slg *slog.Logger) Option { return func(s *Server) { - s.registerLogger(lg) - lg.Debugf("\t-- logger loaded") + s.registerStructuredLogger(slg) + slg.Debug("logger loaded", "webfmwk", "init:option") } } @@ -121,7 +121,7 @@ func WithLogger(lg log.Log) Option { func WithCtrlC() Option { return func(s *Server) { s.enableCtrlC() - s.log.Debugf("\t-- crtl-c support enabled") + s.slog.Debug("\t-- crtl-c support enabled") } } @@ -130,7 +130,7 @@ func WithCtrlC() Option { func CheckIsUp() Option { return func(s *Server) { s.EnableCheckIsUp() - s.log.Debugf("\t-- check is up support enabled") + s.slog.Debug("\t-- check is up support enabled") } } @@ -138,7 +138,7 @@ func CheckIsUp() Option { func WithCORS() Option { return func(s *Server) { s.enableCORS() - s.log.Debugf("\t-- CORS support enabled") + s.slog.Debug("\t-- CORS support enabled") } } @@ -146,7 +146,7 @@ func WithCORS() Option { func SetPrefix(prefix string) Option { return func(s *Server) { s.setPrefix(prefix) - s.log.Debugf("\t-- api prefix loaded") + s.slog.Debug("\t-- api prefix loaded") } } @@ -167,7 +167,7 @@ func SetPrefix(prefix string) Option { func WithDocHandlers(handler ...DocHandler) Option { return func(s *Server) { s.addDocHandlers(handler...) - s.log.Debugf("\t-- doc handlers loaded") + s.slog.Debug("\t-- doc handlers loaded") } } @@ -197,7 +197,7 @@ func WithDocHandlers(handler ...DocHandler) Option { func WithHandlers(h ...Handler) Option { return func(s *Server) { s.addHandlers(h...) - s.log.Debugf("\t-- handlers loaded") + s.slog.Debug("\t-- handlers loaded") } } @@ -209,7 +209,7 @@ func WithHandlers(h ...Handler) Option { func SetReadTimeout(val time.Duration) Option { return func(s *Server) { s.meta.baseServer.ReadTimeout = val - s.log.Debugf("\t-- read timeout loaded") + s.slog.Debug("\t-- read timeout loaded") } } @@ -222,7 +222,7 @@ func SetReadTimeout(val time.Duration) Option { func SetWriteTimeout(val time.Duration) Option { return func(s *Server) { s.meta.baseServer.WriteTimeout = val - s.log.Debugf("\t-- write timeout loaded") + s.slog.Debug("\t-- write timeout loaded") } } @@ -230,7 +230,7 @@ func SetWriteTimeout(val time.Duration) Option { func SetIDLETimeout(val time.Duration) Option { return func(s *Server) { s.meta.baseServer.IdleTimeout = val - s.log.Debugf("\t-- idle aka keepalive timeout loaded") + s.slog.Debug("\t-- idle aka keepalive timeout loaded") } } @@ -238,7 +238,7 @@ func SetIDLETimeout(val time.Duration) Option { func EnableKeepAlive() Option { return func(s *Server) { s.meta.enableKeepAlive = true - s.log.Debugf("\t-- keepalive disabled") + s.slog.Debug("\t-- keepalive disabled") } } @@ -251,14 +251,14 @@ func EnablePprof(path ...string) Option { s.meta.pprofPath = path[0] } - s.log.Debugf("\t-- pprof endpoint enabled") + s.slog.Debug("\t-- pprof endpoint enabled") } } func MaxRequestBodySize(size int) Option { return func(s *Server) { s.meta.baseServer.MaxRequestBodySize = size - s.log.Debugf("\t-- request max body size set to %d", size) + s.slog.Debug("\t-- request max body size set", "value", size) } } @@ -271,14 +271,14 @@ const ( func WithSocketHandlerFunc(path string, hf http.HandlerFunc) Option { return func(s *Server) { s.meta.socketIOHandlerFunc, s.meta.socketIOPath, s.meta.socketIOHF = hf, path, true - s.log.Debugf("\t-- socket io handler func loaded") + s.slog.Debug("\t-- socket io handler func loaded") } } func WithSocketHandler(path string, h http.Handler) Option { return func(s *Server) { s.meta.socketIOHandler, s.meta.socketIOPath, s.meta.socketIOH = h, path, true - s.log.Debugf("\t-- socket io handlers loaded") + s.slog.Debug("\t-- socket io handlers loaded") } } diff --git a/poll_ping.go b/poll_ping.go index 90f49fb..85f8c12 100644 --- a/poll_ping.go +++ b/poll_ping.go @@ -39,7 +39,7 @@ func (s *Server) pollPingEndpoint(addr string, cfg ...tls.IConfig) { } defer func() { - s.log.Infof("server is up") + s.slog.Info("server is up") s.isReady <- true }() @@ -62,7 +62,7 @@ func (s *Server) pollPingEndpoint(addr string, cfg ...tls.IConfig) { return } - s.log.Infof("server not up (%q): %T %v", uri, e, str) + s.slog.Info("server not up", "uri", uri, "error", e) continue } diff --git a/response.go b/response.go index d14f949..ee602c9 100644 --- a/response.go +++ b/response.go @@ -55,7 +55,7 @@ func (c *icontext) setHeaders(headers ...Header) { for _, h := range headers { key, val := h[0], h[1] if key == "" || val == "" { - c.log.Warnf("can't set header [%s] to [%s] (empty value)", key, val) + c.slog.Warn("can't set header: empty value", "key", key, "val", val) return } diff --git a/response_json.go b/response_json.go index dc129a9..e6ef0f0 100644 --- a/response_json.go +++ b/response_json.go @@ -76,7 +76,7 @@ func (c *icontext) JSONBlob(statusCode int, content []byte) error { ) if e := run(); e != nil { - c.log.Errorf("canno't pretting the content : %s", e.Error()) + c.slog.Error("cannot prettying the content", "error", e) } else { content = out.Bytes() } diff --git a/route.go b/route.go index 989e0ae..8cc40af 100644 --- a/route.go +++ b/route.go @@ -190,7 +190,7 @@ func (s *Server) RouteApplier(rpps ...RoutesPerPrefix) { case ANY: s.ANY(prefix+route.Path, route.Handler) default: - s.log.Warnf("Cannot load route [%s](%s)", prefix+route.Path, route.Verbe) + s.slog.Warn("Cannot load route [%s](%s)", "route", prefix+route.Path, "verbe", route.Verbe) } } } @@ -215,7 +215,7 @@ func (s *Server) GetRouter() *router.Router { if len(s.meta.docHandlers) > 0 { for i := range s.meta.docHandlers { h := s.meta.docHandlers[i] - s.log.Infof("load %q doc handler", h.Name) + s.slog.Info("load doc handler", "name", h.Name) r.ANY(s.meta.prefix+h.Path, s.CustomHandler(h.H)) } } @@ -230,17 +230,17 @@ func (s *Server) GetRouter() *router.Router { // register socket.io (goplog) handlers switch { case s.meta.socketIOHF: - s.log.Infof("loading socket io handler func on %q", s.meta.socketIOPath) + s.slog.Info("loading socket io handler func", "path", s.meta.socketIOPath) r.ANY(s.meta.socketIOPath, fasthttpadaptor.NewFastHTTPHandlerFunc(s.meta.socketIOHandlerFunc)) case s.meta.socketIOH: - s.log.Infof("loading socket io handler on %q", s.meta.socketIOPath) + s.slog.Info("loading socket io handler", "path", s.meta.socketIOPath) r.ANY(s.meta.socketIOPath, fasthttpadaptor.NewFastHTTPHandler(s.meta.socketIOHandler)) } if s.meta.pprof { - s.log.Infof("loading pprof handler on '/debug/pprof/{profile:*}'") + s.slog.Info("loading pprof handler", "path", "/debug/pprof/{profile:*}'") r.GET(s.meta.prefix+s.meta.pprofPath, pprofhandler.PprofHandler) } @@ -305,5 +305,5 @@ func (s *Server) CustomHandler(handler HandlerFunc) fasthttp.RequestHandler { func (s *Server) genContext(c *fasthttp.RequestCtx) (Context, context.CancelFunc) { ctx, fn := context.WithCancel(s.ctx) - return &icontext{c, s.log, ctx}, fn + return &icontext{c, s.slog, ctx}, fn } diff --git a/server.go b/server.go index 25d928c..7ecb3e1 100644 --- a/server.go +++ b/server.go @@ -3,14 +3,13 @@ package webfmwk import ( "context" "fmt" + "log/slog" "os" "os/signal" "strings" "sync" "syscall" - "github.com/burgesQ/log" - wlog "github.com/burgesQ/webfmwk/v6/log" "github.com/burgesQ/webfmwk/v6/tls" fasthttp2 "github.com/dgrr/http2" "github.com/lab259/cors" @@ -24,9 +23,10 @@ type ( cancel context.CancelFunc wg *sync.WaitGroup launcher WorkerLauncher - log log.Log - isReady chan bool - meta serverMeta + // log log.Log + slog *slog.Logger + isReady chan bool + meta serverMeta } ) @@ -46,19 +46,21 @@ func (s *Server) Run(addrs ...Address) { for i := range addrs { addr := addrs[i] if !addr.IsOk() { - s.GetLogger().Errorf("invalid address format : %s", addr) + s.GetStructuredLogger().Error("invalid format", "address", addr) continue } if cfg := addr.GetTLS(); cfg != nil && !cfg.Empty() { - s.GetLogger().Infof("starting %s on https://%s", addr.GetName(), addr.GetAddr()) + s.GetStructuredLogger().Info("starting https server", + "name", addr.GetName(), "address", "https://"+addr.GetAddr()) s.StartTLS(addr.GetAddr(), cfg) continue } - s.GetLogger().Infof("starting %s on http://%s", addr.GetName(), addr.GetAddr()) + s.GetStructuredLogger().Info("starting http server", + "name", addr.GetName(), "address", "http://"+addr.GetAddr()) s.Start(addr.GetAddr()) } } @@ -67,8 +69,6 @@ func (s *Server) Run(addrs ...Address) { // Global methods // -func fetchLogger() log.Log { return wlog.GetLogger() } - // Shututdown terminate all running servers. func Shutdown() error { poolMu.Lock() @@ -98,21 +98,22 @@ func Shutdown() error { // Start expose an server to an HTTP endpoint. func (s *Server) Start(addr string) { if s.meta.http2 { - s.log.Warnf("https endpoints required with http2, skipping %q", addr) + s.slog.Warn("https endpoints required with http2, skipping", "address", addr) return } s.internalHandler() s.launcher.Start(func() { - s.log.Debugf("http server %s: starting", addr) + s.slog.Debug("http server: starting", "address", addr) go s.pollPingEndpoint(addr) if e := s.internalInit(addr).ListenAndServe(addr); e != nil { - s.log.Errorf("http server %s (%T): %s", addr, e, e) + s.slog.Error("http server", "address", addr, "error", e) } - s.log.Infof("http server %s: done", addr) + + s.slog.Info("http server: done", "address", addr) }) } @@ -123,31 +124,33 @@ func (s *Server) StartTLS(addr string, cfg tls.IConfig) { tlsCfg, err := tls.GetTLSCfg(cfg, s.meta.http2) if err != nil { - s.log.Fatalf("loading tls config: %v", err) + s.slog.Error("loading tls config", "error", err) + os.Exit(2) } listner, err := tls.LoadListner(addr, tlsCfg) if err != nil { - s.log.Fatalf("loading tls listener: %v", err) + s.slog.Error("loading tls listener", "error", err) + os.Exit(3) } server := s.internalInit(addr) if s.meta.http2 { - s.log.Infof("loading http2 support") + s.slog.Info("loading http2 support") fasthttp2.ConfigureServer(server, fasthttp2.ServerConfig{Debug: true}) } so2 := sOr2(s.meta.http2) s.launcher.Start(func() { - s.log.Debugf("%s server %s: starting", so2, addr) - defer s.log.Infof("%s server %s: done", so2, addr) + s.slog.Debug(fmt.Sprintf("%s server: starting", so2), "address", addr) + defer s.slog.Info(fmt.Sprintf("%s server: done", so2), "address", addr) go s.pollPingEndpoint(addr) if e := server.Serve(listner); e != nil { - s.log.Errorf("%s server %s (%T): %s", so2, addr, e, e) + s.slog.Error(fmt.Sprintf("%s server", so2), "address", addr, "error", e) } }) } @@ -186,13 +189,19 @@ func (s *Server) DumpRoutes() map[string][]string { for m, p := range all { for i := range p { - s.log.Infof("routes: [%s]%s", m, p[i]) + s.slog.Info("routes", "name", m, "route", p[i]) } } return all } +type FastLogger struct{ *slog.Logger } + +func (flg *FastLogger) Printf(msg string, keys ...any) { + flg.Info(msg, keys...) +} + // Initialize a http.Server struct. Save the server in the pool of workers. func (s *Server) internalInit(addr string) *fasthttp.Server { var ( @@ -213,7 +222,7 @@ func (s *Server) internalInit(addr string) *fasthttp.Server { worker.Handler = router.Handler } - worker.Logger = s.log + worker.Logger = &FastLogger{s.slog} // save the server poolMu.Lock() @@ -221,7 +230,7 @@ func (s *Server) internalInit(addr string) *fasthttp.Server { poolOfServers = append(poolOfServers, worker) - s.log.Debugf("[+] server %d (%s) ", len(poolOfServers), addr) + s.slog.Debug("[+] server ", "address", addr, "total", len(poolOfServers)) return worker } @@ -240,9 +249,9 @@ func concatAddr(addr, prefix string) string { func (s *Server) internalHandler() { if s.meta.ctrlc && !s.meta.ctrlcStarted { s.launcher.Start(func() { - s.log.Debugf("exit handler: starting") + s.slog.Debug("exit handler: starting") s.exitHandler(os.Interrupt, syscall.SIGHUP) - s.log.Infof("exit handler: done") + s.slog.Info("exit handler: done") }) s.meta.ctrlcStarted = true @@ -257,14 +266,14 @@ func (s *Server) exitHandler(sig ...os.Signal) { defer func() { if e := s.Shutdown(); e != nil { - s.log.Errorf("cannot stop the server: %v", e) + s.slog.Error("cannot stop the server", "error", e) } }() for s.ctx.Err() == nil { select { case si := <-c: - s.log.Infof("captured %v, exiting...", si) + s.slog.Info("captured signal, exiting...", "signal", si) return case <-s.ctx.Done(): @@ -278,8 +287,8 @@ func (s *Server) exitHandler(sig ...os.Signal) { // // GetLogger return the used Log instance. -func (s *Server) GetLogger() log.Log { - return s.log +func (s *Server) GetStructuredLogger() *slog.Logger { + return s.slog } // GetLauncher return a pointer to the internal workerLauncher. @@ -324,8 +333,8 @@ func (s *Server) setPrefix(prefix string) *Server { } // RegisterLogger register the Log used. -func (s *Server) registerLogger(lg log.Log) *Server { - s.log = lg +func (s *Server) registerStructuredLogger(slg *slog.Logger) *Server { + s.slog = slg return s } diff --git a/server_test.go b/server_test.go index 28ccd98..d23e70f 100644 --- a/server_test.go +++ b/server_test.go @@ -3,7 +3,6 @@ package webfmwk import ( "testing" - "github.com/burgesQ/log" "github.com/stretchr/testify/require" ) @@ -47,28 +46,6 @@ func TestDumpRoutes(t *testing.T) { // require.Contains(t, all, "OPTIONS") } -type customLoggerT struct{} - -func (l customLoggerT) Printf(format string, args ...interface{}) {} -func (l customLoggerT) Debugf(format string, v ...interface{}) {} -func (l customLoggerT) Infof(format string, v ...interface{}) {} -func (l customLoggerT) Warnf(format string, v ...interface{}) {} -func (l customLoggerT) Errorf(format string, v ...interface{}) {} -func (l customLoggerT) Fatalf(format string, v ...interface{}) {} -func (l customLoggerT) SetPrefix(prefix string) log.Log { return l } -func (l customLoggerT) GetPrefix() string { return "" } - -func TestRegisterLogger(t *testing.T) { - var ( - lg = new(customLoggerT) - s, e = InitServer(WithLogger(lg)) - ) - - require.Nil(t, e) - require.Implements(t, (*log.Log)(nil), lg) - require.Equal(t, lg, s.GetLogger()) -} - func TestGetLauncher(t *testing.T) { s, e := InitServer(CheckIsUp())