[#148] Add trace_id to logs #152

Merged
alexvanin merged 1 commit from r.loginov/frostfs-http-gw:feature/148-add_trace_id_to_log into master 2024-10-18 09:29:05 +00:00
7 changed files with 104 additions and 48 deletions

View file

@ -3,7 +3,10 @@
This document outlines major changes between releases. This document outlines major changes between releases.
## [Unreleased] ## [Unreleased]
### Added
- Support percent-encoding for GET queries (#134) - Support percent-encoding for GET queries (#134)
- Add `trace_id` to logs (#148)
dkirillov marked this conversation as resolved Outdated

It's better to use issue link if it exists (#148)

It's better to use issue link if it exists (#148)
### Changed ### Changed
- Update go version to 1.22 (#132) - Update go version to 1.22 (#132)

View file

@ -42,6 +42,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"go.opentelemetry.io/otel/trace"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@ -589,15 +590,15 @@ func (a *app) configureRouter(handler *handler.Handler) {
response.Error(r, "Method Not Allowed", fasthttp.StatusMethodNotAllowed) response.Error(r, "Method Not Allowed", fasthttp.StatusMethodNotAllowed)
} }
r.POST("/upload/{cid}", a.logger(a.canonicalizer(a.tokenizer(a.tracer(a.reqNamespace(handler.Upload)))))) r.POST("/upload/{cid}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.Upload))))))
a.log.Info(logs.AddedPathUploadCid) a.log.Info(logs.AddedPathUploadCid)
r.GET("/get/{cid}/{oid:*}", a.logger(a.canonicalizer(a.tokenizer(a.tracer(a.reqNamespace(handler.DownloadByAddressOrBucketName)))))) r.GET("/get/{cid}/{oid:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.DownloadByAddressOrBucketName))))))
r.HEAD("/get/{cid}/{oid:*}", a.logger(a.canonicalizer(a.tokenizer(a.tracer(a.reqNamespace(handler.HeadByAddressOrBucketName)))))) r.HEAD("/get/{cid}/{oid:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.HeadByAddressOrBucketName))))))
a.log.Info(logs.AddedPathGetCidOid) a.log.Info(logs.AddedPathGetCidOid)
r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(a.canonicalizer(a.tokenizer(a.tracer(a.reqNamespace(handler.DownloadByAttribute)))))) r.GET("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.DownloadByAttribute))))))
r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.logger(a.canonicalizer(a.tokenizer(a.tracer(a.reqNamespace(handler.HeadByAttribute)))))) r.HEAD("/get_by_attribute/{cid}/{attr_key}/{attr_val:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.HeadByAttribute))))))
a.log.Info(logs.AddedPathGetByAttributeCidAttrKeyAttrVal) a.log.Info(logs.AddedPathGetByAttributeCidAttrKeyAttrVal)
r.GET("/zip/{cid}/{prefix:*}", a.logger(a.canonicalizer(a.tokenizer(a.tracer(a.reqNamespace(handler.DownloadZipped)))))) r.GET("/zip/{cid}/{prefix:*}", a.tracer(a.logger(a.canonicalizer(a.tokenizer(a.reqNamespace(handler.DownloadZipped))))))
a.log.Info(logs.AddedPathZipCidPrefix) a.log.Info(logs.AddedPathZipCidPrefix)
a.webServer.Handler = r.Handler a.webServer.Handler = r.Handler
@ -605,11 +606,24 @@ func (a *app) configureRouter(handler *handler.Handler) {
func (a *app) logger(h fasthttp.RequestHandler) fasthttp.RequestHandler { func (a *app) logger(h fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(req *fasthttp.RequestCtx) { return func(req *fasthttp.RequestCtx) {
a.log.Info(logs.Request, zap.String("remote", req.RemoteAddr().String()), requiredFields := []zap.Field{zap.Uint64("id", req.ID())}
reqCtx := utils.GetContextFromRequest(req)
if traceID := trace.SpanFromContext(reqCtx).SpanContext().TraceID(); traceID.IsValid() {
requiredFields = append(requiredFields, zap.String("trace_id", traceID.String()))
}
log := a.log.With(requiredFields...)
reqCtx = utils.SetReqLog(reqCtx, log)
utils.SetContextToRequest(reqCtx, req)
fields := []zap.Field{
zap.String("remote", req.RemoteAddr().String()),
dkirillov marked this conversation as resolved Outdated

Consider something like this

diff --git a/cmd/http-gw/app.go b/cmd/http-gw/app.go
index 007a8d7..de01f75 100644
--- a/cmd/http-gw/app.go
+++ b/cmd/http-gw/app.go
@@ -607,20 +607,24 @@ func (a *app) configureRouter(handler *handler.Handler) {
 
 func (a *app) logger(h fasthttp.RequestHandler) fasthttp.RequestHandler {
 	return func(req *fasthttp.RequestCtx) {
+		requiredFields := []zap.Field{zap.Uint64("id", req.ID())}
+		reqCtx := utils.GetContextFromRequest(req)
+		if traceID := trace.SpanFromContext(reqCtx).SpanContext().TraceID(); traceID.IsValid() {
+			requiredFields = append(requiredFields, zap.String("trace_id", traceID.String()))
+		}
+		log := a.log.With(requiredFields...)
+
+		reqCtx = utils.SetReqLog(reqCtx, log)
+		utils.SetContextToRequest(reqCtx, req)
+
 		fields := []zap.Field{
 			zap.String("remote", req.RemoteAddr().String()),
 			zap.ByteString("method", req.Method()),
 			zap.ByteString("path", req.Path()),
 			zap.ByteString("query", req.QueryArgs().QueryString()),
-			zap.Uint64("id", req.ID()),
 		}
 
-		appCtx := utils.GetContextFromRequest(req)
-		if traceID := trace.SpanFromContext(appCtx).SpanContext().TraceID(); traceID.IsValid() {
-			fields = append(fields, zap.String("trace_id", traceID.String()))
-		}
-
-		a.log.Info(logs.Request, fields...)
+		log.Info(logs.Request, fields...)
 		h(req)
 	}
 }
@@ -659,19 +663,17 @@ func (a *app) canonicalizer(h fasthttp.RequestHandler) fasthttp.RequestHandler {
 
 func (a *app) tokenizer(h fasthttp.RequestHandler) fasthttp.RequestHandler {
 	return func(req *fasthttp.RequestCtx) {
-		appCtx, err := tokens.StoreBearerTokenAppCtx(utils.GetContextFromRequest(req), req)
+		reqCtx := utils.GetContextFromRequest(req)
+
+		appCtx, err := tokens.StoreBearerTokenAppCtx(reqCtx, req)
 		if err != nil {
 			fields := []zap.Field{
 				zap.Uint64("id", req.ID()),
 				zap.Error(err),
 			}
 
-			reqCtx := utils.GetContextFromRequest(req)
-			if traceID := trace.SpanFromContext(reqCtx).SpanContext().TraceID(); traceID.IsValid() {
-				fields = append(fields, zap.String("trace_id", traceID.String()))
-			}
-
-			a.log.Error(logs.CouldNotFetchAndStoreBearerToken, fields...)
+			log := utils.GetReqLogOrDefault(reqCtx, a.log)
+			log.Error(logs.CouldNotFetchAndStoreBearerToken, fields...)
 			response.Error(req, "could not fetch and store bearer token: "+err.Error(), fasthttp.StatusBadRequest)
 			return
 		}
diff --git a/utils/util.go b/utils/util.go
index d513817..dd00d38 100644
--- a/utils/util.go
+++ b/utils/util.go
@@ -4,6 +4,7 @@ import (
 	"context"
 
 	"github.com/valyala/fasthttp"
+	"go.uber.org/zap"
 )
 
 // SetContextToRequest adds new context to fasthttp request.
@@ -15,3 +16,29 @@ func SetContextToRequest(ctx context.Context, c *fasthttp.RequestCtx) {
 func GetContextFromRequest(c *fasthttp.RequestCtx) context.Context {
 	return c.UserValue("context").(context.Context)
 }
+
+type ctxReqLoggerKeyType struct{}
+
+func SetReqLog(ctx context.Context, log *zap.Logger) context.Context {
+	if ctx == nil {
+		return nil
+	}
+	return context.WithValue(ctx, ctxReqLoggerKeyType{}, log)
+}
+
+func GetReqLog(ctx context.Context) *zap.Logger {
+	if ctx == nil {
+		return nil
+	} else if r, ok := ctx.Value(ctxReqLoggerKeyType{}).(*zap.Logger); ok {
+		return r
+	}
+	return nil
+}
+
+func GetReqLogOrDefault(ctx context.Context, defaultLog *zap.Logger) *zap.Logger {
+	log := GetReqLog(ctx)
+	if log == nil {
+		log = defaultLog
+	}
+	return log
+}

Consider something like this ```diff diff --git a/cmd/http-gw/app.go b/cmd/http-gw/app.go index 007a8d7..de01f75 100644 --- a/cmd/http-gw/app.go +++ b/cmd/http-gw/app.go @@ -607,20 +607,24 @@ func (a *app) configureRouter(handler *handler.Handler) { func (a *app) logger(h fasthttp.RequestHandler) fasthttp.RequestHandler { return func(req *fasthttp.RequestCtx) { + requiredFields := []zap.Field{zap.Uint64("id", req.ID())} + reqCtx := utils.GetContextFromRequest(req) + if traceID := trace.SpanFromContext(reqCtx).SpanContext().TraceID(); traceID.IsValid() { + requiredFields = append(requiredFields, zap.String("trace_id", traceID.String())) + } + log := a.log.With(requiredFields...) + + reqCtx = utils.SetReqLog(reqCtx, log) + utils.SetContextToRequest(reqCtx, req) + fields := []zap.Field{ zap.String("remote", req.RemoteAddr().String()), zap.ByteString("method", req.Method()), zap.ByteString("path", req.Path()), zap.ByteString("query", req.QueryArgs().QueryString()), - zap.Uint64("id", req.ID()), } - appCtx := utils.GetContextFromRequest(req) - if traceID := trace.SpanFromContext(appCtx).SpanContext().TraceID(); traceID.IsValid() { - fields = append(fields, zap.String("trace_id", traceID.String())) - } - - a.log.Info(logs.Request, fields...) + log.Info(logs.Request, fields...) h(req) } } @@ -659,19 +663,17 @@ func (a *app) canonicalizer(h fasthttp.RequestHandler) fasthttp.RequestHandler { func (a *app) tokenizer(h fasthttp.RequestHandler) fasthttp.RequestHandler { return func(req *fasthttp.RequestCtx) { - appCtx, err := tokens.StoreBearerTokenAppCtx(utils.GetContextFromRequest(req), req) + reqCtx := utils.GetContextFromRequest(req) + + appCtx, err := tokens.StoreBearerTokenAppCtx(reqCtx, req) if err != nil { fields := []zap.Field{ zap.Uint64("id", req.ID()), zap.Error(err), } - reqCtx := utils.GetContextFromRequest(req) - if traceID := trace.SpanFromContext(reqCtx).SpanContext().TraceID(); traceID.IsValid() { - fields = append(fields, zap.String("trace_id", traceID.String())) - } - - a.log.Error(logs.CouldNotFetchAndStoreBearerToken, fields...) + log := utils.GetReqLogOrDefault(reqCtx, a.log) + log.Error(logs.CouldNotFetchAndStoreBearerToken, fields...) response.Error(req, "could not fetch and store bearer token: "+err.Error(), fasthttp.StatusBadRequest) return } diff --git a/utils/util.go b/utils/util.go index d513817..dd00d38 100644 --- a/utils/util.go +++ b/utils/util.go @@ -4,6 +4,7 @@ import ( "context" "github.com/valyala/fasthttp" + "go.uber.org/zap" ) // SetContextToRequest adds new context to fasthttp request. @@ -15,3 +16,29 @@ func SetContextToRequest(ctx context.Context, c *fasthttp.RequestCtx) { func GetContextFromRequest(c *fasthttp.RequestCtx) context.Context { return c.UserValue("context").(context.Context) } + +type ctxReqLoggerKeyType struct{} + +func SetReqLog(ctx context.Context, log *zap.Logger) context.Context { + if ctx == nil { + return nil + } + return context.WithValue(ctx, ctxReqLoggerKeyType{}, log) +} + +func GetReqLog(ctx context.Context) *zap.Logger { + if ctx == nil { + return nil + } else if r, ok := ctx.Value(ctxReqLoggerKeyType{}).(*zap.Logger); ok { + return r + } + return nil +} + +func GetReqLogOrDefault(ctx context.Context, defaultLog *zap.Logger) *zap.Logger { + log := GetReqLog(ctx) + if log == nil { + log = defaultLog + } + return log +} ```
zap.ByteString("method", req.Method()), zap.ByteString("method", req.Method()),
zap.ByteString("path", req.Path()), zap.ByteString("path", req.Path()),
zap.ByteString("query", req.QueryArgs().QueryString()), zap.ByteString("query", req.QueryArgs().QueryString()),
zap.Uint64("id", req.ID())) }
log.Info(logs.Request, fields...)
h(req) h(req)
} }
} }
@ -648,9 +662,12 @@ func (a *app) canonicalizer(h fasthttp.RequestHandler) fasthttp.RequestHandler {

Are there any reasons why we refused getting ctx from a.ctx? If so, let's replace with reqCtx

Are there any reasons why we refused getting ctx from `a.ctx`? If so, let's replace with `reqCtx`

The reason is that the first middleware that works with context should get context based on appCtx. Previously, the middleware tokenizer was the first handler that featured context, and that's why it didn't use the request context, but used the application context. However, now the first middleware that works with context is tracer. Therefore, tracer gets the context from appCtx, and at the same time tokenizer no longer needs to take the context from appCtx because the necessary information for it will be in the context of the request.
In other words, we want to store the data that is inherent in a specific request (trace information, bearer token) in the context of the request. And we want to make the request context based on the application context.
If I'm wrong or didn't answer the question, let me know.
This comment responds to two comments.

The reason is that the first middleware that works with context should get context based on `appCtx`. Previously, the middleware `tokenizer` was the first handler that featured context, and that's why it didn't use the request context, but used the application context. However, now the first middleware that works with context is `tracer`. Therefore, `tracer` gets the context from `appCtx`, and at the same time `tokenizer` no longer needs to take the context from `appCtx` because the necessary information for it will be in the context of the request. In other words, we want to store the data that is inherent in a specific request (trace information, bearer token) in the context of the request. And we want to make the request context based on the application context. If I'm wrong or didn't answer the question, let me know. This comment responds to two comments.

It seems we can write

reqCtx := utils.GetContextFromRequest(req)
appCtx, err := tokens.StoreBearerTokenAppCtx(reqCtx, req)

This can help don't get again context in line 669

It seems we can write ```golang reqCtx := utils.GetContextFromRequest(req) appCtx, err := tokens.StoreBearerTokenAppCtx(reqCtx, req) ``` This can help don't get again context in line 669
func (a *app) tokenizer(h fasthttp.RequestHandler) fasthttp.RequestHandler { func (a *app) tokenizer(h fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(req *fasthttp.RequestCtx) { return func(req *fasthttp.RequestCtx) {
appCtx, err := tokens.StoreBearerTokenAppCtx(a.ctx, req) reqCtx := utils.GetContextFromRequest(req)
appCtx, err := tokens.StoreBearerTokenAppCtx(reqCtx, req)
if err != nil { if err != nil {
a.log.Error(logs.CouldNotFetchAndStoreBearerToken, zap.Uint64("id", req.ID()), zap.Error(err)) log := utils.GetReqLogOrDefault(reqCtx, a.log)
log.Error(logs.CouldNotFetchAndStoreBearerToken, zap.Error(err))
response.Error(req, "could not fetch and store bearer token: "+err.Error(), fasthttp.StatusBadRequest) response.Error(req, "could not fetch and store bearer token: "+err.Error(), fasthttp.StatusBadRequest)
return return
} }
@ -661,9 +678,7 @@ func (a *app) tokenizer(h fasthttp.RequestHandler) fasthttp.RequestHandler {
func (a *app) tracer(h fasthttp.RequestHandler) fasthttp.RequestHandler { func (a *app) tracer(h fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(req *fasthttp.RequestCtx) { return func(req *fasthttp.RequestCtx) {
appCtx := utils.GetContextFromRequest(req) appCtx, span := utils.StartHTTPServerSpan(a.ctx, req, "REQUEST")
appCtx, span := utils.StartHTTPServerSpan(appCtx, req, "REQUEST")
defer func() { defer func() {
utils.SetHTTPTraceInfo(appCtx, span, req) utils.SetHTTPTraceInfo(appCtx, span, req)
span.End() span.End()

View file

@ -113,8 +113,10 @@ func urlencode(prefix, filename string) string {
} }
func (h *Handler) browseObjects(c *fasthttp.RequestCtx, bucketInfo *data.BucketInfo, prefix string) { func (h *Handler) browseObjects(c *fasthttp.RequestCtx, bucketInfo *data.BucketInfo, prefix string) {
log := h.log.With(zap.String("bucket", bucketInfo.Name))
ctx := utils.GetContextFromRequest(c) ctx := utils.GetContextFromRequest(c)
reqLog := utils.GetReqLogOrDefault(ctx, h.log)
log := reqLog.With(zap.String("bucket", bucketInfo.Name))
dkirillov marked this conversation as resolved Outdated

Why don't we write

ctx := utils.GetContextFromRequest(c)
reqLog := utils.GetReqLogOrDefault(ctx, h.log)
log := reqLog.With(zap.String("bucket", bucketInfo.Name))

?

Why don't we write ```golang ctx := utils.GetContextFromRequest(c) reqLog := utils.GetReqLogOrDefault(ctx, h.log) log := reqLog.With(zap.String("bucket", bucketInfo.Name)) ``` ?
nodes, err := h.listObjects(ctx, bucketInfo, prefix) nodes, err := h.listObjects(ctx, bucketInfo, prefix)
if err != nil { if err != nil {
logAndSendBucketError(c, log, err) logAndSendBucketError(c, log, err)

I think we can extract these repeating conditions to a separate function - it occurs 8 times

I think we can extract these repeating conditions to a separate function - it occurs 8 times

done
I also thought about using this version of the function, but it didn't seem very flexible to me. What do you think?

func WithCustomFields(ctx context.Context, log *zap.Logger, fields ...zap.Field) *zap.Logger {
	if traceID := trace.SpanFromContext(ctx).SpanContext().TraceID(); traceID.IsValid() {
		fields = append(fields, zap.String("trace_id", traceID.String()))
	}

	return log.With(fields...)
}
done I also thought about using this version of the function, but it didn't seem very flexible to me. What do you think? ``` func WithCustomFields(ctx context.Context, log *zap.Logger, fields ...zap.Field) *zap.Logger { if traceID := trace.SpanFromContext(ctx).SpanContext().TraceID(); traceID.IsValid() { fields = append(fields, zap.String("trace_id", traceID.String())) } return log.With(fields...) } ```

View file

@ -84,16 +84,17 @@ func (h *Handler) DownloadZipped(c *fasthttp.RequestCtx) {
scid, _ := c.UserValue("cid").(string) scid, _ := c.UserValue("cid").(string)
prefix, _ := c.UserValue("prefix").(string) prefix, _ := c.UserValue("prefix").(string)
ctx := utils.GetContextFromRequest(c)
log := utils.GetReqLogOrDefault(ctx, h.log)
dkirillov marked this conversation as resolved Outdated

The same as previous comment

The same as previous comment
prefix, err := url.QueryUnescape(prefix) prefix, err := url.QueryUnescape(prefix)
if err != nil { if err != nil {
h.log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("prefix", prefix), zap.Uint64("id", c.ID()), zap.Error(err)) log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("prefix", prefix), zap.Error(err))
response.Error(c, "could not unescape prefix: "+err.Error(), fasthttp.StatusBadRequest) response.Error(c, "could not unescape prefix: "+err.Error(), fasthttp.StatusBadRequest)
dkirillov marked this conversation as resolved Outdated

I suppose we can skip additional adding id to log because we use logger from context

log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("prefix", prefix), zap.Error(err))
I suppose we can skip additional adding id to log because we use logger from context ```golang log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("prefix", prefix), zap.Error(err)) ```
return return
} }
log := h.log.With(zap.String("cid", scid), zap.String("prefix", prefix), zap.Uint64("id", c.ID())) log = log.With(zap.String("cid", scid), zap.String("prefix", prefix))
ctx := utils.GetContextFromRequest(c)
dkirillov marked this conversation as resolved Outdated

The same

log = log.With(zap.String("cid", scid), zap.String("prefix", prefix)
The same ```golang log = log.With(zap.String("cid", scid), zap.String("prefix", prefix) ```
bktInfo, err := h.getBucketInfo(ctx, scid, log) bktInfo, err := h.getBucketInfo(ctx, scid, log)
if err != nil { if err != nil {

View file

@ -190,13 +190,12 @@ func New(params *AppParams, config Config, tree *tree.Tree) *Handler {
// byAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that // byAddress is a wrapper for function (e.g. request.headObject, request.receiveFile) that
// prepares request and object address to it. // prepares request and object address to it.
func (h *Handler) byAddress(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) { func (h *Handler) byAddress(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) {
var ( idCnr, _ := c.UserValue("cid").(string)
idCnr, _ = c.UserValue("cid").(string) idObj, _ := c.UserValue("oid").(string)
idObj, _ = c.UserValue("oid").(string)
log = h.log.With(zap.String("cid", idCnr), zap.String("oid", idObj))
)
ctx := utils.GetContextFromRequest(c) ctx := utils.GetContextFromRequest(c)
reqLog := utils.GetReqLogOrDefault(ctx, h.log)
log := reqLog.With(zap.String("cid", idCnr), zap.String("oid", idObj))
bktInfo, err := h.getBucketInfo(ctx, idCnr, log) bktInfo, err := h.getBucketInfo(ctx, idCnr, log)
if err != nil { if err != nil {
@ -219,12 +218,13 @@ func (h *Handler) byAddress(c *fasthttp.RequestCtx, f func(context.Context, requ
// byObjectName is a wrapper for function (e.g. request.headObject, request.receiveFile) that // byObjectName is a wrapper for function (e.g. request.headObject, request.receiveFile) that
// prepares request and object address to it. // prepares request and object address to it.
func (h *Handler) byObjectName(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) { func (h *Handler) byObjectName(c *fasthttp.RequestCtx, f func(context.Context, request, oid.Address)) {
var ( bucketname := c.UserValue("cid").(string)
bucketname = c.UserValue("cid").(string) key := c.UserValue("oid").(string)
key = c.UserValue("oid").(string) download := c.QueryArgs().GetBool("download")
log = h.log.With(zap.String("bucketname", bucketname), zap.String("key", key))
download = c.QueryArgs().GetBool("download") ctx := utils.GetContextFromRequest(c)
) reqLog := utils.GetReqLogOrDefault(ctx, h.log)
log := reqLog.With(zap.String("bucketname", bucketname), zap.String("key", key))
unescapedKey, err := url.QueryUnescape(key) unescapedKey, err := url.QueryUnescape(key)
if err != nil { if err != nil {
@ -232,8 +232,6 @@ func (h *Handler) byObjectName(c *fasthttp.RequestCtx, f func(context.Context, r
return return
} }
ctx := utils.GetContextFromRequest(c)
bktInfo, err := h.getBucketInfo(ctx, bucketname, log) bktInfo, err := h.getBucketInfo(ctx, bucketname, log)
if err != nil { if err != nil {
logAndSendBucketError(c, log, err) logAndSendBucketError(c, log, err)
@ -275,23 +273,24 @@ func (h *Handler) byAttribute(c *fasthttp.RequestCtx, f func(context.Context, re
key, _ := c.UserValue("attr_key").(string) key, _ := c.UserValue("attr_key").(string)
val, _ := c.UserValue("attr_val").(string) val, _ := c.UserValue("attr_val").(string)
ctx := utils.GetContextFromRequest(c)
log := utils.GetReqLogOrDefault(ctx, h.log)
key, err := url.QueryUnescape(key) key, err := url.QueryUnescape(key)
if err != nil { if err != nil {
h.log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("attr_key", key), zap.Uint64("id", c.ID()), zap.Error(err)) log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("attr_key", key), zap.Error(err))
response.Error(c, "could not unescape attr_key: "+err.Error(), fasthttp.StatusBadRequest) response.Error(c, "could not unescape attr_key: "+err.Error(), fasthttp.StatusBadRequest)
return return
} }
val, err = url.QueryUnescape(val) val, err = url.QueryUnescape(val)
if err != nil { if err != nil {
h.log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("attr_val", val), zap.Uint64("id", c.ID()), zap.Error(err)) log.Error(logs.FailedToUnescapeQuery, zap.String("cid", scid), zap.String("attr_val", val), zap.Error(err))
response.Error(c, "could not unescape attr_val: "+err.Error(), fasthttp.StatusBadRequest) response.Error(c, "could not unescape attr_val: "+err.Error(), fasthttp.StatusBadRequest)
return return
} }
log := h.log.With(zap.String("cid", scid), zap.String("attr_key", key), zap.String("attr_val", val)) log = log.With(zap.String("cid", scid), zap.String("attr_key", key), zap.String("attr_val", val))
ctx := utils.GetContextFromRequest(c)
bktInfo, err := h.getBucketInfo(ctx, scid, log) bktInfo, err := h.getBucketInfo(ctx, scid, log)
if err != nil { if err != nil {

View file

@ -45,16 +45,18 @@ func (pr *putResponse) encode(w io.Writer) error {
// Upload handles multipart upload request. // Upload handles multipart upload request.
func (h *Handler) Upload(c *fasthttp.RequestCtx) { func (h *Handler) Upload(c *fasthttp.RequestCtx) {
var ( var (
file MultipartFile file MultipartFile
idObj oid.ID idObj oid.ID
addr oid.Address addr oid.Address
scid, _ = c.UserValue("cid").(string)
log = h.log.With(zap.String("cid", scid))
bodyStream = c.RequestBodyStream()
drainBuf = make([]byte, drainBufSize)
) )
scid, _ := c.UserValue("cid").(string)
bodyStream := c.RequestBodyStream()
drainBuf := make([]byte, drainBufSize)
ctx := utils.GetContextFromRequest(c) ctx := utils.GetContextFromRequest(c)
reqLog := utils.GetReqLogOrDefault(ctx, h.log)
log := reqLog.With(zap.String("cid", scid))
bktInfo, err := h.getBucketInfo(ctx, scid, log) bktInfo, err := h.getBucketInfo(ctx, scid, log)
if err != nil { if err != nil {
@ -75,13 +77,15 @@ func (h *Handler) Upload(c *fasthttp.RequestCtx) {
zap.Error(err), zap.Error(err),
) )
}() }()
boundary := string(c.Request.Header.MultipartFormBoundary()) boundary := string(c.Request.Header.MultipartFormBoundary())
if file, err = fetchMultipartFile(h.log, bodyStream, boundary); err != nil { if file, err = fetchMultipartFile(log, bodyStream, boundary); err != nil {
log.Error(logs.CouldNotReceiveMultipartForm, zap.Error(err)) log.Error(logs.CouldNotReceiveMultipartForm, zap.Error(err))
response.Error(c, "could not receive multipart/form: "+err.Error(), fasthttp.StatusBadRequest) response.Error(c, "could not receive multipart/form: "+err.Error(), fasthttp.StatusBadRequest)
return return
} }
filtered, err := filterHeaders(h.log, &c.Request.Header)
filtered, err := filterHeaders(log, &c.Request.Header)
if err != nil { if err != nil {
log.Error(logs.CouldNotProcessHeaders, zap.Error(err)) log.Error(logs.CouldNotProcessHeaders, zap.Error(err))
response.Error(c, err.Error(), fasthttp.StatusBadRequest) response.Error(c, err.Error(), fasthttp.StatusBadRequest)
@ -143,7 +147,7 @@ func (h *Handler) Upload(c *fasthttp.RequestCtx) {
} }
if idObj, err = h.frostfs.CreateObject(ctx, prm); err != nil { if idObj, err = h.frostfs.CreateObject(ctx, prm); err != nil {
h.handlePutFrostFSErr(c, err) h.handlePutFrostFSErr(c, err, log)
return return
} }
@ -174,11 +178,11 @@ func (h *Handler) Upload(c *fasthttp.RequestCtx) {
c.Response.Header.SetContentType(jsonHeader) c.Response.Header.SetContentType(jsonHeader)
} }
func (h *Handler) handlePutFrostFSErr(r *fasthttp.RequestCtx, err error) { func (h *Handler) handlePutFrostFSErr(r *fasthttp.RequestCtx, err error, log *zap.Logger) {
statusCode, msg, additionalFields := response.FormErrorResponse("could not store file in frostfs", err) statusCode, msg, additionalFields := response.FormErrorResponse("could not store file in frostfs", err)
logFields := append([]zap.Field{zap.Error(err)}, additionalFields...) logFields := append([]zap.Field{zap.Error(err)}, additionalFields...)
h.log.Error(logs.CouldNotStoreFileInFrostfs, logFields...) log.Error(logs.CouldNotStoreFileInFrostfs, logFields...)
response.Error(r, msg, statusCode) response.Error(r, msg, statusCode)
} }

View file

@ -4,6 +4,7 @@ import (
"context" "context"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
"go.uber.org/zap"
) )
// SetContextToRequest adds new context to fasthttp request. // SetContextToRequest adds new context to fasthttp request.
@ -15,3 +16,34 @@ func SetContextToRequest(ctx context.Context, c *fasthttp.RequestCtx) {
func GetContextFromRequest(c *fasthttp.RequestCtx) context.Context { func GetContextFromRequest(c *fasthttp.RequestCtx) context.Context {
return c.UserValue("context").(context.Context) return c.UserValue("context").(context.Context)
} }
type ctxReqLoggerKeyType struct{}
// SetReqLog sets child zap.Logger in the context.
func SetReqLog(ctx context.Context, log *zap.Logger) context.Context {
if ctx == nil {
return nil
}
return context.WithValue(ctx, ctxReqLoggerKeyType{}, log)
}
// GetReqLog returns log if set.
// If zap.Logger isn't set returns nil.
func GetReqLog(ctx context.Context) *zap.Logger {
if ctx == nil {
return nil
} else if r, ok := ctx.Value(ctxReqLoggerKeyType{}).(*zap.Logger); ok {
return r
}
return nil
}
// GetReqLogOrDefault returns log from context, if it exists.
// If the log is missing from the context, the default logger is returned.
func GetReqLogOrDefault(ctx context.Context, defaultLog *zap.Logger) *zap.Logger {
log := GetReqLog(ctx)
if log == nil {
log = defaultLog
}
return log
}