
Neste artigo, gostaria de compartilhar uma maneira de criar perfil e rastrear programas no Go. Vou mostrar como fazer isso, mantendo o código flexível e limpo.
TL; DR
O registro, a coleta de métricas e tudo o que não está conectado à funcionalidade principal de qualquer código não deve estar dentro desse código. Em vez disso, você precisa identificar pontos de rastreio que podem ser usados para medir o código pelo usuário.
Em outras palavras, o log e a coleta de métricas são subconjuntos do rastreamento .
O código de rastreamento do modelo pode ser gerado usando o gtrace .
Problema
Suponha que tenhamos um pacote libe alguma estrutura lib.Client. Antes de executar qualquer solicitação, ele lib.Clientverifica a conexão:
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)
}
E se quisermos registrar no log antes e logo após o envio da mensagem de ping? A primeira opção é incorporar o criador de logs (ou sua interface) em 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
}
Se quisermos coletar alguma métrica, podemos fazer o mesmo:
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