mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-14 11:22:15 +01:00
WIP: refactoring logging system
This commit is contained in:
@@ -1,51 +1,32 @@
|
||||
package api
|
||||
|
||||
import "github.com/labstack/echo/v4"
|
||||
import (
|
||||
"github.com/labstack/echo/v4"
|
||||
conf "github.com/thecodingmachine/gotenberg/internal/pkg/config"
|
||||
)
|
||||
|
||||
const pingEndpoint = "/ping"
|
||||
|
||||
// Options allows to customize the behaviour
|
||||
// of the API.
|
||||
type Options struct {
|
||||
DefaultWaitTimeout float64
|
||||
DefaultListenPort string
|
||||
EnableChromeEndpoints bool
|
||||
EnableUnoconvEndpoints bool
|
||||
EnableHealthcheckLogging bool
|
||||
DebugProcessStartup bool
|
||||
}
|
||||
|
||||
// DefaultOptions returns default options.
|
||||
func DefaultOptions() *Options {
|
||||
return &Options{
|
||||
DefaultWaitTimeout: 10,
|
||||
DefaultListenPort: "3000",
|
||||
EnableChromeEndpoints: true,
|
||||
EnableUnoconvEndpoints: true,
|
||||
EnableHealthcheckLogging: true,
|
||||
}
|
||||
}
|
||||
|
||||
// New returns an API.
|
||||
func New(opts *Options) *echo.Echo {
|
||||
func New(config *conf.Config) *echo.Echo {
|
||||
api := echo.New()
|
||||
api.HideBanner = true
|
||||
api.HidePort = true
|
||||
api.Use(handleLogging(opts.EnableHealthcheckLogging))
|
||||
api.Use(contextMiddleware(config))
|
||||
api.Use(loggingMiddleware())
|
||||
api.Use(finalizeMiddleware())
|
||||
api.GET(pingEndpoint, func(c echo.Context) error { return nil })
|
||||
g := api.Group("/convert")
|
||||
g.Use(handleContext(opts))
|
||||
g.Use(handleError())
|
||||
g.POST("/merge", merge)
|
||||
if !opts.EnableChromeEndpoints && !opts.EnableUnoconvEndpoints {
|
||||
api.POST("/merge", merge)
|
||||
if !config.EnableChromeEndpoints() && !config.EnableUnoconvEndpoints() {
|
||||
return api
|
||||
}
|
||||
if opts.EnableChromeEndpoints {
|
||||
g := api.Group("/convert")
|
||||
if config.EnableChromeEndpoints() {
|
||||
g.POST("/html", convertHTML)
|
||||
g.POST("/url", convertURL)
|
||||
g.POST("/markdown", convertMarkdown)
|
||||
}
|
||||
if opts.EnableUnoconvEndpoints {
|
||||
if config.EnableUnoconvEndpoints() {
|
||||
g.POST("/office", convertOffice)
|
||||
}
|
||||
return api
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/gommon/random"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/rand"
|
||||
)
|
||||
|
||||
type errBadRequest struct {
|
||||
@@ -20,7 +20,7 @@ func (e *errBadRequest) Error() string {
|
||||
|
||||
func merge(c echo.Context) error {
|
||||
ctx := c.(*resourceContext)
|
||||
opts, err := ctx.resource.mergePrinterOptions()
|
||||
opts, err := ctx.resource.mergePrinterOptions(ctx.config.DefaultWaitTimeout())
|
||||
if err != nil {
|
||||
return &errBadRequest{err}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func merge(c echo.Context) error {
|
||||
|
||||
func convertHTML(c echo.Context) error {
|
||||
ctx := c.(*resourceContext)
|
||||
opts, err := ctx.resource.chromePrinterOptions()
|
||||
opts, err := ctx.resource.chromePrinterOptions(ctx.config.DefaultWaitTimeout())
|
||||
if err != nil {
|
||||
return &errBadRequest{err}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func convertHTML(c echo.Context) error {
|
||||
|
||||
func convertMarkdown(c echo.Context) error {
|
||||
ctx := c.(*resourceContext)
|
||||
opts, err := ctx.resource.chromePrinterOptions()
|
||||
opts, err := ctx.resource.chromePrinterOptions(ctx.config.DefaultWaitTimeout())
|
||||
if err != nil {
|
||||
return &errBadRequest{err}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ func convertMarkdown(c echo.Context) error {
|
||||
|
||||
func convertURL(c echo.Context) error {
|
||||
ctx := c.(*resourceContext)
|
||||
opts, err := ctx.resource.chromePrinterOptions()
|
||||
opts, err := ctx.resource.chromePrinterOptions(ctx.config.DefaultWaitTimeout())
|
||||
if err != nil {
|
||||
return &errBadRequest{err}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func convertURL(c echo.Context) error {
|
||||
|
||||
func convertOffice(c echo.Context) error {
|
||||
ctx := c.(*resourceContext)
|
||||
opts, err := ctx.resource.officePrinterOptions()
|
||||
opts, err := ctx.resource.officePrinterOptions(ctx.config.DefaultWaitTimeout())
|
||||
if err != nil {
|
||||
return &errBadRequest{err}
|
||||
}
|
||||
@@ -105,10 +105,7 @@ func convertOffice(c echo.Context) error {
|
||||
}
|
||||
|
||||
func convert(ctx *resourceContext, p printer.Printer) error {
|
||||
baseFilename, err := rand.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseFilename := random.String(32)
|
||||
filename := fmt.Sprintf("%s.pdf", baseFilename)
|
||||
fpath := fmt.Sprintf("%s/%s", ctx.resource.formFilesDirPath, filename)
|
||||
// if no webhook URL given, run conversion
|
||||
@@ -118,11 +115,12 @@ func convert(ctx *resourceContext, p printer.Printer) error {
|
||||
if err := p.Print(fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.resource.has(resultFilename) {
|
||||
filename, err = ctx.resource.get(resultFilename)
|
||||
if err != nil {
|
||||
return &errBadRequest{err}
|
||||
}
|
||||
if !ctx.resource.has(resultFilename) {
|
||||
return ctx.Attachment(fpath, filename)
|
||||
}
|
||||
filename, err := ctx.resource.get(resultFilename)
|
||||
if err != nil {
|
||||
return &errBadRequest{err}
|
||||
}
|
||||
return ctx.Attachment(fpath, filename)
|
||||
}
|
||||
@@ -132,23 +130,23 @@ func convert(ctx *resourceContext, p printer.Printer) error {
|
||||
go func() {
|
||||
defer ctx.resource.close() // nolint: errcheck
|
||||
if err := p.Print(fpath); err != nil {
|
||||
ctx.Logger().Error(err)
|
||||
ctx.logger.Error(err)
|
||||
return
|
||||
}
|
||||
f, err := os.Open(fpath)
|
||||
if err != nil {
|
||||
ctx.Logger().Error(err)
|
||||
ctx.logger.Error(err)
|
||||
return
|
||||
}
|
||||
defer f.Close() // nolint: errcheck
|
||||
webhook, err := ctx.resource.get(webhookURL)
|
||||
if err != nil {
|
||||
ctx.Logger().Error(err)
|
||||
ctx.logger.Error(err)
|
||||
return
|
||||
}
|
||||
resp, err := http.Post(webhook, "application/pdf", f) /* #nosec */
|
||||
if err != nil {
|
||||
ctx.Logger().Error(err)
|
||||
ctx.logger.Error(err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close() // nolint: errcheck
|
||||
|
||||
@@ -6,70 +6,84 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"github.com/labstack/gommon/random"
|
||||
conf "github.com/thecodingmachine/gotenberg/internal/pkg/config"
|
||||
log "github.com/thecodingmachine/gotenberg/internal/pkg/logger"
|
||||
)
|
||||
|
||||
func handleLogging(enableHealthcheckLogging bool) echo.MiddlewareFunc {
|
||||
if enableHealthcheckLogging {
|
||||
// default logging middleware.
|
||||
return middleware.Logger()
|
||||
}
|
||||
// middleware for skipping logging when the ping endpoint is called.
|
||||
return middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||
Skipper: func(c echo.Context) bool {
|
||||
return c.Request().URL.Path == pingEndpoint
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func handleContext(opts *Options) echo.MiddlewareFunc {
|
||||
// middleware for extending default context with our
|
||||
// custom constext.
|
||||
func contextMiddleware(config *conf.Config) echo.MiddlewareFunc {
|
||||
// middleware for extending the default context
|
||||
// with one of our own context.
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx := &resourceContext{c, opts, nil}
|
||||
r, err := newResource(ctx)
|
||||
if err != nil {
|
||||
if resourceErr := r.close(); resourceErr != nil {
|
||||
c.Logger().Error(resourceErr)
|
||||
// generate a unique identifier for our request.
|
||||
trace := random.String(32)
|
||||
// create the logger for this request using
|
||||
// the previous identifier as trace.
|
||||
logger := log.New(config.LogLevel(), trace)
|
||||
// extend the current echo context with our standard
|
||||
// context.
|
||||
ctx := newStandardContext(c, logger, config)
|
||||
// if the endpoint is not for liveness, make a
|
||||
// context with resource.
|
||||
if ctx.Path() != pingEndpoint {
|
||||
ctx, err := ctx.withResource(trace)
|
||||
if err != nil {
|
||||
ctx.Error(err)
|
||||
return ctx.logEndOfRequest(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
ctx.resource = r
|
||||
return next(ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleError() echo.MiddlewareFunc {
|
||||
// middleware for handling errors and removing resources
|
||||
// once the request has been handled.
|
||||
func loggingMiddleware() echo.MiddlewareFunc {
|
||||
// middleware for enabling logging.
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
err := next(c)
|
||||
ctx := c.(*resourceContext)
|
||||
// if a webhookURL has been given,
|
||||
// do not remove the resources here because
|
||||
// we don't know if the result file has been
|
||||
// generated or sent.
|
||||
if !ctx.resource.has(webhookURL) {
|
||||
if resourceErr := ctx.resource.close(); resourceErr != nil {
|
||||
c.Logger().Error(resourceErr)
|
||||
}
|
||||
}
|
||||
ctx := c.(*standardContext)
|
||||
err := next(ctx)
|
||||
if err != nil {
|
||||
if _, ok := err.(*echo.HTTPError); ok {
|
||||
return err
|
||||
}
|
||||
if _, ok := err.(*errBadRequest); ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if strings.Contains(err.Error(), context.DeadlineExceeded.Error()) {
|
||||
return echo.NewHTTPError(http.StatusRequestTimeout)
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
ctx.Error(err)
|
||||
}
|
||||
return nil
|
||||
return ctx.logEndOfRequest(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func finalizeMiddleware() echo.MiddlewareFunc {
|
||||
// middleware for removing resources at the end of a request
|
||||
// and for improving response in case of error.
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
err := next(c)
|
||||
ctx, ok := c.(*resourceContext)
|
||||
// a resource is associated with the context.
|
||||
if ok {
|
||||
// if a webhookURL has been given,
|
||||
// do not remove the resources here because
|
||||
// we don't know if the result file has been
|
||||
// generated or sent.
|
||||
if !ctx.resource.has(webhookURL) {
|
||||
if resourceErr := ctx.resource.close(); resourceErr != nil {
|
||||
ctx.logger.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if _, ok := err.(*echo.HTTPError); ok {
|
||||
return err
|
||||
}
|
||||
if _, ok := err.(*errBadRequest); ok {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||
}
|
||||
if strings.Contains(err.Error(), context.DeadlineExceeded.Error()) {
|
||||
return echo.NewHTTPError(http.StatusRequestTimeout, err.Error())
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/printer"
|
||||
"github.com/thecodingmachine/gotenberg/internal/pkg/rand"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,53 +30,41 @@ const (
|
||||
type resource struct {
|
||||
formValues map[string]string
|
||||
formFilesDirPath string
|
||||
opts *Options
|
||||
}
|
||||
|
||||
type resourceContext struct {
|
||||
echo.Context
|
||||
opts *Options
|
||||
resource *resource
|
||||
}
|
||||
|
||||
func newResource(ctx *resourceContext) (*resource, error) {
|
||||
func newResource(c echo.Context, dirPath string) (*resource, error) {
|
||||
r := &resource{
|
||||
formValues: formValues(ctx),
|
||||
opts: ctx.opts,
|
||||
}
|
||||
dirPath, err := rand.Get()
|
||||
if err != nil {
|
||||
return r, err
|
||||
formValues: formValues(c),
|
||||
}
|
||||
r.formFilesDirPath = dirPath
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
return nil, fmt.Errorf("%s: making directory: %v", dirPath, err)
|
||||
}
|
||||
if err := formFiles(ctx, dirPath); err != nil {
|
||||
if err := formFiles(c, dirPath); err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func formValues(ctx *resourceContext) map[string]string {
|
||||
func formValues(c echo.Context) map[string]string {
|
||||
v := make(map[string]string)
|
||||
v[resultFilename] = ctx.FormValue(resultFilename)
|
||||
v[waitTimeout] = ctx.FormValue(waitTimeout)
|
||||
v[webhookURL] = ctx.FormValue(webhookURL)
|
||||
v[remoteURL] = ctx.FormValue(remoteURL)
|
||||
v[waitDelay] = ctx.FormValue(waitDelay)
|
||||
v[paperWidth] = ctx.FormValue(paperWidth)
|
||||
v[paperHeight] = ctx.FormValue(paperHeight)
|
||||
v[marginTop] = ctx.FormValue(marginTop)
|
||||
v[marginBottom] = ctx.FormValue(marginBottom)
|
||||
v[marginLeft] = ctx.FormValue(marginLeft)
|
||||
v[marginRight] = ctx.FormValue(marginRight)
|
||||
v[landscape] = ctx.FormValue(landscape)
|
||||
v[resultFilename] = c.FormValue(resultFilename)
|
||||
v[waitTimeout] = c.FormValue(waitTimeout)
|
||||
v[webhookURL] = c.FormValue(webhookURL)
|
||||
v[remoteURL] = c.FormValue(remoteURL)
|
||||
v[waitDelay] = c.FormValue(waitDelay)
|
||||
v[paperWidth] = c.FormValue(paperWidth)
|
||||
v[paperHeight] = c.FormValue(paperHeight)
|
||||
v[marginTop] = c.FormValue(marginTop)
|
||||
v[marginBottom] = c.FormValue(marginBottom)
|
||||
v[marginLeft] = c.FormValue(marginLeft)
|
||||
v[marginRight] = c.FormValue(marginRight)
|
||||
v[landscape] = c.FormValue(landscape)
|
||||
return v
|
||||
}
|
||||
|
||||
func formFiles(ctx *resourceContext, dirPath string) error {
|
||||
form, err := ctx.MultipartForm()
|
||||
func formFiles(c echo.Context, dirPath string) error {
|
||||
form, err := c.MultipartForm()
|
||||
if err != nil {
|
||||
return fmt.Errorf("getting multipart form: %v", err)
|
||||
}
|
||||
@@ -117,8 +104,8 @@ func (r *resource) close() error {
|
||||
|
||||
const defaultHeaderFooterHTML string = "<html><head></head><body></body></html>"
|
||||
|
||||
func (r *resource) chromePrinterOptions() (*printer.ChromeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, r.opts.DefaultWaitTimeout)
|
||||
func (r *resource) chromePrinterOptions(defaultWaitTimeout float64) (*printer.ChromeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, defaultWaitTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,8 +164,8 @@ func (r *resource) chromePrinterOptions() (*printer.ChromeOptions, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *resource) officePrinterOptions() (*printer.OfficeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, r.opts.DefaultWaitTimeout)
|
||||
func (r *resource) officePrinterOptions(defaultWaitTimeout float64) (*printer.OfficeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, defaultWaitTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -192,8 +179,8 @@ func (r *resource) officePrinterOptions() (*printer.OfficeOptions, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *resource) mergePrinterOptions() (*printer.MergeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, r.opts.DefaultWaitTimeout)
|
||||
func (r *resource) mergePrinterOptions(defaultWaitTimeout float64) (*printer.MergeOptions, error) {
|
||||
timeout, err := r.float64(waitTimeout, defaultWaitTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user