WIP: [#369] Enhanced http requests logging #425
6 changed files with 128 additions and 42 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
|
||||
}
|
|
@ -109,6 +109,7 @@ type Config struct {
|
|||
Handler Handler
|
||||
Center s3middleware.Center
|
||||
Log *zap.Logger
|
||||
LogHTTP s3middleware.LogHTTPSettings
|
||||
Metrics *metrics.AppMetrics
|
||||
|
||||
MiddlewareSettings Settings
|
||||
|
@ -129,6 +130,7 @@ type Config struct {
|
|||
func NewRouter(cfg Config) *chi.Mux {
|
||||
api := chi.NewRouter()
|
||||
api.Use(
|
||||
s3middleware.LogHTTP(cfg.Log, cfg.LogHTTP),
|
||||
s3middleware.Request(cfg.Log, cfg.MiddlewareSettings),
|
||||
middleware.ThrottleWithOpts(cfg.Throttle),
|
||||
middleware.Recoverer,
|
||||
|
|
|
@ -83,6 +83,8 @@ type (
|
|||
|
||||
appSettings struct {
|
||||
logLevel zap.AtomicLevel
|
||||
httpLoggingEnabled bool
|
||||
maxHTTPLogBody int64
|
||||
maxClient maxClientsConfig
|
||||
defaultMaxAge int
|
||||
reconnectInterval time.Duration
|
||||
|
@ -193,6 +195,8 @@ func newAppSettings(log *Logger, v *viper.Viper) *appSettings {
|
|||
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
|
||||
reconnectInterval: fetchReconnectInterval(v),
|
||||
frostfsidValidation: v.GetBool(cfgFrostfsIDValidationEnabled),
|
||||
httpLoggingEnabled: v.GetBool(cfgLoggerRequestEnabled),
|
||||
maxHTTPLogBody: v.GetInt64(cfgLoggerRequestMaxBody),
|
||||
}
|
||||
|
||||
settings.resolveZoneList = v.GetStringSlice(cfgResolveBucketAllow)
|
||||
|
@ -682,6 +686,10 @@ func (a *App) Serve(ctx context.Context) {
|
|||
Handler: a.api,
|
||||
Center: a.ctr,
|
||||
Log: a.log,
|
||||
LogHTTP: s3middleware.LogHTTPSettings{
|
||||
Enabled: a.settings.httpLoggingEnabled,
|
||||
MaxBody: a.settings.maxHTTPLogBody,
|
||||
},
|
||||
Metrics: a.metrics,
|
||||
Domains: domains,
|
||||
|
||||
|
|
|
@ -75,6 +75,9 @@ const ( // Settings.
|
|||
cfgLoggerLevel = "logger.level"
|
||||
cfgLoggerDestination = "logger.destination"
|
||||
|
||||
cfgLoggerRequestEnabled = "http_logging.enabled"
|
||||
cfgLoggerRequestMaxBody = "http_logging.max_body"
|
||||
|
||||
// Wallet.
|
||||
cfgWalletPath = "wallet.path"
|
||||
cfgWalletAddress = "wallet.address"
|
||||
|
@ -715,6 +718,10 @@ func newSettings() *viper.Viper {
|
|||
v.SetDefault(cfgLoggerLevel, "debug")
|
||||
v.SetDefault(cfgLoggerDestination, "stdout")
|
||||
|
||||
// http logger
|
||||
v.SetDefault(cfgLoggerRequestEnabled, false)
|
||||
v.SetDefault(cfgLoggerRequestMaxBody, 1024)
|
||||
|
||||
// pool:
|
||||
v.SetDefault(cfgPoolErrorThreshold, defaultPoolErrorThreshold)
|
||||
v.SetDefault(cfgStreamTimeout, defaultStreamTimeout)
|
||||
|
|
|
@ -47,6 +47,12 @@ logger:
|
|||
level: debug
|
||||
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: http://morph-chain.frostfs.devenv:30333
|
||||
resolve_order:
|
||||
|
|
|
@ -100,46 +100,48 @@ const (
|
|||
FailedToPassAuthentication = "failed to pass authentication" // Error in ../../api/middleware/auth.go
|
||||
FailedToResolveCID = "failed to resolve CID" // Debug in ../../api/middleware/metrics.go
|
||||
RequestStart = "request start" // Info in ../../api/middleware/reqinfo.go
|
||||
FailedToUnescapeObjectName = "failed to unescape object name" // Warn in ../../api/middleware/reqinfo.go
|
||||
InvalidDefaultMaxAge = "invalid defaultMaxAge" // Fatal in ../../cmd/s3-gw/app_settings.go
|
||||
CantShutDownService = "can't shut down service" // Panic in ../../cmd/s3-gw/service.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
|
||||
CouldNotLoadFrostFSPrivateKey = "could not load FrostFS private key" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToCreateConnectionPool = "failed to create connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToDialConnectionPool = "failed to dial connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToCreateTreePool = "failed to create tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToDialTreePool = "failed to dial tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
ListenAndServe = "listen and serve" // Fatal in ../../cmd/s3-gw/app.go
|
||||
NoHealthyServers = "no healthy servers" // Fatal in ../../cmd/s3-gw/app.go
|
||||
CouldNotInitializeAPIHandler = "could not initialize API handler" // 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
|
||||
RuntimeSoftMemoryLimitUpdated = "soft runtime memory limit value updated" // Info in ../../cmd/s3-gw/app.go
|
||||
AnonRequestSkipFrostfsIDValidation = "anon request, skip FrostfsID validation" // Debug in ../../api/middleware/auth.go
|
||||
FrostfsIDValidationFailed = "FrostfsID validation failed" // Error in ../../api/middleware/auth.go
|
||||
InitFrostfsIDContractFailed = "init frostfsid contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
||||
InitPolicyContractFailed = "init policy contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
||||
PolicyValidationFailed = "policy validation failed"
|
||||
ServerReconnecting = "reconnecting server..."
|
||||
ServerReconnectedSuccessfully = "server reconnected successfully"
|
||||
ServerReconnectFailed = "failed to reconnect server"
|
||||
ParseTreeNode = "parse tree node"
|
||||
FailedToGetRealObjectSize = "failed to get real object size"
|
||||
CouldntDeleteObjectFromStorageContinueDeleting = "couldn't delete object from storage, continue deleting from tree"
|
||||
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
|
||||
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
|
||||
CouldNotCloseRequestBody = "could not close request body"
|
||||
BucketOwnerKeyIsMissing = "bucket owner key is missing"
|
||||
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
|
||||
SuccessfulAuth = "successful auth"
|
||||
PolicyRequest = "policy request"
|
||||
FailedToGenerateRequestID = "failed to generate request id"
|
||||
InvalidBucketObjectLockEnabledHeader = "invalid X-Amz-Bucket-Object-Lock-Enabled header"
|
||||
InvalidTreeKV = "invalid tree service meta KV"
|
||||
FailedToWriteResponse = "failed to write response"
|
||||
WarnDuplicateAddress = "duplicate address"
|
||||
PolicyCouldntBeConvertedToNativeRules = "policy couldn't be converted to native rules, only s3 rules be applied"
|
||||
CouldntCacheSubject = "couldn't cache subject info"
|
||||
UserGroupsListIsEmpty = "user groups list is empty, subject not found"
|
||||
CouldntCacheUserKey = "couldn't cache user key"
|
||||
|
||||
RequestHTTP = "http request"
|
||||
FailedToUnescapeObjectName = "failed to unescape object name" // Warn in ../../api/middleware/reqinfo.go
|
||||
InvalidDefaultMaxAge = "invalid defaultMaxAge" // Fatal in ../../cmd/s3-gw/app_settings.go
|
||||
CantShutDownService = "can't shut down service" // Panic in ../../cmd/s3-gw/service.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
|
||||
CouldNotLoadFrostFSPrivateKey = "could not load FrostFS private key" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToCreateConnectionPool = "failed to create connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToDialConnectionPool = "failed to dial connection pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToCreateTreePool = "failed to create tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
FailedToDialTreePool = "failed to dial tree pool" // Fatal in ../../cmd/s3-gw/app.go
|
||||
ListenAndServe = "listen and serve" // Fatal in ../../cmd/s3-gw/app.go
|
||||
NoHealthyServers = "no healthy servers" // Fatal in ../../cmd/s3-gw/app.go
|
||||
CouldNotInitializeAPIHandler = "could not initialize API handler" // 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
|
||||
RuntimeSoftMemoryLimitUpdated = "soft runtime memory limit value updated" // Info in ../../cmd/s3-gw/app.go
|
||||
AnonRequestSkipFrostfsIDValidation = "anon request, skip FrostfsID validation" // Debug in ../../api/middleware/auth.go
|
||||
FrostfsIDValidationFailed = "FrostfsID validation failed" // Error in ../../api/middleware/auth.go
|
||||
InitFrostfsIDContractFailed = "init frostfsid contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
||||
InitPolicyContractFailed = "init policy contract failed" // Fatal in ../../cmd/s3-gw/app.go
|
||||
PolicyValidationFailed = "policy validation failed"
|
||||
ServerReconnecting = "reconnecting server..."
|
||||
ServerReconnectedSuccessfully = "server reconnected successfully"
|
||||
ServerReconnectFailed = "failed to reconnect server"
|
||||
ParseTreeNode = "parse tree node"
|
||||
FailedToGetRealObjectSize = "failed to get real object size"
|
||||
CouldntDeleteObjectFromStorageContinueDeleting = "couldn't delete object from storage, continue deleting from tree"
|
||||
CouldntPutAccessBoxIntoCache = "couldn't put accessbox into cache"
|
||||
InvalidAccessBoxCacheRemovingCheckInterval = "invalid accessbox check removing interval, using default value"
|
||||
CouldNotCloseRequestBody = "could not close request body"
|
||||
BucketOwnerKeyIsMissing = "bucket owner key is missing"
|
||||
SettingsNodeInvalidOwnerKey = "settings node: invalid owner key"
|
||||
SuccessfulAuth = "successful auth"
|
||||
PolicyRequest = "policy request"
|
||||
FailedToGenerateRequestID = "failed to generate request id"
|
||||
InvalidBucketObjectLockEnabledHeader = "invalid X-Amz-Bucket-Object-Lock-Enabled header"
|
||||
InvalidTreeKV = "invalid tree service meta KV"
|
||||
FailedToWriteResponse = "failed to write response"
|
||||
WarnDuplicateAddress = "duplicate address"
|
||||
PolicyCouldntBeConvertedToNativeRules = "policy couldn't be converted to native rules, only s3 rules be applied"
|
||||
CouldntCacheSubject = "couldn't cache subject info"
|
||||
UserGroupsListIsEmpty = "user groups list is empty, subject not found"
|
||||
CouldntCacheUserKey = "couldn't cache user key"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue