
Dans cet article, je voudrais partager un moyen de profiler et de tracer des programmes sur Go. Je vais vous montrer comment procéder en gardant le code flexible et propre.
TL; DR
La journalisation, la collecte de métriques et tout ce qui n'est pas lié à la fonctionnalité principale d'un code ne doit pas être à l'intérieur de ce code. Au lieu de cela, vous devez identifier les points de trace qui peuvent être utilisés pour mesurer le code par l'utilisateur.
En d'autres termes, la journalisation et la collecte de métriques sont des sous-ensembles de la trace .
Le code de trace du modèle peut être généré à l'aide de gtrace .
Problème
Supposons que nous ayons un package lib
et une structure lib.Client
. Avant d'exécuter une demande, il lib.Client
vérifie la connexion:
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)
}
Que faire si nous voulons enregistrer dans le journal juste avant et juste après l'envoi du message ping? La première option consiste à intégrer l'enregistreur (ou son interface) dans 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
}
Si nous voulons collecter des mesures, nous pouvons faire de même:
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