
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 lib
e alguma estrutura lib.Client
. Antes de executar qualquer solicitação, ele lib.Client
verifica 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