WIP: refactoring logging system

This commit is contained in:
Julien Neuhart
2019-07-01 17:11:23 +02:00
parent 8ca9866440
commit a4aa8dafac
19 changed files with 225 additions and 391 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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())
}
}
}

View File

@@ -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
}