Skip to content

Commit 0c2c502

Browse files
authored
story(issue-85): rest increase test coverage (#89)
1 parent 79fade9 commit 0c2c502

22 files changed

+1356
-296
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[![Go Reference](https://pkg.go.dev/badge/github.com/z5labs/humus.svg)](https://pkg.go.dev/github.com/z5labs/humus)
44
[![Go Report Card](https://goreportcard.com/badge/github.com/z5labs/humus)](https://goreportcard.com/report/github.com/z5labs/humus)
5-
![Coverage](https://img.shields.io/badge/Coverage-39.0%25-yellow)
5+
![Coverage](https://img.shields.io/badge/Coverage-71.5%25-brightgreen)
66
[![build](https://github.com/z5labs/humus/actions/workflows/build.yaml/badge.svg)](https://github.com/z5labs/humus/actions/workflows/build.yaml)
77

88
**humus one stop shop framework for all Z5Labs projects in Go.**

buildcontext/buildcontext.go

-44
This file was deleted.

config.go

+240
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
// Copyright (c) 2025 Z5Labs and Contributors
2+
//
3+
// This software is released under the MIT License.
4+
// https://opensource.org/licenses/MIT
5+
6+
package humus
7+
8+
import (
9+
"bytes"
10+
"context"
11+
_ "embed"
12+
"errors"
13+
"fmt"
14+
"log/slog"
15+
"time"
16+
17+
"github.com/z5labs/humus/config"
18+
"github.com/z5labs/humus/internal"
19+
"github.com/z5labs/humus/internal/detector"
20+
21+
bedrockcfg "github.com/z5labs/bedrock/config"
22+
"go.opentelemetry.io/contrib/instrumentation/runtime"
23+
"go.opentelemetry.io/otel"
24+
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
25+
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
26+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
27+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
28+
"go.opentelemetry.io/otel/exporters/stdout/stdoutlog"
29+
"go.opentelemetry.io/otel/log/global"
30+
"go.opentelemetry.io/otel/sdk/log"
31+
"go.opentelemetry.io/otel/sdk/metric"
32+
"go.opentelemetry.io/otel/sdk/resource"
33+
"go.opentelemetry.io/otel/sdk/trace"
34+
"google.golang.org/grpc"
35+
"google.golang.org/grpc/credentials/insecure"
36+
)
37+
38+
//go:embed default_config.yaml
39+
var defaultConfig []byte
40+
41+
// DefaultConfig
42+
func DefaultConfig() bedrockcfg.Source {
43+
return internal.ConfigSource(bytes.NewReader(defaultConfig))
44+
}
45+
46+
// Config
47+
type Config struct {
48+
OTel config.OTel `config:"otel"`
49+
}
50+
51+
// InitializeOTel implements the [appbuilder.OTelInitializer] interface.
52+
func (cfg Config) InitializeOTel(ctx context.Context) error {
53+
conn, err := newClientConn(cfg.OTel.OTLP)
54+
if err != nil {
55+
return err
56+
}
57+
58+
r, err := detectResource(ctx, cfg.OTel.Resource)
59+
if err != nil {
60+
return err
61+
}
62+
63+
initers := []initializer{
64+
traceProviderInitializer{
65+
cfg: cfg.OTel.Trace,
66+
cc: conn,
67+
r: r,
68+
newExporter: otlptracegrpc.New,
69+
},
70+
meterProviderInitializer{
71+
cfg: cfg.OTel.Metric,
72+
cc: conn,
73+
r: r,
74+
newExporter: otlpmetricgrpc.New,
75+
},
76+
logProviderInitializer{
77+
cfg: cfg.OTel.Log,
78+
cc: conn,
79+
r: r,
80+
newExporter: otlploggrpc.New,
81+
},
82+
}
83+
84+
for _, initer := range initers {
85+
err := initer.Init(ctx)
86+
if err != nil {
87+
return err
88+
}
89+
}
90+
return nil
91+
}
92+
93+
func newClientConn(cfg config.OTLP) (*grpc.ClientConn, error) {
94+
if !cfg.Enabled {
95+
return nil, nil
96+
}
97+
return grpc.NewClient(
98+
cfg.Target,
99+
// TODO: support secure transport credentials
100+
grpc.WithTransportCredentials(insecure.NewCredentials()),
101+
)
102+
}
103+
104+
func detectResource(ctx context.Context, cfg config.Resource) (*resource.Resource, error) {
105+
return resource.Detect(
106+
ctx,
107+
detector.TelemetrySDK(),
108+
detector.Host(),
109+
detector.ServiceName(cfg.ServiceName),
110+
detector.ServiceVersion(cfg.ServiceVersion),
111+
)
112+
}
113+
114+
var ErrOTLPMustBeEnabled = errors.New("missing otlp client conn")
115+
116+
type initializer interface {
117+
Init(context.Context) error
118+
}
119+
120+
type traceProviderInitializer struct {
121+
cfg config.Trace
122+
cc *grpc.ClientConn
123+
r *resource.Resource
124+
newExporter func(context.Context, ...otlptracegrpc.Option) (*otlptrace.Exporter, error)
125+
}
126+
127+
func (tpi traceProviderInitializer) Init(ctx context.Context) error {
128+
if !tpi.cfg.Enabled {
129+
return nil
130+
}
131+
if tpi.cc == nil {
132+
return fmt.Errorf("can not enable otel tracing: %w", ErrOTLPMustBeEnabled)
133+
}
134+
135+
exp, err := tpi.newExporter(ctx, otlptracegrpc.WithGRPCConn(tpi.cc))
136+
if err != nil {
137+
return err
138+
}
139+
140+
bsp := trace.NewBatchSpanProcessor(
141+
exp,
142+
trace.WithBatchTimeout(tpi.cfg.BatchTimeout),
143+
)
144+
145+
tp := trace.NewTracerProvider(
146+
trace.WithSpanProcessor(bsp),
147+
trace.WithSampler(trace.TraceIDRatioBased(tpi.cfg.Sampling)),
148+
trace.WithResource(tpi.r),
149+
)
150+
otel.SetTracerProvider(tp)
151+
return nil
152+
}
153+
154+
type meterProviderInitializer struct {
155+
cfg config.Metric
156+
cc *grpc.ClientConn
157+
r *resource.Resource
158+
newExporter func(context.Context, ...otlpmetricgrpc.Option) (*otlpmetricgrpc.Exporter, error)
159+
}
160+
161+
func (mpi meterProviderInitializer) Init(ctx context.Context) error {
162+
if !mpi.cfg.Enabled {
163+
return nil
164+
}
165+
if mpi.cc == nil {
166+
return fmt.Errorf("can not enable otel metering: %w", ErrOTLPMustBeEnabled)
167+
}
168+
169+
exp, err := mpi.newExporter(ctx, otlpmetricgrpc.WithGRPCConn(mpi.cc))
170+
if err != nil {
171+
return err
172+
}
173+
174+
pr := metric.NewPeriodicReader(
175+
exp,
176+
metric.WithInterval(mpi.cfg.ExportInterval),
177+
metric.WithProducer(runtime.NewProducer()),
178+
)
179+
180+
mp := metric.NewMeterProvider(
181+
metric.WithReader(pr),
182+
metric.WithResource(mpi.r),
183+
)
184+
otel.SetMeterProvider(mp)
185+
186+
return runtime.Start(
187+
runtime.WithMinimumReadMemStatsInterval(time.Second),
188+
)
189+
}
190+
191+
type logProviderInitializer struct {
192+
cfg config.Log
193+
cc *grpc.ClientConn
194+
r *resource.Resource
195+
newExporter func(context.Context, ...otlploggrpc.Option) (*otlploggrpc.Exporter, error)
196+
}
197+
198+
func (lpi logProviderInitializer) Init(ctx context.Context) error {
199+
p, err := lpi.initLogProcessor(ctx)
200+
if err != nil {
201+
return err
202+
}
203+
204+
lp := log.NewLoggerProvider(
205+
log.WithProcessor(p),
206+
log.WithResource(lpi.r),
207+
)
208+
global.SetLoggerProvider(lp)
209+
210+
log := Logger("otel")
211+
otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
212+
log.Error("encoutered error from otel sdk", slog.Any("error", err))
213+
}))
214+
return nil
215+
}
216+
217+
func (lpi logProviderInitializer) initLogProcessor(ctx context.Context) (log.Processor, error) {
218+
// TODO: this needs to be made more specific it should either always be OTLP or STDOUT
219+
// the enabled config is a bit confusing to interpret
220+
if !lpi.cfg.Enabled {
221+
exp, err := stdoutlog.New()
222+
if err != nil {
223+
return nil, err
224+
}
225+
226+
sp := log.NewSimpleProcessor(exp)
227+
return sp, nil
228+
}
229+
if lpi.cc == nil {
230+
return nil, fmt.Errorf("can not enable otel logging: %w", ErrOTLPMustBeEnabled)
231+
}
232+
233+
exp, err := lpi.newExporter(ctx, otlploggrpc.WithGRPCConn(lpi.cc))
234+
if err != nil {
235+
return nil, err
236+
}
237+
238+
bsp := log.NewBatchProcessor(exp)
239+
return bsp, nil
240+
}

config/otel.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) 2025 Z5Labs and Contributors
2+
//
3+
// This software is released under the MIT License.
4+
// https://opensource.org/licenses/MIT
5+
6+
package config
7+
8+
import (
9+
_ "embed"
10+
"log/slog"
11+
"time"
12+
)
13+
14+
// Resource
15+
type Resource struct {
16+
ServiceName string `config:"service_name"`
17+
ServiceVersion string `config:"service_version"`
18+
}
19+
20+
// Trace
21+
type Trace struct {
22+
Enabled bool `config:"enabled"`
23+
Sampling float64 `config:"sampling"`
24+
BatchTimeout time.Duration `config:"batch_timeout"`
25+
}
26+
27+
// Metric
28+
type Metric struct {
29+
Enabled bool `config:"enabled"`
30+
ExportInterval time.Duration `config:"export_interval"`
31+
}
32+
33+
// Log
34+
type Log struct {
35+
Enabled bool `config:"enabled"`
36+
BatchTimeout time.Duration `config:"batch_timeout"`
37+
MinLevel slog.Level `config:"min_level"`
38+
}
39+
40+
// OTLP
41+
type OTLP struct {
42+
Enabled bool `config:"enabled"`
43+
Target string `config:"target"`
44+
}
45+
46+
// OTel
47+
type OTel struct {
48+
Resource Resource `config:"resource"`
49+
Trace Trace `config:"trace"`
50+
Metric Metric `config:"metric"`
51+
Log Log `config:"log"`
52+
OTLP OTLP `config:"otlp"`
53+
}

0 commit comments

Comments
 (0)