
In diesem Artikel möchte ich eine Möglichkeit zum Profilieren und Verfolgen von Programmen auf Go vorstellen. Ich zeige Ihnen, wie Sie dies tun, während Sie den Code flexibel und sauber halten.
TL; DR
Protokollierung, Erfassung von Metriken und alles, was nicht mit der Hauptfunktionalität eines Codes verbunden ist, sollte nicht in diesem Code enthalten sein. Stattdessen müssen Sie Ablaufverfolgungspunkte identifizieren , mit denen der Benutzer Code messen kann.
Mit anderen Worten, Protokollierung und Erfassung von Metriken sind Teilmengen der Ablaufverfolgung .
Template-Trace-Code kann mit gtrace generiert werden .
Problem
Angenommen, wir haben ein Paket libund eine Struktur lib.Client. Bevor eine Anforderung ausgeführt wird, wird lib.Clientdie Verbindung überprüft:
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)
}
Was ist, wenn wir unmittelbar vor und unmittelbar nach dem Senden der Ping-Nachricht in das Protokoll aufnehmen möchten? Die erste Option besteht darin, den Logger (oder seine Schnittstelle) einzubetten in 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
}
Wenn wir Metriken sammeln möchten, können wir dasselbe tun:
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