diff --git a/api/middleware/log_http.go b/api/middleware/log_http.go new file mode 100644 index 0000000..ad1dd8a --- /dev/null +++ b/api/middleware/log_http.go @@ -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 +} diff --git a/api/router.go b/api/router.go index 0f86e2e..af55c18 100644 --- a/api/router.go +++ b/api/router.go @@ -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, diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index f46c39d..ca467d7 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -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, diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index 18ad39e..9611162 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -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) diff --git a/config/config.yaml b/config/config.yaml index 4ed6be3..afb2e98 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -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: diff --git a/internal/logs/logs.go b/internal/logs/logs.go index 9e1d836..ffccaf8 100644 --- a/internal/logs/logs.go +++ b/internal/logs/logs.go @@ -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" )