Skip to content

Commit b597662

Browse files
Otel integration for Zdb, logger and httpclient (#210)
* Otel integration for Zdb, logger and httpclient * linting issue * add logger resources * fix go mod tidy * attach ctx to logger * tests * minor fix * minor change * Update pkg/logger/methods_test.go Co-authored-by: windsurf-bot[bot] <189301087+windsurf-bot[bot]@users.noreply.github.com> * format * address comments * fix linter * test * fix tests * fix tests --------- Co-authored-by: windsurf-bot[bot] <189301087+windsurf-bot[bot]@users.noreply.github.com>
1 parent 53f8b86 commit b597662

31 files changed

+3958
-24
lines changed

go.mod

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ require (
1919
github.com/spf13/cobra v1.9.1
2020
github.com/spf13/viper v1.20.1
2121
github.com/stretchr/testify v1.10.0
22+
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2
23+
go.opentelemetry.io/contrib/bridges/otelzap v0.6.0
24+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0
25+
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0
26+
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0
27+
go.opentelemetry.io/otel/sdk v1.35.0
28+
go.opentelemetry.io/otel/sdk/log v0.8.0
2229
go.uber.org/zap v1.27.0
2330
golang.org/x/sync v0.14.0
2431
golang.org/x/time v0.11.0
@@ -40,13 +47,16 @@ require (
4047
github.com/google/s2a-go v0.1.9 // indirect
4148
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
4249
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
50+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect
4351
github.com/kylelemons/godebug v1.1.0 // indirect
4452
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
4553
github.com/rogpeppe/go-internal v1.14.1 // indirect
54+
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 // indirect
4655
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
4756
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
48-
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
57+
go.opentelemetry.io/otel/log v0.8.0 // indirect
4958
go.opentelemetry.io/otel/metric v1.35.0 // indirect
59+
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
5060
golang.org/x/oauth2 v0.29.0 // indirect
5161
google.golang.org/api v0.229.0 // indirect
5262
google.golang.org/genproto v0.0.0-20250519155744-55703ea1f237 // indirect
@@ -101,8 +111,8 @@ require (
101111
github.com/tklauser/numcpus v0.10.0 // indirect
102112
github.com/yuin/gopher-lua v1.1.1 // indirect
103113
github.com/yusufpapurcu/wmi v1.2.4 // indirect
104-
go.opentelemetry.io/otel v1.35.0 // indirect
105-
go.opentelemetry.io/otel/trace v1.35.0 // indirect
114+
go.opentelemetry.io/otel v1.35.0
115+
go.opentelemetry.io/otel/trace v1.35.0
106116
go.uber.org/multierr v1.11.0 // indirect
107117
golang.org/x/crypto v0.38.0 // indirect
108118
golang.org/x/net v0.40.0 // indirect

go.sum

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU
9292
github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
9393
github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q=
9494
github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA=
95+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU=
96+
github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0=
9597
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
9698
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
9799
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -200,6 +202,10 @@ github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8O
200202
github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
201203
github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
202204
github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
205+
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2 h1:Jjn3zoRz13f8b1bR6LrXWglx93Sbh4kYfwgmPju3E2k=
206+
github.com/uptrace/opentelemetry-go-extra/otelgorm v0.3.2/go.mod h1:wocb5pNrj/sjhWB9J5jctnC0K2eisSdz/nJJBNFHo+A=
207+
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2 h1:ZjUj9BLYf9PEqBn8W/OapxhPjVRdC6CsXTdULHsyk5c=
208+
github.com/uptrace/opentelemetry-go-extra/otelsql v0.3.2/go.mod h1:O8bHQfyinKwTXKkiKNGmLQS7vRsqRxIQTFZpYpHK3IQ=
203209
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
204210
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
205211
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
@@ -215,20 +221,32 @@ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQ
215221
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
216222
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
217223
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
224+
go.opentelemetry.io/contrib/bridges/otelzap v0.6.0 h1:j8icMXyyqNf6HGuwlYhniPnVsbJIq7n+WirDu3VAJdQ=
225+
go.opentelemetry.io/contrib/bridges/otelzap v0.6.0/go.mod h1:evIOZpl+kAlU5IsaYX2Siw+IbpacAZvXemVsgt70uvw=
218226
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
219227
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
220228
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
221229
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
222230
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
223231
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
232+
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls=
233+
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs=
234+
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8=
235+
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50=
236+
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
237+
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
224238
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
225239
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
226240
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
227241
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
242+
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
243+
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
228244
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
229245
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
230246
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
231247
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
248+
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
249+
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
232250
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
233251
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
234252
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=

pkg/logger/README.md

Lines changed: 172 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Logger Package
22

3-
A structured logging package built on top of [uber-go/zap](https://github.com/uber-go/zap), providing both global and context-aware logging capabilities with sensible defaults.
3+
A structured logging package built on top of [uber-go/zap](https://github.com/uber-go/zap), providing both global and context-aware logging capabilities with sensible defaults and optional OpenTelemetry integration.
44

55
## Features
66

@@ -10,6 +10,7 @@ A structured logging package built on top of [uber-go/zap](https://github.com/ub
1010
- Global and instance-based logging
1111
- Configurable log levels and encoding formats
1212
- Request ID tracking support
13+
- **OpenTelemetry integration** for distributed tracing and observability
1314
- Easy integration with existing applications
1415

1516
## Installation
@@ -45,8 +46,18 @@ log.Info("Service initialized")
4546

4647
```go
4748
type Config struct {
48-
Level string `json:"level"` // Logging level
49-
Encoding string `json:"encoding"` // Output format
49+
Level string `json:"level"` // Logging level
50+
Encoding string `json:"encoding"` // Output format
51+
OpenTelemetry *OpenTelemetryConfig `json:"opentelemetry"` // Optional OpenTelemetry config
52+
}
53+
54+
type OpenTelemetryConfig struct {
55+
Enabled bool `json:"enabled"` // Enable OpenTelemetry integration
56+
ServiceName string `json:"service_name"` // Service name for telemetry
57+
Endpoint string `json:"endpoint"` // OTLP endpoint URL
58+
Protocol string `json:"protocol"` // Protocol: "http" or "grpc"
59+
Insecure bool `json:"insecure"` // Use insecure connection
60+
Headers map[string]string `json:"headers"` // Additional headers
5061
}
5162
```
5263

@@ -77,6 +88,112 @@ Available log levels (in order of increasing severity):
7788
2024-03-20T10:00:00.000Z INFO Server started service=api
7889
```
7990

91+
## OpenTelemetry Integration
92+
93+
The logger package provides seamless integration with OpenTelemetry for distributed tracing and observability platforms like SigNoz, Jaeger, and others.
94+
95+
### Basic OpenTelemetry Setup
96+
97+
```go
98+
import (
99+
"github.com/zondax/golem/pkg/logger"
100+
"github.com/zondax/golem/pkg/logger/otel"
101+
)
102+
103+
// Register the OpenTelemetry provider
104+
provider := otel.NewProvider()
105+
logger.RegisterOpenTelemetryProvider(provider)
106+
107+
// Configure logger with OpenTelemetry
108+
config := logger.Config{
109+
Level: "info",
110+
Encoding: "json",
111+
OpenTelemetry: &logger.OpenTelemetryConfig{
112+
Enabled: true,
113+
ServiceName: "my-service",
114+
Endpoint: "http://localhost:4318", // OTLP HTTP endpoint
115+
Protocol: "http", // or "grpc"
116+
Insecure: true, // for development
117+
Headers: map[string]string{
118+
"Authorization": "Bearer your-token",
119+
},
120+
},
121+
}
122+
123+
// Initialize logger - logs will be sent to both console and OpenTelemetry
124+
logger.InitLogger(config)
125+
126+
// Use logger normally - logs automatically go to OpenTelemetry
127+
logger.Info("Service started")
128+
```
129+
130+
### SigNoz Integration Example
131+
132+
```go
133+
config := logger.Config{
134+
Level: "info",
135+
Encoding: "json",
136+
OpenTelemetry: &logger.OpenTelemetryConfig{
137+
Enabled: true,
138+
ServiceName: "my-api-service",
139+
Endpoint: "http://signoz-otel-collector:4318", // SigNoz OTLP endpoint
140+
Protocol: "http",
141+
Insecure: false,
142+
Headers: map[string]string{
143+
"signoz-access-token": "your-signoz-token",
144+
},
145+
},
146+
}
147+
```
148+
149+
### Jaeger Integration Example
150+
151+
```go
152+
config := logger.Config{
153+
Level: "info",
154+
Encoding: "json",
155+
OpenTelemetry: &logger.OpenTelemetryConfig{
156+
Enabled: true,
157+
ServiceName: "my-service",
158+
Endpoint: "http://jaeger-collector:14268", // Jaeger OTLP endpoint
159+
Protocol: "http",
160+
Insecure: true,
161+
},
162+
}
163+
```
164+
165+
### gRPC Protocol Example
166+
167+
```go
168+
config := logger.Config{
169+
Level: "info",
170+
Encoding: "json",
171+
OpenTelemetry: &logger.OpenTelemetryConfig{
172+
Enabled: true,
173+
ServiceName: "grpc-service",
174+
Endpoint: "localhost:4317", // OTLP gRPC endpoint
175+
Protocol: "grpc",
176+
Insecure: true,
177+
},
178+
}
179+
```
180+
181+
### Graceful Shutdown
182+
183+
```go
184+
import "context"
185+
186+
// Ensure proper cleanup of OpenTelemetry resources
187+
defer func() {
188+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
189+
defer cancel()
190+
191+
if err := logger.ShutdownOpenTelemetryLogger(ctx); err != nil {
192+
log.Printf("Error shutting down OpenTelemetry logger: %v", err)
193+
}
194+
}()
195+
```
196+
80197
## Advanced Usage
81198

82199
### Context-Aware Logging
@@ -148,15 +265,66 @@ logger.Errorf("Failed to connect to %s: %v", host, err)
148265
4. **Resource Cleanup**
149266
```go
150267
defer logger.Sync()
268+
269+
// For OpenTelemetry integration
270+
defer func() {
271+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
272+
defer cancel()
273+
logger.ShutdownOpenTelemetryLogger(ctx)
274+
}()
151275
```
152276

277+
5. **OpenTelemetry Best Practices**
278+
- Always set a meaningful `ServiceName` for better observability
279+
- Use appropriate protocol (`http` vs `grpc`) based on your infrastructure
280+
- Include authentication headers when required by your observability platform
281+
- Handle shutdown gracefully to ensure all logs are flushed
282+
- Test connectivity to your OpenTelemetry endpoint before production deployment
283+
284+
## Configuration Examples
285+
286+
### Development Configuration
287+
288+
```yaml
289+
# config.yaml
290+
logger:
291+
level: "debug"
292+
encoding: "console"
293+
opentelemetry:
294+
enabled: true
295+
service_name: "my-service-dev"
296+
endpoint: "http://localhost:4318"
297+
protocol: "http"
298+
insecure: true
299+
```
300+
301+
### Production Configuration
302+
303+
```yaml
304+
# config.yaml
305+
logger:
306+
level: "info"
307+
encoding: "json"
308+
opentelemetry:
309+
enabled: true
310+
service_name: "my-service-prod"
311+
endpoint: "https://otel-collector.company.com:4318"
312+
protocol: "http"
313+
insecure: false
314+
headers:
315+
authorization: "Bearer your-token-here"
316+
x-api-key: "your-api-key-here"
317+
```
318+
153319
## Performance Considerations
154320
155321
- The logger is designed to be zero-allocation in most cases
156322
- JSON encoding is more CPU-intensive but provides structured data
157323
- Log level checks are performed atomically
158324
- Field allocation is optimized for minimal overhead
325+
- OpenTelemetry integration adds minimal overhead when properly configured
326+
- Batch processing is used for OpenTelemetry exports to optimize performance
159327
160328
## Thread Safety
161329
162-
The logger is completely thread-safe and can be used concurrently from multiple goroutines.
330+
The logger is completely thread-safe and can be used concurrently from multiple goroutines. The OpenTelemetry integration maintains thread safety through proper synchronization mechanisms.

pkg/logger/logger.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ var (
2121
)
2222

2323
type Config struct {
24-
Level string `json:"level"`
25-
Encoding string `json:"encoding"`
24+
Level string `json:"level"`
25+
Encoding string `json:"encoding"`
26+
OpenTelemetry *OpenTelemetryConfig `json:"opentelemetry,omitempty"`
2627
}
2728

2829
type Field struct {
@@ -78,6 +79,33 @@ func NewDevelopmentLogger(fields ...Field) *Logger {
7879
}
7980

8081
func configureAndBuildLogger(config Config) *zap.Logger {
82+
// Create standard logger first
83+
standardLogger := createStandardLoggerInternal(config)
84+
85+
// Early return if OpenTelemetry is not configured or disabled
86+
if config.OpenTelemetry == nil || !config.OpenTelemetry.Enabled {
87+
return standardLogger
88+
}
89+
90+
// Early return if no provider is registered
91+
provider := getOpenTelemetryProvider()
92+
if provider == nil {
93+
return standardLogger
94+
}
95+
96+
// Try to enhance with OpenTelemetry
97+
enhancedLogger, err := provider.CreateLogger(config, standardLogger)
98+
if err != nil {
99+
// If OpenTelemetry fails, fall back to standard logger
100+
standardLogger.Warn("Failed to initialize OpenTelemetry logger, falling back to standard logger", zap.Error(err))
101+
return standardLogger
102+
}
103+
104+
return enhancedLogger
105+
}
106+
107+
// createStandardLoggerInternal contains the actual logic for creating a standard logger
108+
func createStandardLoggerInternal(config Config) *zap.Logger {
81109
cfg := zap.NewProductionConfig()
82110
if strings.EqualFold(config.Encoding, ConsoleEncode) {
83111
cfg = zap.NewDevelopmentConfig()

0 commit comments

Comments
 (0)