• Stars
    star
    125
  • Rank 286,335 (Top 6 %)
  • Language
    Go
  • License
    MIT License
  • Created over 4 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A code generation tool for instrumenting Go components.

gtrace

CI

Command line tool gtrace generates boilerplate code for Go components tracing (aka instrumentation).

Usage

As a developer of some module (whenever its library or application module) you should define trace points (or hooks) which user of your code can then initialize with some function (aka probes) during runtime.

TL;DR

gtrace suggests you to use structures (tagged with //gtrace:gen) holding all hooks related to your component and generates helper functions around them so that you can merge such structures and call the hooks without any checks for nil. It also can generate context aware helpers to call hooks associated with context.

Example of generated code is here.

Basic

Lets assume that we have some package called lib and some lib.Client structure which holds net.Conn internally and pings it every time before making some request when user calls Client.Do(). For the sake of simplicity lets not cover how dial, ping or any other thing happens.

type Client struct {
	conn net.Conn
}

func (c *Client) Do(ctx context.Context) error {
	if err := c.ping(ctx); err != nil {
		return err
	}
	// Some client logic.
}

func (c *Client) ping(ctx context.Context) error {
	return doPing(ctx, c.conn)
}

What if we need to write some logs right before and after ping happens? There are several ways to do it, but with gtrace we start by defining trace points in our package:

package lib

type ClientTrace struct {
	OnPing func() func(error)
}

type Client struct {
	Trace ClientTrace
	...
}

That is, we export hook functions for every code point that might be interesting for the user of our package. The ClientTrace structure contains definitions of all trace points for the Client. For this example it has only one point. It defines pair of ping start and ping done callbacks. A user of our package can use it like so:

c := lib.Client{
	Trace: ClientTrace{
		OnPing: func() {
			log.Println("ping start")
			return func(err error) {
				log.Printf("ping done; err=%v", err)
			}
		},	
	},
}

How the Client should call that hooks? Well, thats the one of the reason of gtrace exists: it generates few useful (and very annoying to be manually typed) helpers to use this tracing approach. Lets do this:

package lib

//go:generate gtrace

//gtrace:gen
type ClientTrace struct {
	OnPing func() func(error)
}

And after go generate we can instrument our pinging facility as this:

func (c *Client) ping(ctx context.Context) error {
	done := c.Trace.onPing() // added this line.
	err := doPing(ctx, c.conn)
	done(err) // and this line.
	return err
}

grace has generated that lib.Client.onPing() non-exported method which checks if appopriate probe function is non-nil (as well as the returned ping done callback). If any of the callbacks is nil it returns noop functions to avoid branching in the Client.ping() code.

Composing

Lets return to the user of our package and cover another feature that gtrace generates for us: trace points composing. Composing is about merging two structures of the same trace and resulting a third one which calls hooks from both of them. It is useful when user wants to instrument our ping facility with different measure types (to write logs as well as measure call latency):

var t ClientTrace
t = t.Compose(ClientTrace{
	OnPing: func() {
		log.Println("ping start")
		return func(err error) {
			log.Printf("ping done; err=%v", err)
		}
	},	
})
t = t.Compose(ClientTrace{
	OnPing: func() {
		start := time.Now()
		return func(error) {
			sendLatency(time.Since(start))
		}
	},	
})
c := lib.Client{
	Trace: t,
}

Context

Trace points composing gives us additional way to instrument our package: a context based tracing. We can setup ClientTrace not for the whole Client, but for some particular context (and probably do this on some particular condition). To do this we should ask gtrace to generate context aware tracing:

//gtrace:gen
//gtrace:set context
type ClientTrace struct {
	OnPing func() func(error)
}

After go generate command signature of lib.Client.onPing changed to onPing(context.Context), as well as two additional exported functions added: lib.WithClientTrace() and lib.ContextClientTrace(). The former is to associate some ClientTrace with some context; and the latter is to obtain associated ClientTrace from context. So on the Client side we should only pass the context to the onPing() method:

func (c *Client) ping(ctx context.Context) error {
	done := c.Trace.onPing(ctx) // this line has changed.
	err := doPing(ctx, c.conn)
	done(err)
	return err
}

And on the user side we can do this:

c := lib.Client{
	Trace: t, // Note that both traces are used.
}
// Send 100 requests with every 5th being instrumented additionally.
for i := 0; i < 100; i++ {
	ctx := context.Background()
	if i % 5 == 0 {
		ctx = lib.WithClientTrace(ctx, ClientTrace{
			...
		})
	}
	if err := c.Do(ctx); err != nil {
		// handle error.
	}
}

Shortcuts

Thats it for basic tracing. But usually trace points define hooks with number of arguments way bigger than one or two. In that case we can declare a struct holding all hook's arguments instead:

type ClientTrace struct {
	OnPing func(ClientTracePingStart) func(ClientTracePingDone)
}

This makes hooks more readable and extensible. But it also makes calling such hooks a bit more verbose:

func (c *Client) ping(ctx context.Context) error {
	done := c.Trace.onPing(ClientTracePingStart{
		Foo: 1,
		Bar: 2,
		Baz: 3,
	}) 
	err := doPing(ctx, c.conn)
	done(ClientTracePingDone{
		Foo: 1,
		Bar: 2,
		Baz: 3,
		Err: err,
	}) 
	return err
}

gtrace can generate functions called shortcuts to call the hook in more "flat" way:

//gtrace:gen
//gtrace:set shortcut
type ClientTrace struct {
	OnPing func(ClientTracePingStart) func(ClientTracePingDone)
}

After go generate we able to call hooks like this:

func (c *Client) ping(ctx context.Context) error {
	done := clientTraceOnPing(c.Trace, 1, 2, 3)
	err := doPing(ctx, c.conn)
	done(1, 2, 3, err)
	return err
}

Build Tags

gtrace can generate zero-cost tracing helpers when tracing of your app is optional. That is, your client code will remain the same -- composing traces with needed callbacks, calling non-exported versions of hooks (or shortcuts) etc. But after compilation calling the tracing helpers would take no CPU time.

To do that, you can pass the -tag flag to gtrace binary, which will result generation of two _gtrace files -- one which will be used when compiling with -tags gtrace, and one with stubs.

NOTE: gtrace also respects build constraints for GOOS and GOARCH.

Examples

For more details feel free to read the examples package of this repo as well as delve into test/test_grace.go.

More Repositories

1

ws

Tiny WebSocket library for Go.
Go
5,816
star
2

glob

Go glob
Go
893
star
3

ws-examples

Examples of using github.com/gobwas/ws
Go
198
star
4

dm.js

Javascript Dependency Injection Manager
JavaScript
109
star
5

pool

Go Pooling Helpers
Go
109
star
6

graceful

A library for graceful restarts in Go.
Go
90
star
7

httphead

Go
81
star
8

influent.rs

InfluxDB Rust driver
Rust
44
star
9

gulp-sprite-generator

Plugin that generate sprites from your stylesheets.
JavaScript
42
star
10

influent

InfluxDB Javascript driver
JavaScript
38
star
11

flagutil

Easy flags population.
Go
34
star
12

hashring

Consistent hashing hashring implementation.
Go
32
star
13

gws

Go Web Socket
Go
31
star
14

jQuery.buttonCaptcha

Plugin that protects sites from robots using jQuery.
JavaScript
16
star
15

xsync

Priortizable and cancellable synchronization primitives in Go.
Go
14
star
16

cli

Tiny and minimalistic CLI library for Go
Go
14
star
17

schinquirer

schinquirer
JavaScript
8
star
18

jQuery-Loading

Draws animations via DOM elements, with ability to push your own effects&algorithms for animations
JavaScript
8
star
19

vk

A tiny vk.com API library written in Go
Go
6
star
20

ppgo

Experimental templates library for golang
Go
6
star
21

radix

Go
5
star
22

deadline

Simple deadliner in Go.
Go
5
star
23

grunt-marked

Plugin that compiles markdown files into html, using marked parser
JavaScript
5
star
24

.dotfiles

Shell
4
star
25

ps1ws

Golang Talk Slides @ MailRu, April 2017
Go
4
star
26

wrkp

wrk tool report parser
Go
3
star
27

atomicfloat

Atomic float utils
Go
3
star
28

prompt

Ascetic and minimalistic CLI prompt library in Go
Go
3
star
29

json-honey

Yet another super sweet json stringifier-prettyfier-sortifier-sweetifier
JavaScript
3
star
30

jQuery.slideBanjo

Plugin that shows pretty slides at your site.
JavaScript
3
star
31

avl

Go
3
star
32

inherits.js

Backbone's extend inspired standalone inheritance tool.
JavaScript
2
star
33

goproxyd

Go modules local cache server.
Go
2
star
34

articles

2
star
35

xtt

Go
2
star
36

validator.js

ValidatorJS
JavaScript
2
star
37

gracefultalk

Go
2
star
38

tcprpc.rs

Test
Rust
2
star
39

white.vim

Drained of its colours vim color scheme.
Vim Script
1
star
40

mk52

Calculus
Go
1
star
41

kursobot

Go
1
star
42

rk

Rabin-Karp implementation.
Go
1
star
43

array

Golang Sorted Immutable Array
Go
1
star
44

QuerySniffer

QuerySniffer – is a command line tool for easy sniffing database queries.
PHP
1
star
45

tcprpc.go

Go
1
star
46

json-compile

Compilation of references in json.
JavaScript
1
star
47

tcprpc.js

Test TCPRPC
JavaScript
1
star
48

hyper_rust_example

Rust
1
star
49

lexicon.js

JavaScript
1
star
50

safely

Tool, that creates filenames for safe rewriting of target file
JavaScript
1
star
51

rbtree

Go
1
star
52

gulp-safe

Simple gulp plugin for saving backuped existing files.
JavaScript
1
star
53

flagvar

Collection of useful flag.Value implementations.
Go
1
star
54

stream-branch

Node stream branch tool
JavaScript
1
star
55

jquery-megamask

Simple mask plugin for jquery.
JavaScript
1
star
56

Enum.js

Javascript Enum object.
JavaScript
1
star