
在本文中,我想分享一种在Go上分析和跟踪程序的方法。我将向您展示如何做到这一点,同时保持代码的灵活性和简洁性。
TL; DR
日志,度量标准的收集以及与任何代码的主要功能无关的所有内容都不应包含在此代码内。相反,您需要标识可用于用户测量代码的跟踪点。
换句话说,记录和度量标准收集是跟踪的子集。
可以使用gtrace生成模板跟踪代码。
问题
假设我们有一个包装lib和一些结构lib.Client。在执行任何请求之前,它会lib.Client检查连接:
package lib
type Client struct {
    conn net.Conn
}
func (c *Client) Request(ctx context.Context) error {
    if err := c.ping(ctx); err != nil {
        return err
    }
    
}
func (c *Client) ping(ctx context.Context) error {
    return doPing(ctx, c.conn)
}
如果我们想在发送ping消息之前和之后立即在日志中记录该怎么办?第一种选择是将记录器(或其界面)嵌入到Client:
package lib
type Client struct {
    Logger Logger
    conn net.Conn
}
func (c *Client) ping(ctx context.Context) (err error) {
    c.Logger.Info("ping started")
    err = doPing(ctx, c.conn)
    c.Logger.Info("ping done (err is %v)", err)
    return
}
如果我们想收集任何指标,我们可以这样做:
package lib
type Client struct {
    Logger  Logger
    Metrics Registry
    conn net.Conn
}
func (c *Client) ping(ctx context.Context) (err error) {
    start := time.Now()
    c.Logger.Info("ping started")
    err = doPing(ctx, c.conn)
    c.Logger.Info("ping done (err is %v)", err)
    metric := c.Metrics.Get("ping_latency")
    metric.Send(time.Since(start))
    return err
}
, – . Client, , ó , ( doPing()).
( Client) .
, , , ? , , ?
, Client.
, , - . , (SRP).
, Client ? , , ? , Go, , .
, :
,
.
, ( ), ( ).
, , .
, , httptrace Go.
, : OnPingStart() OnPingDone(), OnPing(), callback. OnPing() ping-, callback – . (, doPing()).
Client :
package lib
type Client struct {
    OnPing func() func(error)
    conn net.Conn
}
func (c *Client) ping(ctx context.Context) (err error) {
    done := c.OnPing()
    err = doPing(ctx, c.conn)
    done(err)
    return
}
, OnPing nil. :
func (c *Client) ping(ctx context.Context) (err error) {
    var done func(error)
    if fn := c.OnPing; fn != nil {
        done = fn()
    }
    err = doPing(ctx, c.conn)
    if done != nil {
        done(err)
    }
    return
}
SRP , .
, .
?
httptrace ClientTrace.compose(), n . n ( ).
( reflect). OnPing Client ClientTrace:
package lib
type Client struct {
    Trace ClientTrace
    conn net.Conn
}
type ClientTrace struct {
    OnPing func() func(error)
}
:
func (a ClientTrace) Compose(b ClientTrace) (c ClientTrace) {
    switch {
    case a.OnPing == nil:
        c.OnPing = b.OnPing
    case b.OnPing == nil:
        c.OnPing = a.OnPing
    default:
        c.OnPing = func() func(error) {
            doneA := a.OnPing()
            doneB := b.OnPing() 
            switch {
            case doneA == nil:
                return doneB
            case doneB == nil:
                return doneA
            default:
                return func(err error) {
                    doneA(err)
                    doneB(err)
                }
            }
        }
    }
    return c
}
, ? , .
:
package main
import (
    "log"
    "some/path/to/lib"
)
func main() {
    var trace lib.ClientTrace
    
    trace = trace.Compose(lib.ClientTrace{
        OnPing: func() func(error) {
            log.Println("ping start")
            return func(err error) {
                log.Println("ping done", err)
            }
        },
    })
    
    trace = trace.Compose(lib.ClientTrace{
        OnPing: func() func(error) {
            start := time.Now()
            return func(err error) {
                metric := stats.Get("ping_latency")
                metric.Send(time.Since(start))
            }
        },
    })
    c := lib.Client{
        Trace: trace,
    }
}
. ClientTrace context.Context, Client.Request():
package lib
type clientTraceContextKey struct{}
func ClientTrace(ctx context.Context) ClientTrace {
    t, _ := ctx.Value(clientTraceContextKey{})
    return t
}
func WithClientTrace(ctx context.Context, t ClientTrace) context.Context {
    prev := ContextClientTrace(ctx)
    return context.WithValue(ctx,
        clientTraceContextKey{},
        prev.Compose(t),
    )
}
. , !
?
, Vim ( - ), .
, , nil , reflection.
github.com/gobwas/gtrace
gtrace . , //gtrace:gen. - nil, .
.
:
package lib
type ClientTrace struct {
    OnPing func() func(error)
}
type Client struct {
    Trace ClientTrace
    conn net.Conn
}
func (c *Client) ping(ctx context.Context) (err error) {
    done := c.Trace.onPing(ctx)
    err = doPing(ctx, c.conn)
    done(err)
    return
}
go generate ClientTrace.
! gtrace , .
!
References