Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 99 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,19 @@ The core module is the base module for cold brew and provides the base implement
- [func SetupLogger\(logLevel string, jsonlogs bool\) error](<#SetupLogger>)
- [func SetupNROpenTelemetry\(serviceName, license, version string, ratio float64\) error](<#SetupNROpenTelemetry>)
- [func SetupNewRelic\(serviceName, apiKey string, tracing bool\) error](<#SetupNewRelic>)
- [func SetupOpenTelemetry\(config OTLPConfig\) error](<#SetupOpenTelemetry>)
- [func SetupReleaseName\(rel string\)](<#SetupReleaseName>)
- [func SetupSentry\(dsn string\)](<#SetupSentry>)
- [type CB](<#CB>)
- [func New\(c config.Config\) CB](<#New>)
- [type CBGracefulStopper](<#CBGracefulStopper>)
- [type CBService](<#CBService>)
- [type CBStopper](<#CBStopper>)
- [type OTLPConfig](<#OTLPConfig>)


<a name="ConfigureInterceptors"></a>
## func [ConfigureInterceptors](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L204>)
## func [ConfigureInterceptors](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L306>)

```go
func ConfigureInterceptors(DoNotLogGRPCReflection bool, traceHeaderName string)
Expand All @@ -67,7 +69,7 @@ func ConfigureInterceptors(DoNotLogGRPCReflection bool, traceHeaderName string)
ConfigureInterceptors configures the interceptors package with the provided DoNotLogGRPCReflection is a boolean that indicates whether to log the grpc.reflection.v1alpha.ServerReflection service calls in logs traceHeaderName is the name of the header to use for tracing \(e.g. X\-Trace\-Id\) \- if empty, defaults to X\-Trace\-Id

<a name="InitializeVTProto"></a>
## func [InitializeVTProto](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L249>)
## func [InitializeVTProto](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L351>)

```go
func InitializeVTProto()
Expand All @@ -78,7 +80,7 @@ InitializeVTProto initializes the vtproto package for use with the service
https://github.com/planetscale/vtprotobuf?tab=readme-ov-file#mixing-protobuf-implementations-with-grpc

<a name="SetupAutoMaxProcs"></a>
## func [SetupAutoMaxProcs](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L215>)
## func [SetupAutoMaxProcs](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L317>)

```go
func SetupAutoMaxProcs()
Expand All @@ -96,7 +98,7 @@ func SetupEnvironment(env string)
SetupEnvironment sets the environment This is used to identify the environment in Sentry and New Relic env is the environment to set for the service \(e.g. prod, staging, dev\)

<a name="SetupHystrixPrometheus"></a>
## func [SetupHystrixPrometheus](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L196>)
## func [SetupHystrixPrometheus](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L298>)

```go
func SetupHystrixPrometheus()
Expand All @@ -114,13 +116,22 @@ func SetupLogger(logLevel string, jsonlogs bool) error
SetupLogger sets up the logger It uses the coldbrew logger to log messages to stdout logLevel is the log level to set for the logger jsonlogs is a boolean to enable or disable json logs

<a name="SetupNROpenTelemetry"></a>
## func [SetupNROpenTelemetry](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L140>)
## func [SetupNROpenTelemetry](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L282>)

```go
func SetupNROpenTelemetry(serviceName, license, version string, ratio float64) error
```

setupOpenTelemetry sets up the OpenTelemetry tracing It uses the New Relic OTLP exporter to send traces to New Relic One APM and Insights serviceName is the name of the service license is the New Relic license key version is the version of the service ratio is the sampling ratio to use for traces
SetupNROpenTelemetry sets up OpenTelemetry tracing with New Relic

This function configures OpenTelemetry to send traces to New Relic's OTLP endpoint. It's a convenience wrapper around SetupOpenTelemetry with New Relic\-specific configuration.

Parameters:

- serviceName: the name of the service
- license: the New Relic license key
- version: the version of the service
- ratio: the sampling ratio to use for traces \(0.0 to 1.0\)

<a name="SetupNewRelic"></a>
## func [SetupNewRelic](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L45>)
Expand All @@ -131,6 +142,44 @@ func SetupNewRelic(serviceName, apiKey string, tracing bool) error

SetupNewRelic sets up the New Relic tracing and monitoring agent for the service It uses the New Relic Go Agent to send traces to New Relic One APM and Insights serviceName is the name of the service apiKey is the New Relic license key tracing is a boolean to enable or disable tracing

<a name="SetupOpenTelemetry"></a>
## func [SetupOpenTelemetry](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L200>)

```go
func SetupOpenTelemetry(config OTLPConfig) error
```

SetupOpenTelemetry sets up OpenTelemetry tracing with a generic OTLP exporter

This function provides a flexible way to configure OpenTelemetry tracing with any OTLP\-compatible backend. It sets up the trace provider, configures sampling, and optionally sets up an OpenTracing bridge for compatibility.

Example usage with Jaeger:

```
config := OTLPConfig{
Endpoint: "localhost:4317",
ServiceName: "my-service",
ServiceVersion: "v1.0.0",
SamplingRatio: 0.1,
UseOpenTracingBridge: true,
Insecure: true, // for local development
}
err := SetupOpenTelemetry(config)
```

Example usage with Honeycomb:

```
config := OTLPConfig{
Endpoint: "api.honeycomb.io:443",
Headers: map[string]string{"x-honeycomb-team": "your-api-key"},
ServiceName: "my-service",
ServiceVersion: "v1.0.0",
SamplingRatio: 0.2,
}
err := SetupOpenTelemetry(config)
```

<a name="SetupReleaseName"></a>
## func [SetupReleaseName](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L103>)

Expand Down Expand Up @@ -171,7 +220,7 @@ type CB interface {
```

<a name="New"></a>
### func [New](<https://github.com/go-coldbrew/core/blob/main/core.go#L444>)
### func [New](<https://github.com/go-coldbrew/core/blob/main/core.go#L502>)

```go
func New(c config.Config) CB
Expand Down Expand Up @@ -223,4 +272,47 @@ type CBStopper interface {
}
```

<a name="OTLPConfig"></a>
## type [OTLPConfig](<https://github.com/go-coldbrew/core/blob/main/initializers.go#L138-L170>)

OTLPConfig holds configuration for OpenTelemetry OTLP exporter

This struct provides a flexible way to configure OpenTelemetry tracing with any OTLP\-compatible backend \(e.g., Jaeger, Honeycomb, New Relic, etc.\)

```go
type OTLPConfig struct {
// Endpoint is the OTLP gRPC endpoint to send traces to
// Examples: "localhost:4317", "otlp.nr-data.net:4317", "api.honeycomb.io:443"
Endpoint string

// Headers are custom headers to send with each request
// Examples:
// New Relic: {"api-key": "your-license-key"}
// Honeycomb: {"x-honeycomb-team": "your-api-key"}
Headers map[string]string

// ServiceName is the name of the service sending traces
ServiceName string

// ServiceVersion is the version of the service
ServiceVersion string

// SamplingRatio is the ratio of traces to sample (0.0 to 1.0)
// 1.0 means sample all traces, 0.1 means sample 10% of traces
SamplingRatio float64

// Compression specifies the compression type (e.g., "gzip", "none")
// If empty, defaults to "gzip"
Compression string

// UseOpenTracingBridge determines whether to set up OpenTracing compatibility bridge
// This allows using OpenTracing instrumentation with OpenTelemetry
UseOpenTracingBridge bool

// Insecure disables TLS verification for the connection
// Only use this for local development or testing
Insecure bool
}
```

Generated by [gomarkdoc](<https://github.com/princjef/gomarkdoc>)
27 changes: 26 additions & 1 deletion config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import "github.com/go-coldbrew/core/config"


<a name="Config"></a>
## type [Config](<https://github.com/go-coldbrew/core/blob/main/config/config.go#L6-L101>)
## type [Config](<https://github.com/go-coldbrew/core/blob/main/config/config.go#L6-L129>)

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

Expand Down Expand Up @@ -116,6 +116,31 @@ type Config struct {
// 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"`
// GRPCMaxSendMsgSize and GRPCMaxRecvMsgSize are the maximum message
// sizes for sending and receiving messages over GRPC
GRPCMaxSendMsgSize int `envconfig:"GRPC_MAX_SEND_MSG_SIZE" default:"2147483647"` // Unlimited
GRPCMaxRecvMsgSize int `envconfig:"GRPC_MAX_RECV_MSG_SIZE" default:"4194304"` // 4MB

// OTLPEndpoint is the OTLP gRPC endpoint to send traces to
// Examples: "localhost:4317", "api.honeycomb.io:443", "otel-collector:4317"
// When set, this takes precedence over NewRelic OpenTelemetry configuration
OTLPEndpoint string `envconfig:"OTLP_ENDPOINT" default:""`
// OTLPHeaders are custom headers to send with each OTLP request
// Format: "key1=value1,key2=value2" (comma-separated key=value pairs)
// Example: "x-honeycomb-team=your-api-key" or "api-key=your-key,dataset=your-dataset"
OTLPHeaders string `envconfig:"OTLP_HEADERS" default:""`
// OTLPCompression specifies the compression type for OTLP requests
// Options: "gzip", "none". Defaults to "gzip" if not specified
OTLPCompression string `envconfig:"OTLP_COMPRESSION" default:"gzip"`
// OTLPInsecure disables TLS verification for OTLP connection
// Only use this for local development or testing with self-signed certificates
OTLPInsecure bool `envconfig:"OTLP_INSECURE" default:"false"`
// OTLPSamplingRatio is the ratio of traces to sample (0.0 to 1.0)
// 1.0 means sample all traces, 0.1 means sample 10% of traces
OTLPSamplingRatio float64 `envconfig:"OTLP_SAMPLING_RATIO" default:"0.2"`
// OTLPUseOpenTracingBridge determines whether to set up OpenTracing compatibility bridge
// This allows using existing OpenTracing instrumentation with OpenTelemetry
OTLPUseOpenTracingBridge bool `envconfig:"OTLP_USE_OPENTRACING_BRIDGE" default:"true"`
}
```

Expand Down
24 changes: 24 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,28 @@ type Config struct {
// sizes for sending and receiving messages over GRPC
GRPCMaxSendMsgSize int `envconfig:"GRPC_MAX_SEND_MSG_SIZE" default:"2147483647"` // Unlimited
GRPCMaxRecvMsgSize int `envconfig:"GRPC_MAX_RECV_MSG_SIZE" default:"4194304"` // 4MB

// Custom OpenTelemetry OTLP Configuration
// When OTLPEndpoint is set, it takes precedence over NewRelic OpenTelemetry configuration

// OTLPEndpoint is the OTLP gRPC endpoint to send traces to
// Examples: "localhost:4317", "api.honeycomb.io:443", "otel-collector:4317"
// When set, this takes precedence over NewRelic OpenTelemetry configuration
OTLPEndpoint string `envconfig:"OTLP_ENDPOINT" default:""`
// OTLPHeaders are custom headers to send with each OTLP request
// Format: "key1=value1,key2=value2" (comma-separated key=value pairs)
// Example: "x-honeycomb-team=your-api-key" or "api-key=your-key,dataset=your-dataset"
OTLPHeaders string `envconfig:"OTLP_HEADERS" default:""`
// OTLPCompression specifies the compression type for OTLP requests
// Options: "gzip", "none". Defaults to "gzip" if not specified
OTLPCompression string `envconfig:"OTLP_COMPRESSION" default:"gzip"`
// OTLPInsecure disables TLS verification for OTLP connection
// Only use this for local development or testing with self-signed certificates
OTLPInsecure bool `envconfig:"OTLP_INSECURE" default:"false"`
// OTLPSamplingRatio is the ratio of traces to sample (0.0 to 1.0)
// 1.0 means sample all traces, 0.1 means sample 10% of traces
OTLPSamplingRatio float64 `envconfig:"OTLP_SAMPLING_RATIO" default:"0.2"`
// OTLPUseOpenTracingBridge determines whether to set up OpenTracing compatibility bridge
// This allows using existing OpenTracing instrumentation with OpenTelemetry
OTLPUseOpenTracingBridge bool `envconfig:"OTLP_USE_OPENTRACING_BRIDGE" default:"true"`
}
58 changes: 54 additions & 4 deletions core.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ func (c *cb) SetOpenAPIHandler(handler http.Handler) {
c.openAPIHandler = handler
}

// parseHeaders parses a comma-separated string of key=value pairs into a map
// Example: "key1=value1,key2=value2" -> map[string]string{"key1": "value1", "key2": "value2"}
func parseHeaders(headerString string) map[string]string {
headers := make(map[string]string)
if headerString == "" {
return headers
}

pairs := strings.SplitSeq(headerString, ",")
Comment thread
ankurs marked this conversation as resolved.
for pair := range pairs {
Comment thread
ankurs marked this conversation as resolved.
Comment thread
ankurs marked this conversation as resolved.
kv := strings.SplitN(strings.TrimSpace(pair), "=", 2)
if len(kv) == 2 {
headers[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
} else {
log.Warn(context.Background(), "msg", "Ignoring malformed header pair Expected format 'key=value'", "pair", pair)
}
}
Comment thread
ankurs marked this conversation as resolved.
return headers
}

// processConfig processes the config and sets up the logger, newrelic, sentry, environment, release name, jaeger, hystrix prometheus and signal handler
func (c *cb) processConfig() {
SetupLogger(c.config.LogLevel, c.config.JSONLogs)
Expand Down Expand Up @@ -94,13 +114,35 @@ func (c *cb) processConfig() {
if c.config.EnablePrometheusGRPCHistogram {
grpc_prometheus.EnableHandlingTimeHistogram()
}
if c.config.NewRelicOpentelemetry {
SetupNROpenTelemetry(

// Setup OpenTelemetry - custom OTLP takes precedence over New Relic
if c.config.OTLPEndpoint != "" {
// Use custom OTLP configuration
headers := parseHeaders(c.config.OTLPHeaders)
otlpConfig := OTLPConfig{
Endpoint: c.config.OTLPEndpoint,
Headers: headers,
ServiceName: c.config.AppName,
ServiceVersion: c.config.ReleaseName,
SamplingRatio: c.config.OTLPSamplingRatio,
Compression: c.config.OTLPCompression,
UseOpenTracingBridge: c.config.OTLPUseOpenTracingBridge,
Insecure: c.config.OTLPInsecure,
}
if err := SetupOpenTelemetry(otlpConfig); err != nil {
log.Error(context.Background(), "msg", "Failed to setup custom OTLP", "err", err)
}
} else if c.config.NewRelicOpentelemetry {
// Fall back to New Relic OpenTelemetry if no custom OTLP is configured
err := SetupNROpenTelemetry(
nrName,
c.config.NewRelicLicenseKey,
c.config.ReleaseName,
c.config.NewRelicOpentelemetrySample,
)
if err != nil {
log.Error(context.Background(), "msg", "Failed to setup New Relic OpenTelemetry", "err", err)
}
}
Comment thread
ankurs marked this conversation as resolved.
}

Expand Down Expand Up @@ -395,7 +437,10 @@ func (c *cb) close() {
for _, closer := range c.closers {
if closer != nil {
log.Info(context.Background(), "closing", closer)
closer.Close()
err := closer.Close()
if err != nil {
log.Error(context.Background(), "msg", "Failed to close resource", "err", err, "resource", closer)
}
}
}
}
Expand Down Expand Up @@ -426,7 +471,12 @@ func (c *cb) Stop(dur time.Duration) error {
}
log.Info(context.Background(), "msg", "Server shut down started, bye bye")
if c.httpServer != nil {
go c.httpServer.Shutdown(ctx)
go func(ctx context.Context, c *cb) {
err := c.httpServer.Shutdown(ctx)
if err != nil {
log.Error(context.Background(), "msg", "http server shutdown error", "err", err)
}
}(ctx, c) // shutdown http server gracefully
}
if c.grpcServer != nil {
timedCall(ctx, c.grpcServer.GracefulStop)
Expand Down
Loading
Loading