mirror of
https://github.com/Fishwaldo/huma.git
synced 2025-03-15 19:31:27 +00:00
feat: log middleware
This commit is contained in:
parent
401e4d59ea
commit
77a0bc9072
5 changed files with 77 additions and 39 deletions
|
@ -21,6 +21,7 @@ Features include:
|
|||
- Default middleware
|
||||
- Automatic recovery from panics
|
||||
- Automatically handle CORS headers
|
||||
- Structured logging middleware using [Zap](https://github.com/uber-go/zap)
|
||||
- Annotated Go types for input and output models
|
||||
- Automatic input model validation
|
||||
- Dependency injection for loggers, datastores, etc
|
||||
|
|
4
go.mod
4
go.mod
|
@ -11,10 +11,12 @@ require (
|
|||
github.com/gin-contrib/cors v1.3.1
|
||||
github.com/gin-gonic/gin v1.5.0
|
||||
github.com/gosimple/slug v1.9.0
|
||||
github.com/rs/zerolog v1.18.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.9
|
||||
github.com/rs/zerolog v1.18.0
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/spf13/cobra v0.0.6
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
go.uber.org/zap v1.10.0
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -97,6 +97,7 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
|
|||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
|
@ -159,8 +160,11 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
|
|||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
|
66
middelware.go
Normal file
66
middelware.go
Normal file
|
@ -0,0 +1,66 @@
|
|||
package huma
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/mattn/go-isatty"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// LogMiddleware creates a new middleware to set a tagged `*zap.SugarLogger` in the
|
||||
// Gin context under the `log` key. It debug logs request info. If the current
|
||||
// terminal is a TTY, it will try to use colored output automatically.
|
||||
func LogMiddleware() func(*gin.Context) {
|
||||
var l *zap.Logger
|
||||
var err error
|
||||
if isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd()) {
|
||||
config := zap.NewDevelopmentConfig()
|
||||
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
l, err = config.Build()
|
||||
} else {
|
||||
l, err = zap.NewProduction()
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
contextLog := l.With(
|
||||
zap.String("method", c.Request.Method),
|
||||
zap.String("template", c.FullPath()),
|
||||
zap.String("path", c.Request.URL.RequestURI()),
|
||||
zap.String("ip", c.ClientIP()),
|
||||
)
|
||||
c.Set("log", contextLog.Sugar())
|
||||
|
||||
c.Next()
|
||||
|
||||
contextLog.Debug("Request",
|
||||
zap.Int("status", c.Writer.Status()),
|
||||
zap.Duration("duration", time.Since(start)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// LogDependency returns a dependency that resolves to a `*zap.SugaredLogger`
|
||||
// for the current request. This dependency *requires* the use of
|
||||
// `LogMiddleware` and will error if the logger is not in the request context.
|
||||
func LogDependency() *Dependency {
|
||||
return &Dependency{
|
||||
Depends: []*Dependency{ContextDependency(), OperationDependency()},
|
||||
Value: func(c *gin.Context, op *Operation) (*zap.SugaredLogger, error) {
|
||||
l, ok := c.Get("log")
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("missing logger in context")
|
||||
}
|
||||
sl := l.(*zap.SugaredLogger).With("operation", op.ID)
|
||||
sl.Desugar()
|
||||
return sl, nil
|
||||
},
|
||||
}
|
||||
}
|
41
router.go
41
router.go
|
@ -132,12 +132,13 @@ type Router struct {
|
|||
|
||||
// NewRouter creates a new Huma router for handling API requests with
|
||||
// default middleware and routes attached. This is equivalent to calling
|
||||
// `NewRouterWithGin` with a new Gin instance with just the recovery and
|
||||
// CORS (allowing all origins) middlewares.
|
||||
// `NewRouterWithGin` with a new Gin instance with just the recovery,
|
||||
// CORS (allowing all origins), and log middlewares.
|
||||
func NewRouter(api *OpenAPI) *Router {
|
||||
g := gin.New()
|
||||
g.Use(gin.Recovery())
|
||||
g.Use(cors.Default())
|
||||
g.Use(LogMiddleware())
|
||||
return NewRouterWithGin(g, api)
|
||||
}
|
||||
|
||||
|
@ -192,42 +193,6 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
r.engine.ServeHTTP(w, req)
|
||||
}
|
||||
|
||||
// Dependency registers a new dependency type to be injected into handler
|
||||
// functions, e.g. for loggers, metrics, datastores, etc. Provide a value
|
||||
// or a function to return a contextual value/error. Dependency functions
|
||||
// can take their own dependencies. To prevent circular dependency loops,
|
||||
// a function can only depend on previously defined dependencies.
|
||||
//
|
||||
// Some dependency types are built in:
|
||||
// - `*gin.Context` the current Gin request execution context
|
||||
// - `*huma.Operation` the current Huma operation
|
||||
//
|
||||
// // Register a global dependency like a datastore
|
||||
// router.Dependency(&MyDB{...})
|
||||
//
|
||||
// // Register a contextual dependency like a logger
|
||||
// router.Dependency(func (c *gin.Context) (*MyLogger, error) {
|
||||
// return &MyLogger{Tags: []string{c.Request.RemoteAddr}}, nil
|
||||
// })
|
||||
//
|
||||
// Then use the dependency in a handler function:
|
||||
//
|
||||
// router.Register(&huma.Operation{
|
||||
// ...
|
||||
// Handler: func(db *MyDB, log *MyLogger) *MyItem {
|
||||
// item := db.GetItem("some-id")
|
||||
// log.Info("Got item!")
|
||||
// return item
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// Panics on invalid input to force stop execution on service startup.
|
||||
// func (r *Router) Dependency(f interface{}) {
|
||||
// if err := r.deps.Add(f); err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// Register a new operation.
|
||||
func (r *Router) Register(op *Operation) {
|
||||
// First, make sure the operation and handler make sense, as well as pre-
|
||||
|
|
Loading…
Add table
Reference in a new issue