{"id":16834625,"url":"https://github.com/gobwas/gtrace","last_synced_at":"2025-03-17T04:32:37.604Z","repository":{"id":42030419,"uuid":"265948198","full_name":"gobwas/gtrace","owner":"gobwas","description":"A code generation tool for instrumenting Go components.","archived":false,"fork":false,"pushed_at":"2022-04-17T05:28:31.000Z","size":117,"stargazers_count":133,"open_issues_count":2,"forks_count":10,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-14T12:07:06.954Z","etag":null,"topics":["go","golang","instrumenting","tracing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gobwas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-05-21T20:45:47.000Z","updated_at":"2024-07-09T03:10:12.000Z","dependencies_parsed_at":"2022-08-12T02:50:47.934Z","dependency_job_id":null,"html_url":"https://github.com/gobwas/gtrace","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gobwas%2Fgtrace","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gobwas%2Fgtrace/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gobwas%2Fgtrace/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gobwas%2Fgtrace/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gobwas","download_url":"https://codeload.github.com/gobwas/gtrace/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221672017,"owners_count":16861348,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","instrumenting","tracing"],"created_at":"2024-10-13T12:07:09.940Z","updated_at":"2024-10-27T11:57:54.662Z","avatar_url":"https://github.com/gobwas.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gtrace\n\n[![CI][ci-badge]][ci-url]\n\nCommand line tool **gtrace** generates boilerplate code for Go components tracing (aka _instrumentation_).\n\n## Usage\n\nAs 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.\n\n### TL;DR\n\n**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.\n\nExample of generated code [is here](examples/pinger/main_gtrace.go).\n\n### Basic\n\nLets 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()`.\nFor the sake of simplicity lets not cover how dial, ping or any other thing happens.\n\n```go\ntype Client struct {\n\tconn net.Conn\n}\n\nfunc (c *Client) Do(ctx context.Context) error {\n\tif err := c.ping(ctx); err != nil {\n\t\treturn err\n\t}\n\t// Some client logic.\n}\n\nfunc (c *Client) ping(ctx context.Context) error {\n\treturn doPing(ctx, c.conn)\n}\n```\n\nWhat 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:\n\n```go\npackage lib\n\ntype ClientTrace struct {\n\tOnPing func() func(error)\n}\n\ntype Client struct {\n\tTrace ClientTrace\n\t...\n}\n```\n\nThat 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:\n\n```go\nc := lib.Client{\n\tTrace: ClientTrace{\n\t\tOnPing: func() {\n\t\t\tlog.Println(\"ping start\")\n\t\t\treturn func(err error) {\n\t\t\t\tlog.Printf(\"ping done; err=%v\", err)\n\t\t\t}\n\t\t},\t\n\t},\n}\n```\n\nHow 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:\n\n```go\npackage lib\n\n//go:generate gtrace\n\n//gtrace:gen\ntype ClientTrace struct {\n\tOnPing func() func(error)\n}\n```\n\nAnd after `go generate` we can instrument our pinging facility as this:\n\n```go\nfunc (c *Client) ping(ctx context.Context) error {\n\tdone := c.Trace.onPing() // added this line.\n\terr := doPing(ctx, c.conn)\n\tdone(err) // and this line.\n\treturn err\n}\n```\n\n*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.\n\n### Composing\n\nLets 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):\n\n```go\nvar t ClientTrace\nt = t.Compose(ClientTrace{\n\tOnPing: func() {\n\t\tlog.Println(\"ping start\")\n\t\treturn func(err error) {\n\t\t\tlog.Printf(\"ping done; err=%v\", err)\n\t\t}\n\t},\t\n})\nt = t.Compose(ClientTrace{\n\tOnPing: func() {\n\t\tstart := time.Now()\n\t\treturn func(error) {\n\t\t\tsendLatency(time.Since(start))\n\t\t}\n\t},\t\n})\nc := lib.Client{\n\tTrace: t,\n}\n```\n\n### Context\n\n_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:\n\n```go\n//gtrace:gen\n//gtrace:set context\ntype ClientTrace struct {\n\tOnPing func() func(error)\n}\n```\n\nAfter `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:\n\n```go\nfunc (c *Client) ping(ctx context.Context) error {\n\tdone := c.Trace.onPing(ctx) // this line has changed.\n\terr := doPing(ctx, c.conn)\n\tdone(err)\n\treturn err\n}\n```\n\nAnd on the user side we can do this:\n\n```go\nc := lib.Client{\n\tTrace: t, // Note that both traces are used.\n}\n// Send 100 requests with every 5th being instrumented additionally.\nfor i := 0; i \u003c 100; i++ {\n\tctx := context.Background()\n\tif i % 5 == 0 {\n\t\tctx = lib.WithClientTrace(ctx, ClientTrace{\n\t\t\t...\n\t\t})\n\t}\n\tif err := c.Do(ctx); err != nil {\n\t\t// handle error.\n\t}\n}\n```\n\n### Shortcuts\n\nThats 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:\n\n```go\ntype ClientTrace struct {\n\tOnPing func(ClientTracePingStart) func(ClientTracePingDone)\n}\n```\n\nThis makes _hooks_ more readable and extensible. But it also makes calling such _hooks_ a bit more verbose:\n\n```go\nfunc (c *Client) ping(ctx context.Context) error {\n\tdone := c.Trace.onPing(ClientTracePingStart{\n\t\tFoo: 1,\n\t\tBar: 2,\n\t\tBaz: 3,\n\t}) \n\terr := doPing(ctx, c.conn)\n\tdone(ClientTracePingDone{\n\t\tFoo: 1,\n\t\tBar: 2,\n\t\tBaz: 3,\n\t\tErr: err,\n\t}) \n\treturn err\n}\n```\n\n**gtrace** can generate functions called **shortcuts** to call the _hook_ in more \"flat\" way:\n\n```go\n//gtrace:gen\n//gtrace:set shortcut\ntype ClientTrace struct {\n\tOnPing func(ClientTracePingStart) func(ClientTracePingDone)\n}\n```\n\nAfter `go generate` we able to call _hooks_ like this:\n\n```go\nfunc (c *Client) ping(ctx context.Context) error {\n\tdone := clientTraceOnPing(c.Trace, 1, 2, 3)\n\terr := doPing(ctx, c.conn)\n\tdone(1, 2, 3, err)\n\treturn err\n}\n```\n\n### Build Tags\n\n**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.\n\nTo 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.\n\n\u003e NOTE: **gtrace** also respects build constraints for GOOS and GOARCH.\n\n### Examples\n\nFor more details feel free to read the `examples` package of this repo as well as delve into `test/test_grace.go`.\n\n[ci-badge]: https://github.com/gobwas/gtrace/workflows/CI/badge.svg\n[ci-url]:   https://github.com/gobwas/gtrace/actions?query=workflow%3ACI\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgobwas%2Fgtrace","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgobwas%2Fgtrace","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgobwas%2Fgtrace/lists"}