背景:
在看OpenTelemetry-gin源码时遇到一个函数
func Middleware(service string, opts ...Option) gin.HandlerFunc
可以看到这个函数是以Option为参数的;
点进去发现Option是一个接口,
type Option interface {
apply(*config)
}
在Middleware
内是这样使用该参数的:
func Middleware(service string, opts ...Option) gin.HandlerFunc {
cfg := config{}
for _, opt := range opts {
opt.apply(&cfg)
}
综上不难发现,如果我们要成功调用Middleware
函数,就要实现一个Option类,该类主要用applay方法来进行参数的装配;
调研
调研发现这是go的一种设计模式:选项模式
1.一般的选项模式都是函数选项模式,例子如下:
我们先定义一个OptionFunc的函数类型
type OptionFunc func(*Option)
然后利用闭包为每个字段编写一个设置值的With函数:
func WithA(a string) OptionFunc {
return func(o *Option) {
o.A = a
}
}
func WithB(b string) OptionFunc {
return func(o *Option) {
o.B = b
}
}
func WithC(c int) OptionFunc {
return func(o *Option) {
o.C = c
}
}
然后,我们定义一个默认的Option如下:
var (
defaultOption = &Option{
A: "A",
B: "B",
C: 100,
}
)
最后编写我们新版的构造函数如下:
func newOption2(opts ...OptionFunc) (opt *Option) {
opt = defaultOption
for _, o := range opts {
o(opt)
}
return
}
测试一下:
func main() {
x := newOption("nazha", "小王子", 10)
fmt.Println(x)
x = newOption2()
fmt.Println(x)
x = newOption2(
WithA("沙河娜扎"),
WithC(250),
)
fmt.Println(x)
}
输出:
&{nazha 小王子 10}
&{A B 100}
&{沙河娜扎 B 250}
2.openTelemetry-gin中用到的也是函数选项模式(也可称为结构体选项模式)
接口
type Option interface {
apply(*config)
}
调用
func Middleware(service string, opts ...Option) gin.HandlerFunc {
cfg := config{}
for _, opt := range opts {
opt.apply(&cfg)
}
if cfg.TracerProvider == nil {
cfg.TracerProvider = otel.GetTracerProvider()
}
tracer := cfg.TracerProvider.Tracer(
tracerName,
oteltrace.WithInstrumentationVersion(SemVersion()),
)
if cfg.Propagators == nil {
cfg.Propagators = otel.GetTextMapPropagator()
}
return func(c *gin.Context) {
c.Set(tracerKey, tracer)
savedCtx := c.Request.Context()
defer func() {
c.Request = c.Request.WithContext(savedCtx)
}()
ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header))
opts := []oteltrace.SpanStartOption{
oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", c.Request)...),
oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(c.Request)...),
oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(service, c.FullPath(), c.Request)...),
oteltrace.WithSpanKind(oteltrace.SpanKindServer),
}
spanName := c.FullPath()
if spanName == "" {
spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method)
}
ctx, span := tracer.Start(ctx, spanName, opts...)
defer span.End()
// pass the span through the request context
c.Request = c.Request.WithContext(ctx)
// serve the request to the next middleware
c.Next()
status := c.Writer.Status()
attrs := semconv.HTTPAttributesFromHTTPStatusCode(status)
spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCodeAndSpanKind(status, oteltrace.SpanKindServer)
span.SetAttributes(attrs...)
span.SetStatus(spanStatus, spanMessage)
if len(c.Errors) > 0 {
span.SetAttributes(attribute.String("gin.errors", c.Errors.String()))
}
}
}
可以看到这种方法本质上还是进行方法的调用,不同的是可以将一组属性封装到一个apply中,不用一个属性写一个函数装配;
参考: