diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 9557887b..ad4eb124 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -28,7 +28,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - name: Build run: make build diff --git a/README.rst b/README.rst index 0de33111..b0fd960e 100644 --- a/README.rst +++ b/README.rst @@ -920,7 +920,6 @@ Levels available are: * info * error * warning -* fatal Allowed sizes ------------- diff --git a/constants/constants.go b/constants/constants.go index 015dc05f..0587c677 100644 --- a/constants/constants.go +++ b/constants/constants.go @@ -41,3 +41,5 @@ var StickPositions = []string{ } const ModifiedTimeFormat = time.RFC1123 + +const RequestIDCtx = "request-id" diff --git a/engine/backend/backend.go b/engine/backend/backend.go index 4ab177c2..313d31c6 100644 --- a/engine/backend/backend.go +++ b/engine/backend/backend.go @@ -1,6 +1,7 @@ package backend import ( + "context" "fmt" "github.com/disintegration/imaging" @@ -32,11 +33,11 @@ func (o Options) String() string { // Engine is an interface to define an image engine type Backend interface { - Fit(img *image.ImageFile, options *Options) ([]byte, error) - Flat(background *image.ImageFile, options *Options) ([]byte, error) - Flip(img *image.ImageFile, options *Options) ([]byte, error) - Resize(img *image.ImageFile, options *Options) ([]byte, error) - Rotate(img *image.ImageFile, options *Options) ([]byte, error) + Fit(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) + Flat(ctx context.Context, background *image.ImageFile, options *Options) ([]byte, error) + Flip(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) + Resize(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) + Rotate(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) String() string - Thumbnail(img *image.ImageFile, options *Options) ([]byte, error) + Thumbnail(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) } diff --git a/engine/backend/gifsicle.go b/engine/backend/gifsicle.go index 2ecd9c0d..3fba6238 100644 --- a/engine/backend/gifsicle.go +++ b/engine/backend/gifsicle.go @@ -2,6 +2,7 @@ package backend import ( "bytes" + "context" "errors" "fmt" "image/gif" @@ -20,7 +21,7 @@ func (b *Gifsicle) String() string { } // Resize implements Backend. -func (b *Gifsicle) Resize(imgfile *image.ImageFile, opts *Options) ([]byte, error) { +func (b *Gifsicle) Resize(ctx context.Context, imgfile *image.ImageFile, opts *Options) ([]byte, error) { img, err := gif.Decode(bytes.NewReader(imgfile.Source)) if err != nil { return nil, err @@ -30,7 +31,7 @@ func (b *Gifsicle) Resize(imgfile *image.ImageFile, opts *Options) ([]byte, erro return imgfile.Source, nil } - cmd := exec.Command(b.Path, + cmd := exec.CommandContext(ctx, b.Path, "--resize", fmt.Sprintf("%dx%d", opts.Width, opts.Height), ) cmd.Stdin = bytes.NewReader(imgfile.Source) @@ -49,7 +50,7 @@ func (b *Gifsicle) Resize(imgfile *image.ImageFile, opts *Options) ([]byte, erro } // Thumbnail implements Backend. -func (b *Gifsicle) Thumbnail(imgfile *image.ImageFile, opts *Options) ([]byte, error) { +func (b *Gifsicle) Thumbnail(ctx context.Context, imgfile *image.ImageFile, opts *Options) ([]byte, error) { img, err := gif.Decode(bytes.NewReader(imgfile.Source)) if err != nil { return nil, err @@ -62,7 +63,7 @@ func (b *Gifsicle) Thumbnail(imgfile *image.ImageFile, opts *Options) ([]byte, e bounds := img.Bounds() left, top, cropw, croph := computecrop(bounds.Dx(), bounds.Dy(), opts.Width, opts.Height) - cmd := exec.Command(b.Path, + cmd := exec.CommandContext(ctx, b.Path, "--crop", fmt.Sprintf("%d,%d+%dx%d", left, top, cropw, croph), "--resize", fmt.Sprintf("%dx%d", opts.Width, opts.Height), ) @@ -82,22 +83,22 @@ func (b *Gifsicle) Thumbnail(imgfile *image.ImageFile, opts *Options) ([]byte, e } // Rotate implements Backend. -func (b *Gifsicle) Rotate(*image.ImageFile, *Options) ([]byte, error) { +func (b *Gifsicle) Rotate(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) { return nil, MethodNotImplementedError } // Fit implements Backend. -func (b *Gifsicle) Fit(*image.ImageFile, *Options) ([]byte, error) { +func (b *Gifsicle) Fit(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) { return nil, MethodNotImplementedError } // Flat implements Backend. -func (b *Gifsicle) Flat(*image.ImageFile, *Options) ([]byte, error) { +func (b *Gifsicle) Flat(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) { return nil, MethodNotImplementedError } // Flip implements Backend. -func (b *Gifsicle) Flip(*image.ImageFile, *Options) ([]byte, error) { +func (b *Gifsicle) Flip(ctx context.Context, img *image.ImageFile, options *Options) ([]byte, error) { return nil, MethodNotImplementedError } diff --git a/engine/backend/goimage.go b/engine/backend/goimage.go index 7a867dfd..df37456a 100644 --- a/engine/backend/goimage.go +++ b/engine/backend/goimage.go @@ -2,6 +2,7 @@ package backend import ( "bytes" + "context" "fmt" "image" "image/color/palette" @@ -43,15 +44,15 @@ type GoImage struct{} func (e *GoImage) String() string { return "goimage" } -func (e *GoImage) Resize(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Resize(ctx context.Context, img *imagefile.ImageFile, options *Options) ([]byte, error) { return e.resize(img, options, imaging.Resize) } -func (e *GoImage) Thumbnail(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Thumbnail(ctx context.Context, img *imagefile.ImageFile, options *Options) ([]byte, error) { return e.resize(img, options, imaging.Thumbnail) } -func (e *GoImage) Rotate(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Rotate(ctx context.Context, img *imagefile.ImageFile, options *Options) ([]byte, error) { image, err := e.source(img) if err != nil { return nil, err @@ -67,7 +68,7 @@ func (e *GoImage) Rotate(img *imagefile.ImageFile, options *Options) ([]byte, er return e.toBytes(transform(image), options.Format, options.Quality) } -func (e *GoImage) Flip(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Flip(ctx context.Context, img *imagefile.ImageFile, options *Options) ([]byte, error) { image, err := e.source(img) if err != nil { return nil, err @@ -83,7 +84,7 @@ func (e *GoImage) Flip(img *imagefile.ImageFile, options *Options) ([]byte, erro return e.toBytes(transform(image), options.Format, options.Quality) } -func (e *GoImage) Fit(img *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Fit(ctx context.Context, img *imagefile.ImageFile, options *Options) ([]byte, error) { if options.Format == imaging.GIF { content, err := e.transformGIF(img, options, imaging.Thumbnail) if err != nil { diff --git a/engine/backend/goimage_flat.go b/engine/backend/goimage_flat.go index 9ce974b0..01347398 100644 --- a/engine/backend/goimage_flat.go +++ b/engine/backend/goimage_flat.go @@ -2,6 +2,7 @@ package backend import ( "bytes" + "context" "fmt" "image" "image/draw" @@ -16,7 +17,7 @@ import ( imagefile "github.com/thoas/picfit/image" ) -func (e *GoImage) Flat(backgroundFile *imagefile.ImageFile, options *Options) ([]byte, error) { +func (e *GoImage) Flat(ctx context.Context, backgroundFile *imagefile.ImageFile, options *Options) ([]byte, error) { var err error images := make([]image.Image, len(options.Images)) for i := range options.Images { diff --git a/engine/engine.go b/engine/engine.go index 3155c8b4..4caf2792 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -1,16 +1,16 @@ package engine import ( + "context" "fmt" - "os/exec" - "sort" - "strings" - "github.com/thoas/picfit/engine/backend" "github.com/thoas/picfit/engine/config" "github.com/thoas/picfit/image" - "github.com/thoas/picfit/logger" - "go.uber.org/zap" + "log/slog" + "os/exec" + "sort" + "strings" + "time" ) type Engine struct { @@ -18,7 +18,7 @@ type Engine struct { DefaultQuality int Format string backends []*backendWrapper - logger *zap.Logger + logger *slog.Logger } type backendWrapper struct { @@ -28,7 +28,7 @@ type backendWrapper struct { } // New initializes an Engine -func New(cfg config.Config, logger *zap.Logger) *Engine { +func New(cfg config.Config, logger *slog.Logger) *Engine { var b []*backendWrapper if cfg.Backends == nil { @@ -87,11 +87,12 @@ func (e Engine) String() string { return strings.Join(backendNames, " ") } -func (e Engine) Transform(output *image.ImageFile, operations []EngineOperation) (*image.ImageFile, error) { +func (e Engine) Transform(ctx context.Context, output *image.ImageFile, operations []EngineOperation) (*image.ImageFile, error) { var ( err error processed []byte source = output.Source + start = time.Now() ) ct := output.ContentType() @@ -109,13 +110,16 @@ func (e Engine) Transform(output *image.ImageFile, operations []EngineOperation) continue } - e.logger.Debug("Processing image...", - logger.String("backend", e.backends[j].backend.String()), - logger.String("operation", operations[i].Operation.String()), - logger.String("options", operations[i].Options.String()), - ) + defer func() { + e.logger.InfoContext(ctx, "Processing image", + slog.String("backend", e.backends[j].backend.String()), + slog.String("operation", operations[i].Operation.String()), + slog.String("options", operations[i].Options.String()), + slog.String("duration", time.Now().Sub(start).String()), + ) + }() - processed, err = operate(e.backends[j].backend, output, operations[i].Operation, operations[i].Options) + processed, err = operate(ctx, e.backends[j].backend, output, operations[i].Operation, operations[i].Options) if err == nil { output.Source = processed break @@ -132,22 +136,22 @@ func (e Engine) Transform(output *image.ImageFile, operations []EngineOperation) return output, err } -func operate(b backend.Backend, img *image.ImageFile, operation Operation, options *backend.Options) ([]byte, error) { +func operate(ctx context.Context, b backend.Backend, img *image.ImageFile, operation Operation, options *backend.Options) ([]byte, error) { switch operation { case Noop: return img.Source, nil case Flip: - return b.Flip(img, options) + return b.Flip(ctx, img, options) case Rotate: - return b.Rotate(img, options) + return b.Rotate(ctx, img, options) case Resize: - return b.Resize(img, options) + return b.Resize(ctx, img, options) case Thumbnail: - return b.Thumbnail(img, options) + return b.Thumbnail(ctx, img, options) case Fit: - return b.Fit(img, options) + return b.Fit(ctx, img, options) case Flat: - return b.Flat(img, options) + return b.Flat(ctx, img, options) default: return nil, fmt.Errorf("Operation not found for %s", operation) } diff --git a/go.mod b/go.mod index c8a369b8..aafcb4b2 100644 --- a/go.mod +++ b/go.mod @@ -25,9 +25,6 @@ require ( github.com/ulule/gokvstores v0.1.1-0.20221229151109-3bd12fb72ebe github.com/ulule/gostorages v0.2.5-0.20230314124119-11134a4bce61 github.com/urfave/cli v1.22.10 - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.24.0 golang.org/x/image v0.6.0 google.golang.org/protobuf v1.30.0 // indirect gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect @@ -35,7 +32,7 @@ require ( ) require ( - github.com/gin-contrib/zap v0.1.0 + github.com/google/uuid v1.3.0 github.com/prometheus/client_golang v1.14.0 ) @@ -64,6 +61,7 @@ require ( github.com/jstemmer/go-junit-report v0.9.1 // indirect github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88 // indirect github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/kr/pretty v0.3.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -77,6 +75,7 @@ require ( github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/common v0.37.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -85,8 +84,6 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect go.opencensus.io v0.23.0 // indirect - go.opentelemetry.io/otel v1.10.0 // indirect - go.opentelemetry.io/otel/trace v1.10.0 // indirect golang.org/x/arch v0.3.0 // indirect golang.org/x/crypto v0.9.0 // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect @@ -100,9 +97,10 @@ require ( google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c // indirect google.golang.org/grpc v1.38.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.19 +go 1.21 diff --git a/go.sum b/go.sum index db09a1b7..e13273f2 100644 --- a/go.sum +++ b/go.sum @@ -53,8 +53,6 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo= github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -112,11 +110,8 @@ github.com/getsentry/sentry-go v0.19.0/go.mod h1:y3+lGEFEFexZtpbG1GUE2WD/f9zGyKY github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-contrib/zap v0.1.0 h1:RMSFFJo34XZogV62OgOzvrlaMNmXrNxmJ3bFmMwl6Cc= -github.com/gin-contrib/zap v0.1.0/go.mod h1:hvnZaPs478H1PGvRP8w89ZZbyJUiyip4ddiI/53WG3o= github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19 h1:J2LPEOcQmWaooBnBtUDV9KHFEnP5LYTZY03GiQ0oQBw= github.com/gin-gonic/contrib v0.0.0-20201101042839-6a891bf89f19/go.mod h1:iqneQ2Df3omzIVTkIfn7c1acsVnMGiSLn4XF5Blh3Yg= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -131,18 +126,11 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 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.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= @@ -150,7 +138,6 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -200,7 +187,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= @@ -222,6 +208,8 @@ github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -288,7 +276,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lucasb-eyer/go-colorful v0.0.0-20180709185858-c7842319cf3a h1:B2QfFRl5yGVGGcyEVFzfdXlC1BBvszsIAsCeef2oD0k= @@ -299,7 +286,6 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -345,7 +331,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -441,8 +426,6 @@ github.com/thoas/stats v0.0.0-20160726120248-152b5d051953 h1:PD6HdaGc9tn2a8W/33z github.com/thoas/stats v0.0.0-20160726120248-152b5d051953/go.mod h1:GkZsNBOco11YY68OnXUARbSl26IOXXAeYf6ZKmSZR2M= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ulule/gokvstores v0.1.1-0.20221229151109-3bd12fb72ebe h1:ayWYvm5FWr78c8RYS32fXoZ5DM+sn3ngVeE3CHY3beM= @@ -468,22 +451,9 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/otel v1.10.0 h1:Y7DTJMR6zs1xkS/upamJYk0SxxN4C9AqRd77jmZnyY4= -go.opentelemetry.io/otel v1.10.0/go.mod h1:NbvWjCthWHKBEUMpf0/v8ZRZlni86PpGFEMA9pnQSnQ= -go.opentelemetry.io/otel/trace v1.10.0 h1:npQMbR8o7mum8uF95yFbOEJffhs1sbCOfDh8zAJiH5E= -go.opentelemetry.io/otel/trace v1.10.0/go.mod h1:Sij3YYczqAdz+EhmGhE6TpTxUO5/F/AzrK+kxfGqySM= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -495,7 +465,6 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= @@ -673,8 +642,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -757,7 +724,6 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= @@ -871,7 +837,6 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= diff --git a/image/factory.go b/image/factory.go index 738d766c..8b13d8b2 100644 --- a/image/factory.go +++ b/image/factory.go @@ -12,10 +12,10 @@ import ( ) // FromURL retrieves an ImageFile from an url -func FromURL(u *url.URL, userAgent string) (*ImageFile, error) { +func FromURL(ctx context.Context, u *url.URL, userAgent string) (*ImageFile, error) { storage := storagepkg.NewHTTPStorage(nil, http.NewClient(http.WithUserAgent(userAgent))) - content, err := storage.OpenFromURL(u) + content, err := storage.OpenFromURL(ctx, u) if err != nil { return nil, err } diff --git a/logger/config.go b/logger/config.go index 29a95ef8..bec6bb07 100644 --- a/logger/config.go +++ b/logger/config.go @@ -1,23 +1,6 @@ package logger -const ( - DevelopmentLevel = "development" - ProductionLevel = "production" - - // defaultLevel is the default logger level - defaultLevel = DevelopmentLevel -) - -// Config is a struct to configure logger type Config struct { - Level string -} - -// GetLevel returns the level of the logger -func (l *Config) GetLevel() string { - if l.Level == "" { - return defaultLevel - } - - return l.Level + Level string `json:"level"` + ContextKeys []string } diff --git a/logger/logger.go b/logger/logger.go index c70dcfb9..4fefa075 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -1,70 +1,78 @@ package logger import ( - "time" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" + "context" + "log/slog" + "os" + "strings" ) -type Field = zapcore.Field -type ObjectEncoder = zapcore.ObjectEncoder - -func New(cfg Config) *zap.Logger { - var logger *zap.Logger - if cfg.GetLevel() == DevelopmentLevel { - logger, _ = NewDevelopmentLogger() - } else { - logger, _ = NewProductionLogger() - } - - return logger -} - -func String(k, v string) Field { - return zap.String(k, v) -} - -func Duration(k string, d time.Duration) Field { - return zap.Duration(k, d) -} - -func Float64(key string, val float64) Field { - return zap.Float64(key, val) +type LogHandler struct { + level slog.Leveler + sloghandler slog.Handler + contextKeys []string } -func Time(key string, val time.Time) Field { - return zap.Time(key, val) -} - -func Int(k string, i int) Field { - return zap.Int(k, i) +func (h LogHandler) Handle(ctx context.Context, record slog.Record) error { + if !h.Enabled(ctx, record.Level) { + return nil + } + contextfields := getContextFields(ctx, h.contextKeys) + if len(contextfields) > 0 { + record.AddAttrs(contextfields...) + } + return h.sloghandler.Handle(ctx, record) } -func Array(key string, val zapcore.ArrayMarshaler) Field { - return zap.Array(key, val) +func (h LogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { + return LogHandler{ + level: h.level, + sloghandler: h.sloghandler.WithAttrs(attrs), + contextKeys: h.contextKeys, + } } -func Int64(k string, i int64) Field { - return zap.Int64(k, i) +func (h LogHandler) WithGroup(name string) slog.Handler { + return &LogHandler{ + sloghandler: h.sloghandler.WithGroup(name), + } } -func Error(v error) Field { - return zap.Error(v) +func (h LogHandler) Enabled(_ context.Context, level slog.Level) bool { + return level >= h.level.Level() } -func Object(key string, val zapcore.ObjectMarshaler) Field { - return zap.Object(key, val) +func getContextFields(ctx context.Context, keys []string) []slog.Attr { + var fields []slog.Attr + for _, k := range keys { + value := ctx.Value(k) + if value != nil { + fields = append(fields, slog.Any(k, value)) + } + } + return fields } -func NewProductionLogger() (*zap.Logger, error) { - return zap.NewProduction() -} +func New(cfg Config) *slog.Logger { + var level slog.Level + switch strings.ToLower(cfg.Level) { + case "debug": + level = slog.LevelDebug + case "info": + level = slog.LevelInfo + case "warning": + level = slog.LevelWarn + case "error": + level = slog.LevelError + default: + level = slog.LevelDebug + } -func NewDevelopmentLogger() (*zap.Logger, error) { - return zap.NewDevelopment() -} + var opts = slog.HandlerOptions{Level: level} + return slog.New(LogHandler{ + contextKeys: cfg.ContextKeys, + level: opts.Level, + sloghandler: slog.NewJSONHandler(os.Stderr, &opts), + }) -func NewNopLogger() (*zap.Logger, error) { - return zap.NewNop(), nil } diff --git a/middleware/auth.go b/middleware/auth.go index 197398be..b3b1d6cc 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -1,6 +1,7 @@ package middleware import ( + "github.com/thoas/picfit/signature" "net/http" "strconv" @@ -8,7 +9,6 @@ import ( "github.com/thoas/go-funk" "github.com/thoas/picfit/config" - "github.com/thoas/picfit/signature" ) // Security wraps the request and confront sent parameters with secret key diff --git a/middleware/log.go b/middleware/log.go new file mode 100644 index 00000000..92696e4c --- /dev/null +++ b/middleware/log.go @@ -0,0 +1,71 @@ +package middleware + +import ( + "context" + "fmt" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/thoas/picfit/config" + "github.com/thoas/picfit/constants" + "log/slog" + "runtime" + "time" +) + +func NewLogger(cfg *config.Config, logger *slog.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + path := c.Request.URL.Path + + requestID := uuid.New().String() + c.Header("X-Request-ID", requestID) + c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), constants.RequestIDCtx, requestID)) + + c.Next() + + end := time.Now() + latency := end.Sub(start) + + attributes := []slog.Attr{ + slog.Int("status", c.Writer.Status()), + slog.String("method", c.Request.Method), + slog.Any("params", c.Request.URL.Query()), + slog.String("path", path), + slog.String("ip", c.ClientIP()), + slog.String("latency", latency.String()), + slog.String("user-agent", c.Request.UserAgent()), + slog.Time("time", end.UTC()), + } + + if len(c.Errors) > 0 { + for _, e := range c.Errors.Errors() { + logger.LogAttrs(c.Request.Context(), slog.LevelError, e, attributes...) + } + } else { + logger.LogAttrs(c.Request.Context(), slog.LevelInfo, path, attributes...) + } + if cfg.Debug { + logMemStats(c.Request.Context(), logger) + + } + } +} + +func logMemStats(ctx context.Context, logger *slog.Logger) { + var m runtime.MemStats + runtime.ReadMemStats(&m) + + attributes := []slog.Attr{ + slog.String("alloc", fmt.Sprintf("%v MiB", bToMb(m.Alloc))), + slog.String("heap-alloc", fmt.Sprintf("%v MiB", bToMb(m.HeapAlloc))), + slog.String("total-alloc", fmt.Sprintf("%v MiB", bToMb(m.TotalAlloc))), + slog.String("sys", fmt.Sprintf("%v MiB", bToMb(m.Sys))), + slog.String("numgc", fmt.Sprintf("%v", m.NumGC)), + slog.Int("total-goroutine", runtime.NumGoroutine()), + } + logger.LogAttrs(ctx, slog.LevelInfo, "memory stats", attributes...) +} + +func bToMb(b uint64) uint64 { + return b / 1024 / 1024 +} diff --git a/middleware/metrics.go b/middleware/metrics.go new file mode 100644 index 00000000..1f034a76 --- /dev/null +++ b/middleware/metrics.go @@ -0,0 +1,31 @@ +package middleware + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" +) + +func MetricsMiddlewares(c *gin.Context) { + c.Next() + customMetrics.histogram.WithLabelValues( + c.Request.Method, + c.Request.URL.String(), + fmt.Sprint(c.Writer.Status())) + +} + +var customMetrics = newMetrics() + +func newMetrics() *metrics { + return &metrics{ + histogram: prometheus.NewHistogramVec( + prometheus.HistogramOpts{Name: "http_request"}, + []string{"http_method", "http_route", "http_status_code"}, + ), + } +} + +type metrics struct{ histogram *prometheus.HistogramVec } + +func init() { prometheus.MustRegister(customMetrics.histogram) } diff --git a/picfit.go b/picfit.go index e4f2403f..a0e6a430 100644 --- a/picfit.go +++ b/picfit.go @@ -2,6 +2,8 @@ package picfit import ( "context" + "github.com/thoas/picfit/constants" + "log/slog" "github.com/thoas/picfit/config" "github.com/thoas/picfit/engine" @@ -12,25 +14,26 @@ import ( // NewProcessor returns a Processor instance from a config.Config instance func NewProcessor(ctx context.Context, cfg *config.Config) (*Processor, error) { + cfg.Logger.ContextKeys = []string{constants.RequestIDCtx} log := logger.New(cfg.Logger) sourceStorage, destinationStorage, err := storage.New(ctx, - log.With(logger.String("logger", "storage")), cfg.Storage) + log.With(slog.String("logger", "storage")), cfg.Storage) if err != nil { return nil, err } s, err := store.New(ctx, - log.With(logger.String("logger", "store")), + log.With(slog.String("logger", "store")), cfg.KVStore) if err != nil { return nil, err } - e := engine.New(*cfg.Engine, log.With(logger.String("logger", "engine"))) + e := engine.New(*cfg.Engine, log.With(slog.String("logger", "engine"))) - log.Info("Image engine configured", - logger.String("engine", e.String())) + log.InfoContext(ctx, "Image engine configured", + slog.String("engine", e.String())) return &Processor{ Logger: log, diff --git a/processor.go b/processor.go index 348ba098..bf725edf 100644 --- a/processor.go +++ b/processor.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "log/slog" "net/url" "os" filepathpkg "path/filepath" @@ -16,20 +17,18 @@ import ( "github.com/thoas/picfit/storage" "github.com/thoas/picfit/util" "github.com/ulule/gostorages" - "go.uber.org/zap" "github.com/thoas/picfit/config" "github.com/thoas/picfit/engine" "github.com/thoas/picfit/failure" "github.com/thoas/picfit/hash" "github.com/thoas/picfit/image" - "github.com/thoas/picfit/logger" "github.com/thoas/picfit/payload" "github.com/thoas/picfit/store" ) type Processor struct { - Logger *zap.Logger + Logger *slog.Logger config *config.Config destinationStorage storage.Storage @@ -61,15 +60,15 @@ func (p *Processor) Upload(ctx context.Context, payload *payload.Multipart) (*im } // Store stores an image file with the defined filepath -func (p *Processor) Store(ctx context.Context, log *zap.Logger, filepath string, i *image.ImageFile) error { +func (p *Processor) Store(ctx context.Context, log *slog.Logger, filepath string, i *image.ImageFile) error { starttime := time.Now() if err := i.Save(ctx); err != nil { return err } endtime := time.Now() - log.Info("Save file to storage", - logger.Duration("duration", endtime.Sub(starttime)), + log.InfoContext(ctx, "Save file to storage", + slog.String("duration", endtime.Sub(starttime).String()), ) starttime = time.Now() @@ -82,8 +81,8 @@ func (p *Processor) Store(ctx context.Context, log *zap.Logger, filepath string, strings.ToLower(filepathpkg.Ext(filepath)), ).Observe(endtime.Sub(starttime).Seconds()) - log.Info("Save key to store", - logger.Duration("duration", endtime.Sub(starttime)), + log.InfoContext(ctx, "Save key to store", + slog.String("duration", endtime.Sub(starttime).String()), ) // Write children info only when we actually want to be able to delete things. @@ -96,9 +95,9 @@ func (p *Processor) Store(ctx context.Context, log *zap.Logger, filepath string, return err } - log.Info("Put key into set in store", - logger.String("set", parentKey), - logger.String("value", filepath), + log.InfoContext(ctx, "Put key into set in store", + slog.String("set", parentKey), + slog.String("value", filepath), ) } @@ -132,20 +131,20 @@ func (p *Processor) DeleteChild(ctx context.Context, key string) error { return errors.Wrapf(err, "unable to delete key %s", key) } - p.Logger.Info("Deleting child", - logger.String("key", key)) + p.Logger.InfoContext(ctx, "Deleting child", + slog.String("key", key)) return nil } // Delete removes a file from store and storage func (p *Processor) Delete(ctx context.Context, filepath string) error { - p.Logger.Info("Deleting file on source storage", - logger.String("file", filepath)) + p.Logger.InfoContext(ctx, "Deleting file on source storage", + slog.String("file", filepath)) if !p.FileExists(ctx, filepath) { - p.Logger.Info("File does not exist anymore on source storage", - logger.String("file", filepath)) + p.Logger.InfoContext(ctx, "File does not exist anymore on source storage", + slog.String("file", filepath)) return errors.Wrapf(failure.ErrFileNotExists, "unable to delete, file does not exist: %s", filepath) } @@ -165,9 +164,9 @@ func (p *Processor) Delete(ctx context.Context, filepath string) error { } if !exists { - p.Logger.Info("Children key does not exist in set", - logger.String("key", childrenKey), - logger.String("set", parentKey)) + p.Logger.InfoContext(ctx, "Children key does not exist in set", + slog.String("key", childrenKey), + slog.String("set", parentKey)) return nil } @@ -179,8 +178,8 @@ func (p *Processor) Delete(ctx context.Context, filepath string) error { } if children == nil { - p.Logger.Info("No children to delete in set", - logger.String("set", parentKey)) + p.Logger.InfoContext(ctx, "No children to delete in set", + slog.String("set", parentKey)) return nil } @@ -198,8 +197,8 @@ func (p *Processor) Delete(ctx context.Context, filepath string) error { } // Delete them right away, we don't care about them anymore. - p.Logger.Info("Delete set %s", - logger.String("set", childrenKey)) + p.Logger.InfoContext(ctx, "Delete set %s", + slog.String("set", childrenKey)) err = p.store.Delete(ctx, childrenKey) if err != nil { @@ -216,7 +215,7 @@ func (p *Processor) ProcessContext(c *gin.Context, opts ...Option) (*image.Image force = c.Query("force") options = newOptions(opts...) ctx = c.Request.Context() - log = p.Logger.With(logger.String("key", storeKey)) + log = p.Logger.With(slog.String("key", storeKey)) ) modifiedSince := c.Request.Header.Get("If-Modified-Since") @@ -227,8 +226,8 @@ func (p *Processor) ProcessContext(c *gin.Context, opts ...Option) (*image.Image } if exists { - log.Info("Key already exists on store, file not modified", - logger.String("modified-since", modifiedSince)) + log.InfoContext(ctx, "Key already exists on store, file not modified", + slog.String("modified-since", modifiedSince)) return nil, failure.ErrFileNotModified } @@ -247,8 +246,8 @@ func (p *Processor) ProcessContext(c *gin.Context, opts ...Option) (*image.Image return nil, err } - log.Info("Key found in store", - logger.String("filepath", filepath)) + log.InfoContext(ctx, "Key found in store", + slog.String("filepath", filepath)) starttime := time.Now() img, err := p.fileFromStorage(ctx, storeKey, filepath, options.Load) @@ -263,10 +262,10 @@ func (p *Processor) ProcessContext(c *gin.Context, opts ...Option) (*image.Image filesize := util.ByteCountDecimal(int64(len(img.Content()))) endtime := time.Now() - log.Info("Image retrieved from storage", - logger.Duration("duration", endtime.Sub(starttime)), - logger.String("size", filesize), - logger.String("image", img.Filepath)) + log.InfoContext(ctx, "Image retrieved from storage", + slog.String("duration", endtime.Sub(starttime).String()), + slog.String("size", filesize), + slog.String("image", img.Filepath)) defaultMetrics.histogram.WithLabelValues( "load", @@ -278,9 +277,9 @@ func (p *Processor) ProcessContext(c *gin.Context, opts ...Option) (*image.Image // Image not found from the Store, we need to process it // URL available in Query String - log.Info("Key not found in store") + log.InfoContext(ctx, "Key not found in store") } else { - log.Info("Force activated, key will be re-processed") + log.InfoContext(ctx, "Force activated, key will be re-processed") } return p.processImage(c, storeKey) @@ -313,7 +312,7 @@ func (p *Processor) processImage(c *gin.Context, storeKey string) (*image.ImageF filepath string err error ctx = c.Request.Context() - log = p.Logger.With(logger.String("key", storeKey)) + log = p.Logger.With(slog.String("key", storeKey)) ) file := &image.ImageFile{ @@ -326,7 +325,7 @@ func (p *Processor) processImage(c *gin.Context, storeKey string) (*image.ImageF starttime := time.Now() u, exists := c.Get("url") if exists { - file, err = image.FromURL(u.(*url.URL), p.config.Options.DefaultUserAgent) + file, err = image.FromURL(ctx, u.(*url.URL), p.config.Options.DefaultUserAgent) } else { // URL provided we use http protocol to retrieve it filepath = qs["path"].(string) @@ -349,12 +348,12 @@ func (p *Processor) processImage(c *gin.Context, storeKey string) (*image.ImageF filesize := util.ByteCountDecimal(int64(len(file.Content()))) log = log.With( - logger.String("image", file.Filepath), - logger.String("size", filesize), + slog.String("image", file.Filepath), + slog.String("size", filesize), ) - log.Info("Retrieved image to process from storage", - logger.Duration("duration", endtime.Sub(starttime))) + log.InfoContext(ctx, "Retrieved image to process from storage", + slog.String("duration", endtime.Sub(starttime).String())) parameters, err := p.NewParameters(ctx, file, qs) if err != nil { @@ -362,7 +361,7 @@ func (p *Processor) processImage(c *gin.Context, storeKey string) (*image.ImageF } starttime = time.Now() - file, err = p.engine.Transform(parameters.output, parameters.operations) + file, err = p.engine.Transform(ctx, parameters.output, parameters.operations) if err != nil { return nil, errors.Wrap(err, "unable to process image") } @@ -380,12 +379,12 @@ func (p *Processor) processImage(c *gin.Context, storeKey string) (*image.ImageF file.Headers["ETag"] = storeKey log = log.With( - logger.String("image", file.Filepath), - logger.String("size", filesize), + slog.String("image", file.Filepath), + slog.String("size", filesize), ) - log.Info("Image processed", - logger.Duration("duration", endtime.Sub(starttime))) + log.InfoContext(ctx, "Image processed", + slog.String("duration", endtime.Sub(starttime).String())) if err := p.Store(ctx, log, filepath, file); err != nil { return nil, errors.Wrapf(err, "unable to store processed image: %s", filepath) diff --git a/server/http.go b/server/http.go index 933a7547..98631b38 100644 --- a/server/http.go +++ b/server/http.go @@ -11,11 +11,9 @@ import ( "github.com/getsentry/sentry-go" sentrygin "github.com/getsentry/sentry-go/gin" - ginzap "github.com/gin-contrib/zap" - "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/gin-gonic/contrib/cors" "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/thoas/picfit" "github.com/thoas/picfit/config" "github.com/thoas/picfit/failure" @@ -68,7 +66,8 @@ func (s *HTTPServer) Init() error { router.Use(gin.Recovery()) } - router.Use(ginzap.Ginzap(s.processor.Logger, time.RFC3339, true)) + router.Use(middleware.NewLogger(s.config, s.processor.Logger)) + router.Use(middleware.MetricsMiddlewares) if s.config.Sentry != nil { if err := sentry.Init(sentry.ClientOptions{ diff --git a/storage/http.go b/storage/http.go index 4f367dfa..fcc0ba1f 100644 --- a/storage/http.go +++ b/storage/http.go @@ -44,12 +44,12 @@ func (s *HTTPStorage) Open(ctx context.Context, filepath string) (io.ReadCloser, return nil, err } - return s.OpenFromURL(u) + return s.OpenFromURL(ctx, u) } // OpenFromURL retrieves bytes from an url -func (s *HTTPStorage) OpenFromURL(u *url.URL) (io.ReadCloser, error) { - req, err := http.NewRequestWithContext(context.Background(), "GET", u.String(), nil) +func (s *HTTPStorage) OpenFromURL(ctx context.Context, u *url.URL) (io.ReadCloser, error) { + req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil) if err != nil { return nil, err } diff --git a/storage/storage.go b/storage/storage.go index 5264152b..d39d4b1a 100644 --- a/storage/storage.go +++ b/storage/storage.go @@ -3,6 +3,7 @@ package storage import ( "context" "fmt" + "log/slog" "path" "strings" @@ -11,9 +12,6 @@ import ( fsstorage "github.com/ulule/gostorages/fs" gcstorage "github.com/ulule/gostorages/gcs" s3storage "github.com/ulule/gostorages/s3" - "go.uber.org/zap" - - "github.com/thoas/picfit/logger" ) const ( @@ -49,12 +47,12 @@ func (s *Storage) Path(filepath string) string { } // New return destination and source storages from config -func New(ctx context.Context, log *zap.Logger, cfg *Config) (*Storage, *Storage, error) { +func New(ctx context.Context, log *slog.Logger, cfg *Config) (*Storage, *Storage, error) { if cfg == nil { storage := &Storage{Storage: &DummyStorage{}} - log.Info("Source storage configured", - logger.String("type", "dummy")) + log.InfoContext(ctx, "Source storage configured", + slog.String("type", "dummy")) return storage, storage, nil } @@ -71,13 +69,13 @@ func New(ctx context.Context, log *zap.Logger, cfg *Config) (*Storage, *Storage, return nil, nil, err } - log.Info("Source storage configured", - logger.String("type", cfg.Source.Type)) + log.InfoContext(ctx, "Source storage configured", + slog.String("type", cfg.Source.Type)) } if cfg.Destination == nil { - log.Info("Destination storage not set, source storage will be used", - logger.String("type", cfg.Source.Type)) + log.InfoContext(ctx, "Destination storage not set, source storage will be used", + slog.String("type", cfg.Source.Type)) return &Storage{ Storage: sourceStorage, @@ -93,8 +91,8 @@ func New(ctx context.Context, log *zap.Logger, cfg *Config) (*Storage, *Storage, return nil, nil, err } - log.Info("Destination storage configured", - logger.String("type", cfg.Destination.Type)) + log.InfoContext(ctx, "Destination storage configured", + slog.String("type", cfg.Destination.Type)) return &Storage{ Storage: sourceStorage, diff --git a/store/store.go b/store/store.go index 1a5ede8a..4af7b988 100644 --- a/store/store.go +++ b/store/store.go @@ -3,15 +3,13 @@ package store import ( "context" "fmt" + "log/slog" "net" "net/url" "strconv" "time" "github.com/ulule/gokvstores" - "go.uber.org/zap" - - "github.com/thoas/picfit/logger" ) type Store gokvstores.KVStore @@ -62,13 +60,13 @@ func getHostPortWithDefaults(u *url.URL) (string, string) { } // New returns a KVStore from config -func New(ctx context.Context, log *zap.Logger, cfg *Config) (gokvstores.KVStore, error) { +func New(ctx context.Context, log *slog.Logger, cfg *Config) (gokvstores.KVStore, error) { if cfg == nil { return gokvstores.DummyStore{}, nil } - log.Info("KVStore configured", - logger.String("type", cfg.Type)) + log.InfoContext(ctx, "KVStore configured", + slog.String("type", cfg.Type)) switch cfg.Type { case dummyKVStoreType: