forked from TrueCloudLab/frostfs-s3-gw
[#369] Enhanced http requests logging
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com> Signed-off-by: Alex Vanin <a.vanin@yadro.com>
This commit is contained in:
parent
5abd69f58f
commit
91995f27f6
6 changed files with 143 additions and 57 deletions
61
api/middleware/log_http.go
Normal file
61
api/middleware/log_http.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LogHTTPSettings struct {
|
||||||
|
Enabled bool
|
||||||
|
MaxBody int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogHTTP logs http parameters from s3 request.
|
||||||
|
func LogHTTP(l *zap.Logger, settings LogHTTPSettings) Func {
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if !settings.Enabled {
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var httplog = l.With(
|
||||||
|
zap.String("from", r.RemoteAddr),
|
||||||
|
zap.String("URI", r.RequestURI),
|
||||||
|
zap.String("method", r.Method),
|
||||||
|
)
|
||||||
|
|
||||||
|
httplog = withFieldIfExist(httplog, "query", r.URL.Query())
|
||||||
|
httplog = withFieldIfExist(httplog, "headers", r.Header)
|
||||||
|
if r.ContentLength != 0 && r.ContentLength <= settings.MaxBody {
|
||||||
|
var err error
|
||||||
|
httplog, err = withBody(httplog, r)
|
||||||
|
if err != nil {
|
||||||
|
l.Error("read body error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httplog.Info(logs.RequestHTTP)
|
||||||
|
h.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func withBody(httplog *zap.Logger, r *http.Request) (*zap.Logger, error) {
|
||||||
|
var body = make([]byte, r.ContentLength)
|
||||||
|
_, err := r.Body.Read(body)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
httplog = httplog.With(zap.String("body", string(body)))
|
||||||
|
|
||||||
|
return httplog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withFieldIfExist(log *zap.Logger, label string, data map[string][]string) *zap.Logger {
|
||||||
|
if len(data) != 0 {
|
||||||
|
log = log.With(zap.Any(label, data))
|
||||||
|
}
|
||||||
|
return log
|
||||||
|
}
|
|
@ -108,6 +108,7 @@ type Config struct {
|
||||||
Handler Handler
|
Handler Handler
|
||||||
Center s3middleware.Center
|
Center s3middleware.Center
|
||||||
Log *zap.Logger
|
Log *zap.Logger
|
||||||
|
LogHTTP s3middleware.LogHTTPSettings
|
||||||
Metrics *metrics.AppMetrics
|
Metrics *metrics.AppMetrics
|
||||||
|
|
||||||
MiddlewareSettings Settings
|
MiddlewareSettings Settings
|
||||||
|
@ -125,6 +126,7 @@ type Config struct {
|
||||||
func NewRouter(cfg Config) *chi.Mux {
|
func NewRouter(cfg Config) *chi.Mux {
|
||||||
api := chi.NewRouter()
|
api := chi.NewRouter()
|
||||||
api.Use(
|
api.Use(
|
||||||
|
s3middleware.LogHTTP(cfg.Log, cfg.LogHTTP),
|
||||||
s3middleware.Request(cfg.Log, cfg.MiddlewareSettings),
|
s3middleware.Request(cfg.Log, cfg.MiddlewareSettings),
|
||||||
middleware.ThrottleWithOpts(cfg.Throttle),
|
middleware.ThrottleWithOpts(cfg.Throttle),
|
||||||
middleware.Recoverer,
|
middleware.Recoverer,
|
||||||
|
|
|
@ -87,6 +87,8 @@ type (
|
||||||
|
|
||||||
appSettings struct {
|
appSettings struct {
|
||||||
logLevel zap.AtomicLevel
|
logLevel zap.AtomicLevel
|
||||||
|
httpLoggingEnabled bool
|
||||||
|
maxHTTPLogBody int64
|
||||||
maxClient maxClientsConfig
|
maxClient maxClientsConfig
|
||||||
defaultMaxAge int
|
defaultMaxAge int
|
||||||
notificatorEnabled bool
|
notificatorEnabled bool
|
||||||
|
@ -211,6 +213,8 @@ func newAppSettings(log *Logger, v *viper.Viper, key *keys.PrivateKey) *appSetti
|
||||||
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
|
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
|
||||||
notificatorEnabled: v.GetBool(cfgEnableNATS),
|
notificatorEnabled: v.GetBool(cfgEnableNATS),
|
||||||
frostfsidValidation: v.GetBool(cfgFrostfsIDValidationEnabled),
|
frostfsidValidation: v.GetBool(cfgFrostfsIDValidationEnabled),
|
||||||
|
httpLoggingEnabled: v.GetBool(cfgLoggerRequestEnabled),
|
||||||
|
maxHTTPLogBody: v.GetInt64(cfgLoggerRequestMaxBody),
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.resolveZoneList = v.GetStringSlice(cfgResolveBucketAllow)
|
settings.resolveZoneList = v.GetStringSlice(cfgResolveBucketAllow)
|
||||||
|
@ -727,6 +731,10 @@ func (a *App) Serve(ctx context.Context) {
|
||||||
Handler: a.api,
|
Handler: a.api,
|
||||||
Center: a.ctr,
|
Center: a.ctr,
|
||||||
Log: a.log,
|
Log: a.log,
|
||||||
|
LogHTTP: s3middleware.LogHTTPSettings{
|
||||||
|
Enabled: a.settings.httpLoggingEnabled,
|
||||||
|
MaxBody: a.settings.maxHTTPLogBody,
|
||||||
|
},
|
||||||
Metrics: a.metrics,
|
Metrics: a.metrics,
|
||||||
Domains: domains,
|
Domains: domains,
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,9 @@ const ( // Settings.
|
||||||
cfgLoggerLevel = "logger.level"
|
cfgLoggerLevel = "logger.level"
|
||||||
cfgLoggerDestination = "logger.destination"
|
cfgLoggerDestination = "logger.destination"
|
||||||
|
|
||||||
|
cfgLoggerRequestEnabled = "http_logging.enabled"
|
||||||
|
cfgLoggerRequestMaxBody = "http_logging.max_body"
|
||||||
|
|
||||||
// Wallet.
|
// Wallet.
|
||||||
cfgWalletPath = "wallet.path"
|
cfgWalletPath = "wallet.path"
|
||||||
cfgWalletAddress = "wallet.address"
|
cfgWalletAddress = "wallet.address"
|
||||||
|
@ -739,6 +742,10 @@ func newSettings() *viper.Viper {
|
||||||
v.SetDefault(cfgLoggerLevel, "debug")
|
v.SetDefault(cfgLoggerLevel, "debug")
|
||||||
v.SetDefault(cfgLoggerDestination, "stdout")
|
v.SetDefault(cfgLoggerDestination, "stdout")
|
||||||
|
|
||||||
|
// http logger
|
||||||
|
v.SetDefault(cfgLoggerRequestEnabled, false)
|
||||||
|
v.SetDefault(cfgLoggerRequestMaxBody, 1024)
|
||||||
|
|
||||||
// pool:
|
// pool:
|
||||||
v.SetDefault(cfgPoolErrorThreshold, defaultPoolErrorThreshold)
|
v.SetDefault(cfgPoolErrorThreshold, defaultPoolErrorThreshold)
|
||||||
v.SetDefault(cfgStreamTimeout, defaultStreamTimeout)
|
v.SetDefault(cfgStreamTimeout, defaultStreamTimeout)
|
||||||
|
|
|
@ -54,6 +54,12 @@ logger:
|
||||||
level: debug
|
level: debug
|
||||||
destination: stdout
|
destination: stdout
|
||||||
|
|
||||||
|
# log http request data (URI, headers, query, etc)
|
||||||
|
http_logging:
|
||||||
|
enabled: false
|
||||||
|
# max body size to log
|
||||||
|
max_body: 1024
|
||||||
|
|
||||||
# RPC endpoint and order of resolving of bucket names
|
# RPC endpoint and order of resolving of bucket names
|
||||||
rpc_endpoint: http://morph-chain.frostfs.devenv:30333
|
rpc_endpoint: http://morph-chain.frostfs.devenv:30333
|
||||||
resolve_order:
|
resolve_order:
|
||||||
|
|
|
@ -105,61 +105,63 @@ const (
|
||||||
FailedToPassAuthentication = "failed to pass authentication" // Error in ../../api/middleware/auth.go
|
FailedToPassAuthentication = "failed to pass authentication" // Error in ../../api/middleware/auth.go
|
||||||
FailedToResolveCID = "failed to resolve CID" // Debug in ../../api/middleware/metrics.go
|
FailedToResolveCID = "failed to resolve CID" // Debug in ../../api/middleware/metrics.go
|
||||||
RequestStart = "request start" // Info in ../../api/middleware/reqinfo.go
|
RequestStart = "request start" // Info in ../../api/middleware/reqinfo.go
|
||||||
FailedToUnescapeObjectName = "failed to unescape object name" // Warn in ../../api/middleware/reqinfo.go
|
|
||||||
CouldNotHandleMessage = "could not handle message" // Error in ../../api/notifications/controller.go
|
RequestHTTP = "http request"
|
||||||
CouldNotACKMessage = "could not ACK message" // Error in ../../api/notifications/controller.go
|
FailedToUnescapeObjectName = "failed to unescape object name" // Warn in ../../api/middleware/reqinfo.go
|
||||||
CouldntMarshalAnEvent = "couldn't marshal an event" // Error in ../../api/notifications/controller.go
|
CouldNotHandleMessage = "could not handle message" // Error in ../../api/notifications/controller.go
|
||||||
CouldntSendAnEventToTopic = "couldn't send an event to topic" // Error in ../../api/notifications/controller.go
|
CouldNotACKMessage = "could not ACK message" // Error in ../../api/notifications/controller.go
|
||||||
InvalidDefaultMaxAge = "invalid defaultMaxAge" // Fatal in ../../cmd/s3-gw/app_settings.go
|
CouldntMarshalAnEvent = "couldn't marshal an event" // Error in ../../api/notifications/controller.go
|
||||||
CantShutDownService = "can't shut down service" // Panic in ../../cmd/s3-gw/service.go
|
CouldntSendAnEventToTopic = "couldn't send an event to topic" // Error in ../../api/notifications/controller.go
|
||||||
CouldntGenerateRandomKey = "couldn't generate random key" // Fatal in ../../cmd/s3-gw/app.go
|
InvalidDefaultMaxAge = "invalid defaultMaxAge" // Fatal in ../../cmd/s3-gw/app_settings.go
|
||||||
FailedToEnableNotifications = "failed to enable notifications" // Fatal in ../../cmd/s3-gw/app.go
|
CantShutDownService = "can't shut down service" // Panic in ../../cmd/s3-gw/service.go
|
||||||
CouldntInitializeLayer = "couldn't initialize layer" // Fatal in ../../cmd/s3-gw/app.go
|
CouldntGenerateRandomKey = "couldn't generate random key" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
FailedToCreateResolver = "failed to create resolver" // Fatal in ../../cmd/s3-gw/app.go
|
FailedToEnableNotifications = "failed to enable notifications" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
CouldNotLoadFrostFSPrivateKey = "could not load FrostFS private key" // Fatal in ../../cmd/s3-gw/app.go
|
CouldntInitializeLayer = "couldn't initialize layer" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
FailedToCreateConnectionPool = "failed to create connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
FailedToCreateResolver = "failed to create resolver" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
FailedToDialConnectionPool = "failed to dial connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
CouldNotLoadFrostFSPrivateKey = "could not load FrostFS private key" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
FailedToCreateTreePool = "failed to create tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
FailedToCreateConnectionPool = "failed to create connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
FailedToDialTreePool = "failed to dial tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
FailedToDialConnectionPool = "failed to dial connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
ListenAndServe = "listen and serve" // Fatal in ../../cmd/s3-gw/app.go
|
FailedToCreateTreePool = "failed to create tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
NoHealthyServers = "no healthy servers" // Fatal in ../../cmd/s3-gw/app.go
|
FailedToDialTreePool = "failed to dial tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
CouldNotInitializeAPIHandler = "could not initialize API handler" // Fatal in ../../cmd/s3-gw/app.go
|
ListenAndServe = "listen and serve" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
RuntimeSoftMemoryDefinedWithGOMEMLIMIT = "soft runtime memory defined with GOMEMLIMIT environment variable, config value skipped" // Warn in ../../cmd/s3-gw/app.go
|
NoHealthyServers = "no healthy servers" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
RuntimeSoftMemoryLimitUpdated = "soft runtime memory limit value updated" // Info in ../../cmd/s3-gw/app.go
|
CouldNotInitializeAPIHandler = "could not initialize API handler" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
AnonRequestSkipFrostfsIDValidation = "anon request, skip FrostfsID validation" // Debug in ../../api/middleware/auth.go
|
RuntimeSoftMemoryDefinedWithGOMEMLIMIT = "soft runtime memory defined with GOMEMLIMIT environment variable, config value skipped" // Warn in ../../cmd/s3-gw/app.go
|
||||||
FrostfsIDValidationFailed = "FrostfsID validation failed" // Error in ../../api/middleware/auth.go
|
RuntimeSoftMemoryLimitUpdated = "soft runtime memory limit value updated" // Info in ../../cmd/s3-gw/app.go
|
||||||
InitFrostfsIDContractFailed = "init frostfsid contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
AnonRequestSkipFrostfsIDValidation = "anon request, skip FrostfsID validation" // Debug in ../../api/middleware/auth.go
|
||||||
InitPolicyContractFailed = "init policy contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
FrostfsIDValidationFailed = "FrostfsID validation failed" // Error in ../../api/middleware/auth.go
|
||||||
ControlAPIHealthcheck = "healthcheck request"
|
InitFrostfsIDContractFailed = "init frostfsid contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
ControlAPIPutPolicies = "put policies request"
|
InitPolicyContractFailed = "init policy contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
||||||
ControlAPIRemovePolicies = "remove policies request"
|
ControlAPIHealthcheck = "healthcheck request"
|
||||||
ControlAPIGetPolicy = "get policy request"
|
ControlAPIPutPolicies = "put policies request"
|
||||||
ControlAPIListPolicies = "list policies request"
|
ControlAPIRemovePolicies = "remove policies request"
|
||||||
PolicyValidationFailed = "policy validation failed"
|
ControlAPIGetPolicy = "get policy request"
|
||||||
ParseTreeNode = "parse tree node"
|
ControlAPIListPolicies = "list policies request"
|
||||||
FailedToGetRealObjectSize = "failed to get real object size"
|
PolicyValidationFailed = "policy validation failed"
|
||||||
CouldntDeleteObjectFromStorageContinueDeleting = "couldn't delete object from storage, continue deleting from tree"
|
ParseTreeNode = "parse tree node"
|
||||||
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
|
FailedToGetRealObjectSize = "failed to get real object size"
|
||||||
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
|
CouldntDeleteObjectFromStorageContinueDeleting = "couldn't delete object from storage, continue deleting from tree"
|
||||||
CouldNotCloseRequestBody = "could not close request body"
|
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
|
||||||
BucketOwnerKeyIsMissing = "bucket owner key is missing"
|
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
|
||||||
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
|
CouldNotCloseRequestBody = "could not close request body"
|
||||||
SuccessfulAuth = "successful auth"
|
BucketOwnerKeyIsMissing = "bucket owner key is missing"
|
||||||
PolicyRequest = "policy request"
|
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
|
||||||
FailedToGenerateRequestID = "failed to generate request id"
|
SuccessfulAuth = "successful auth"
|
||||||
InvalidBucketObjectLockEnabledHeader = "invalid X-Amz-Bucket-Object-Lock-Enabled header"
|
PolicyRequest = "policy request"
|
||||||
InvalidTreeKV = "invalid tree service meta KV"
|
FailedToGenerateRequestID = "failed to generate request id"
|
||||||
FailedToWriteResponse = "failed to write response"
|
InvalidBucketObjectLockEnabledHeader = "invalid X-Amz-Bucket-Object-Lock-Enabled header"
|
||||||
PolicyCouldntBeConvertedToNativeRules = "policy couldn't be converted to native rules, only s3 rules be applied"
|
InvalidTreeKV = "invalid tree service meta KV"
|
||||||
CouldntCacheSubject = "couldn't cache subject info"
|
FailedToWriteResponse = "failed to write response"
|
||||||
UserGroupsListIsEmpty = "user groups list is empty, subject not found"
|
PolicyCouldntBeConvertedToNativeRules = "policy couldn't be converted to native rules, only s3 rules be applied"
|
||||||
CouldntCacheUserKey = "couldn't cache user key"
|
CouldntCacheSubject = "couldn't cache subject info"
|
||||||
FoundSeveralBucketCorsNodes = "found several bucket cors nodes, latest be used"
|
UserGroupsListIsEmpty = "user groups list is empty, subject not found"
|
||||||
FoundSeveralNotificationConfigurationNodes = "found several notification configuration nodes, latest be used"
|
CouldntCacheUserKey = "couldn't cache user key"
|
||||||
FoundSeveralObjectTaggingNodes = "found several object tagging nodes, latest be used"
|
FoundSeveralBucketCorsNodes = "found several bucket cors nodes, latest be used"
|
||||||
FoundSeveralBucketTaggingNodes = "found several bucket tagging nodes, latest be used"
|
FoundSeveralNotificationConfigurationNodes = "found several notification configuration nodes, latest be used"
|
||||||
FoundSeveralBucketSettingsNodes = "found several bucket settings nodes, latest be used"
|
FoundSeveralObjectTaggingNodes = "found several object tagging nodes, latest be used"
|
||||||
UnexpectedMultiNodeIDsInSubTreeMultiParts = "unexpected multi node ids in sub tree multi parts"
|
FoundSeveralBucketTaggingNodes = "found several bucket tagging nodes, latest be used"
|
||||||
FoundSeveralSystemNodes = "found several system nodes, latest be used"
|
FoundSeveralBucketSettingsNodes = "found several bucket settings nodes, latest be used"
|
||||||
FailedToParsePartInfo = "failed to parse part info"
|
UnexpectedMultiNodeIDsInSubTreeMultiParts = "unexpected multi node ids in sub tree multi parts"
|
||||||
|
FoundSeveralSystemNodes = "found several system nodes, latest be used"
|
||||||
|
FailedToParsePartInfo = "failed to parse part info"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Add table
Reference in a new issue