diff --git a/README.md b/README.md index 2d43283..71dfaea 100755 --- a/README.md +++ b/README.md @@ -137,7 +137,7 @@ func OTELMeterProvider() otelmetric.MeterProvider OTELMeterProvider returns the global OTel MeterProvider. This is a convenience accessor for code that needs the interface type. -## func [SetOTELGRPCClientOptions]() +## func [SetOTELGRPCClientOptions]() ```go func SetOTELGRPCClientOptions(opts ...otelgrpc.Option) @@ -146,7 +146,7 @@ func SetOTELGRPCClientOptions(opts ...otelgrpc.Option) Deprecated: Use SetOTELOptions instead. Only applies when OTEL\_USE\_LEGACY\_INSTRUMENTATION=true. -## func [SetOTELGRPCServerOptions]() +## func [SetOTELGRPCServerOptions]() ```go func SetOTELGRPCServerOptions(opts ...otelgrpc.Option) @@ -155,7 +155,7 @@ func SetOTELGRPCServerOptions(opts ...otelgrpc.Option) Deprecated: Use SetOTELOptions instead. Only applies when OTEL\_USE\_LEGACY\_INSTRUMENTATION=true. -## func [SetOTELOptions]() +## func [SetOTELOptions]() ```go func SetOTELOptions(opts grpcotel.Options) @@ -314,7 +314,7 @@ type CB interface { ``` -### func [New]() +### func [New]() ```go func New(c config.Config) CB diff --git a/config/README.md b/config/README.md index 9ad893b..e7bbf18 100755 --- a/config/README.md +++ b/config/README.md @@ -66,7 +66,7 @@ import "github.com/go-coldbrew/core/config" -## type [Config]() +## type [Config]() Config is the configuration for the Coldbrew server It is populated from environment variables and has sensible defaults for all fields so that you can just use it as is without any configuration The following environment variables are supported and can be used to override the defaults for the fields @@ -182,6 +182,16 @@ type Config struct { // per-request debug logging. The header value should be a valid log level // (e.g., "debug"). Default: "x-debug-log-level". DebugLogHeaderName string `envconfig:"DEBUG_LOG_HEADER_NAME" default:"x-debug-log-level"` + // RateLimitPerSecond is the maximum number of incoming requests per second + // for this pod. This is a per-pod in-memory limit — with N pods, the + // effective cluster-wide limit is N × this value. Set to 0 to disable (default). + // For distributed rate limiting, use interceptors.SetRateLimiter() with a custom implementation. + RateLimitPerSecond float64 `envconfig:"RATE_LIMIT_PER_SECOND" default:"0"` + // RateLimitBurst is the maximum burst size for the token bucket rate limiter. + // Only takes effect when RateLimitPerSecond > 0. + RateLimitBurst int `envconfig:"RATE_LIMIT_BURST" default:"1"` + // DisableRateLimit disables the rate limiting interceptor entirely. + DisableRateLimit bool `envconfig:"DISABLE_RATE_LIMIT" default:"false"` // DisableVTProtobuf disables the use of the vtprotobuf marshaller and unmarshaller for GRPC // https://github.com/planetscale/vtprotobuf DisableVTProtobuf bool `envconfig:"DISABLE_VT_PROTOBUF" default:"false"` @@ -246,7 +256,7 @@ type Config struct { ``` -### func \(Config\) [Validate]() +### func \(Config\) [Validate]() ```go func (c Config) Validate() []string diff --git a/config/config.go b/config/config.go index 7c55749..0cb2810 100644 --- a/config/config.go +++ b/config/config.go @@ -120,6 +120,16 @@ type Config struct { // per-request debug logging. The header value should be a valid log level // (e.g., "debug"). Default: "x-debug-log-level". DebugLogHeaderName string `envconfig:"DEBUG_LOG_HEADER_NAME" default:"x-debug-log-level"` + // RateLimitPerSecond is the maximum number of incoming requests per second + // for this pod. This is a per-pod in-memory limit — with N pods, the + // effective cluster-wide limit is N × this value. Set to 0 to disable (default). + // For distributed rate limiting, use interceptors.SetRateLimiter() with a custom implementation. + RateLimitPerSecond float64 `envconfig:"RATE_LIMIT_PER_SECOND" default:"0"` + // RateLimitBurst is the maximum burst size for the token bucket rate limiter. + // Only takes effect when RateLimitPerSecond > 0. + RateLimitBurst int `envconfig:"RATE_LIMIT_BURST" default:"1"` + // DisableRateLimit disables the rate limiting interceptor entirely. + DisableRateLimit bool `envconfig:"DISABLE_RATE_LIMIT" default:"false"` // DisableVTProtobuf disables the use of the vtprotobuf marshaller and unmarshaller for GRPC // https://github.com/planetscale/vtprotobuf DisableVTProtobuf bool `envconfig:"DISABLE_VT_PROTOBUF" default:"false"` @@ -258,5 +268,12 @@ func (c Config) Validate() []string { warnings = append(warnings, "GRPCServerDefaultTimeoutInSeconds exceeds ShutdownDurationInSeconds; in-flight RPCs may be killed before timeout") } + if c.RateLimitPerSecond < 0 { + warnings = append(warnings, "RateLimitPerSecond is negative; use 0 to disable rate limiting") + } + if c.RateLimitPerSecond > 0 && c.RateLimitBurst <= 0 { + warnings = append(warnings, "RateLimitBurst should be positive when RateLimitPerSecond is set") + } + return warnings } diff --git a/core.go b/core.go index 47a2805..88cd4b1 100644 --- a/core.go +++ b/core.go @@ -148,6 +148,12 @@ func (c *cb) processConfig() { if c.config.DebugLogHeaderName != "" { interceptors.SetDebugLogHeaderName(c.config.DebugLogHeaderName) } + if c.config.DisableRateLimit { + interceptors.SetDisableRateLimit(true) + } + if c.config.RateLimitPerSecond > 0 { + interceptors.SetDefaultRateLimit(c.config.RateLimitPerSecond, c.config.RateLimitBurst) + } if c.config.EnablePrometheusGRPCHistogram { if len(c.config.PrometheusGRPCHistogramBuckets) > 0 { interceptors.SetServerMetricsOptions( diff --git a/go.mod b/go.mod index d483b2e..0556f72 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 github.com/go-coldbrew/errors v0.2.14 github.com/go-coldbrew/hystrixprometheus v0.1.2 - github.com/go-coldbrew/interceptors v0.1.23 + github.com/go-coldbrew/interceptors v0.1.24 github.com/go-coldbrew/log v0.3.2 github.com/go-coldbrew/options v0.3.0 github.com/go-coldbrew/tracing v0.2.2 @@ -285,6 +285,7 @@ require ( golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c // indirect golang.org/x/term v0.41.0 // indirect golang.org/x/text v0.35.0 // indirect + golang.org/x/time v0.15.0 // indirect golang.org/x/tools v0.43.0 // indirect golang.org/x/vuln v1.1.4 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect diff --git a/go.sum b/go.sum index b59c621..36e50ae 100644 --- a/go.sum +++ b/go.sum @@ -197,8 +197,8 @@ github.com/go-coldbrew/errors v0.2.14 h1:SQcV9Kw+hNfNGXjvu4fWl5uXw5NRD6lW+rkHgtz github.com/go-coldbrew/errors v0.2.14/go.mod h1:f9eGGKKF9KmyCpSWZRSqqV4HRWqbzmh1E9lyL8jyL+Y= github.com/go-coldbrew/hystrixprometheus v0.1.2 h1:WSt4FtYr8xNDKgdGWYpMfXGFIK7zdDSBwDSbpuPhBHI= github.com/go-coldbrew/hystrixprometheus v0.1.2/go.mod h1:OrNRHHxZagpmQXNp//oHKOemGSU0ScOqEcJgeKbJ+wg= -github.com/go-coldbrew/interceptors v0.1.23 h1:mbrAx4ztOMei2DeLyxjXKttUvGAFeU5heDVMPhTOctw= -github.com/go-coldbrew/interceptors v0.1.23/go.mod h1:CjH6gc6GrdTNJnw+d7JigYtreWW5PcaGc5QH7zpsJPk= +github.com/go-coldbrew/interceptors v0.1.24 h1:z24O4TRxT/+fHTJIILml9ulmK2cYGKKCENpxxL8pGaA= +github.com/go-coldbrew/interceptors v0.1.24/go.mod h1:YkAmxYZ7R3Sjif0WpzhuhGjv2j67CwUoqnq9K8XgpBY= github.com/go-coldbrew/log v0.3.2 h1:CoHa0PGX7a7o/Cv/ke7PdQfq4LKtbPVypUf3uXcRLMs= github.com/go-coldbrew/log v0.3.2/go.mod h1:tumRNCmLWRep5wnhS/vzDQ7UMinF6OZ7WW8K/qlXAzc= github.com/go-coldbrew/options v0.3.0 h1:JwyVntb9bzBeFdaHFK6yGVVz30G3aVlqJJ6uVyYQfCc= @@ -796,6 +796,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=