├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── cmd └── gtrace │ ├── main.go │ └── writer.go ├── examples ├── buildtags │ ├── main.go │ ├── main_gtrace.go │ └── main_gtrace_stub.go └── pinger │ ├── main.go │ └── main_gtrace.go ├── go.mod └── test ├── internal └── types.go ├── test.go ├── test_darwin.go ├── test_darwin_amd64.go ├── test_darwin_arm64.go ├── test_gtrace_darwin_amd64.go ├── test_gtrace_darwin_arm64.go ├── test_gtrace_linux.go ├── test_gtrace_windows.go ├── test_imports.go ├── test_imports_gtrace.go ├── test_linux.go ├── test_returned.go ├── test_returned_gtrace.go ├── test_returned_tags.go ├── test_returned_tags_gtrace.go ├── test_returned_tags_gtrace_stub.go ├── test_shortcut.go ├── test_shortcut_gtrace.go ├── test_tags.go ├── test_tags_dup.go ├── test_tags_dup_gtrace.go ├── test_tags_dup_gtrace_stub.go ├── test_tags_gtrace.go ├── test_tags_gtrace_stub.go ├── test_test.go ├── test_windows.go └── types.go /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | os: [ ubuntu-latest, macos-latest, windows-latest ] 12 | go: [ '1.14' ] 13 | runs-on: ${{ matrix.os }} 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Go 19 | uses: actions/setup-go@v2 20 | with: 21 | go-version: ${{ matrix.go }} 22 | 23 | - name: Go Env 24 | run: | 25 | go env 26 | 27 | - name: Build 28 | run: | 29 | go install ./cmd/gtrace 30 | 31 | - name: Generate 32 | run: | 33 | gtrace -v -w ./test/test.go 34 | 35 | - name: Test 36 | run: | 37 | go test -v ./test 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /gtrace 2 | /pinger 3 | /buildtags 4 | /test/test_gtrace.go 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Sergey Kamardin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: gtrace 2 | gtrace: 3 | go build -o gtrace ./cmd/gtrace 4 | 5 | .PHONY: examples 6 | examples: gtrace 7 | PATH=$(PWD):$(PATH) go generate ./examples/... 8 | go build -o pinger ./examples/pinger 9 | go build -o buildtags ./examples/buildtags 10 | 11 | .PHONY: test 12 | test: gtrace 13 | find ./test -name '*_gtrace*' -delete 14 | for os in linux darwin windows; do \ 15 | echo "--------"; \ 16 | echo "running: GOOS=$$os go generate ./test"; \ 17 | echo "--------"; \ 18 | PATH=$(PWD):$(PATH) GOOS=$$os go generate ./test; \ 19 | if [[ $$? -ne 0 ]]; then \ 20 | exit 1;\ 21 | fi; \ 22 | done; \ 23 | for os in linux darwin; do \ 24 | for arch in amd64 arm64; do \ 25 | echo "--------"; \ 26 | echo "running: GOOS=$$os GOARCH=$$arch go generate ./test"; \ 27 | echo "--------"; \ 28 | PATH=$(PWD):$(PATH) GOOS=$$os GOARCH=$$arch go generate ./test; \ 29 | if [[ $$? -ne 0 ]]; then \ 30 | exit 1;\ 31 | fi; \ 32 | done; \ 33 | done 34 | go test -v ./test 35 | 36 | clean: 37 | rm -f gtrace 38 | rm -f pinger 39 | find ./test -name '*_gtrace*' -delete 40 | find ./examples -name '*_gtrace*' -delete 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gtrace 2 | 3 | [![CI][ci-badge]][ci-url] 4 | 5 | Command line tool **gtrace** generates boilerplate code for Go components tracing (aka _instrumentation_). 6 | 7 | ## Usage 8 | 9 | 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. 10 | 11 | ### TL;DR 12 | 13 | **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. 14 | 15 | Example of generated code [is here](examples/pinger/main_gtrace.go). 16 | 17 | ### Basic 18 | 19 | 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()`. 20 | For the sake of simplicity lets not cover how dial, ping or any other thing happens. 21 | 22 | ```go 23 | type Client struct { 24 | conn net.Conn 25 | } 26 | 27 | func (c *Client) Do(ctx context.Context) error { 28 | if err := c.ping(ctx); err != nil { 29 | return err 30 | } 31 | // Some client logic. 32 | } 33 | 34 | func (c *Client) ping(ctx context.Context) error { 35 | return doPing(ctx, c.conn) 36 | } 37 | ``` 38 | 39 | 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: 40 | 41 | ```go 42 | package lib 43 | 44 | type ClientTrace struct { 45 | OnPing func() func(error) 46 | } 47 | 48 | type Client struct { 49 | Trace ClientTrace 50 | ... 51 | } 52 | ``` 53 | 54 | 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: 55 | 56 | ```go 57 | c := lib.Client{ 58 | Trace: ClientTrace{ 59 | OnPing: func() { 60 | log.Println("ping start") 61 | return func(err error) { 62 | log.Printf("ping done; err=%v", err) 63 | } 64 | }, 65 | }, 66 | } 67 | ``` 68 | 69 | 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: 70 | 71 | ```go 72 | package lib 73 | 74 | //go:generate gtrace 75 | 76 | //gtrace:gen 77 | type ClientTrace struct { 78 | OnPing func() func(error) 79 | } 80 | ``` 81 | 82 | And after `go generate` we can instrument our pinging facility as this: 83 | 84 | ```go 85 | func (c *Client) ping(ctx context.Context) error { 86 | done := c.Trace.onPing() // added this line. 87 | err := doPing(ctx, c.conn) 88 | done(err) // and this line. 89 | return err 90 | } 91 | ``` 92 | 93 | *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. 94 | 95 | ### Composing 96 | 97 | 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): 98 | 99 | ```go 100 | var t ClientTrace 101 | t = t.Compose(ClientTrace{ 102 | OnPing: func() { 103 | log.Println("ping start") 104 | return func(err error) { 105 | log.Printf("ping done; err=%v", err) 106 | } 107 | }, 108 | }) 109 | t = t.Compose(ClientTrace{ 110 | OnPing: func() { 111 | start := time.Now() 112 | return func(error) { 113 | sendLatency(time.Since(start)) 114 | } 115 | }, 116 | }) 117 | c := lib.Client{ 118 | Trace: t, 119 | } 120 | ``` 121 | 122 | ### Context 123 | 124 | _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: 125 | 126 | ```go 127 | //gtrace:gen 128 | //gtrace:set context 129 | type ClientTrace struct { 130 | OnPing func() func(error) 131 | } 132 | ``` 133 | 134 | 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: 135 | 136 | ```go 137 | func (c *Client) ping(ctx context.Context) error { 138 | done := c.Trace.onPing(ctx) // this line has changed. 139 | err := doPing(ctx, c.conn) 140 | done(err) 141 | return err 142 | } 143 | ``` 144 | 145 | And on the user side we can do this: 146 | 147 | ```go 148 | c := lib.Client{ 149 | Trace: t, // Note that both traces are used. 150 | } 151 | // Send 100 requests with every 5th being instrumented additionally. 152 | for i := 0; i < 100; i++ { 153 | ctx := context.Background() 154 | if i % 5 == 0 { 155 | ctx = lib.WithClientTrace(ctx, ClientTrace{ 156 | ... 157 | }) 158 | } 159 | if err := c.Do(ctx); err != nil { 160 | // handle error. 161 | } 162 | } 163 | ``` 164 | 165 | ### Shortcuts 166 | 167 | 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: 168 | 169 | ```go 170 | type ClientTrace struct { 171 | OnPing func(ClientTracePingStart) func(ClientTracePingDone) 172 | } 173 | ``` 174 | 175 | This makes _hooks_ more readable and extensible. But it also makes calling such _hooks_ a bit more verbose: 176 | 177 | ```go 178 | func (c *Client) ping(ctx context.Context) error { 179 | done := c.Trace.onPing(ClientTracePingStart{ 180 | Foo: 1, 181 | Bar: 2, 182 | Baz: 3, 183 | }) 184 | err := doPing(ctx, c.conn) 185 | done(ClientTracePingDone{ 186 | Foo: 1, 187 | Bar: 2, 188 | Baz: 3, 189 | Err: err, 190 | }) 191 | return err 192 | } 193 | ``` 194 | 195 | **gtrace** can generate functions called **shortcuts** to call the _hook_ in more "flat" way: 196 | 197 | ```go 198 | //gtrace:gen 199 | //gtrace:set shortcut 200 | type ClientTrace struct { 201 | OnPing func(ClientTracePingStart) func(ClientTracePingDone) 202 | } 203 | ``` 204 | 205 | After `go generate` we able to call _hooks_ like this: 206 | 207 | ```go 208 | func (c *Client) ping(ctx context.Context) error { 209 | done := clientTraceOnPing(c.Trace, 1, 2, 3) 210 | err := doPing(ctx, c.conn) 211 | done(1, 2, 3, err) 212 | return err 213 | } 214 | ``` 215 | 216 | ### Build Tags 217 | 218 | **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. 219 | 220 | 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. 221 | 222 | > NOTE: **gtrace** also respects build constraints for GOOS and GOARCH. 223 | 224 | ### Examples 225 | 226 | For more details feel free to read the `examples` package of this repo as well as delve into `test/test_grace.go`. 227 | 228 | [ci-badge]: https://github.com/gobwas/gtrace/workflows/CI/badge.svg 229 | [ci-url]: https://github.com/gobwas/gtrace/actions?query=workflow%3ACI 230 | -------------------------------------------------------------------------------- /cmd/gtrace/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "go/ast" 9 | "go/build" 10 | "go/importer" 11 | "go/parser" 12 | "go/token" 13 | "go/types" 14 | "io" 15 | "log" 16 | "os" 17 | "path/filepath" 18 | "reflect" 19 | "strings" 20 | "text/tabwriter" 21 | 22 | _ "unsafe" // For go:linkname. 23 | ) 24 | 25 | //go:linkname build_goodOSArchFile go/build.(*Context).goodOSArchFile 26 | func build_goodOSArchFile(*build.Context, string, map[string]bool) bool 27 | 28 | func main() { 29 | var ( 30 | verbose bool 31 | suffix string 32 | stubSuffix string 33 | write bool 34 | buildTag string 35 | ) 36 | flag.BoolVar(&verbose, 37 | "v", false, 38 | "output debug info", 39 | ) 40 | flag.BoolVar(&write, 41 | "w", false, 42 | "write trace to file", 43 | ) 44 | flag.StringVar(&suffix, 45 | "file-suffix", "_gtrace", 46 | "suffix for generated go files", 47 | ) 48 | flag.StringVar(&stubSuffix, 49 | "stub-file-suffix", "_stub", 50 | "suffix for generated stub go files", 51 | ) 52 | flag.StringVar(&buildTag, 53 | "tag", "", 54 | "build tag which needs to be passed to enable tracing", 55 | ) 56 | flag.Parse() 57 | 58 | if verbose { 59 | log.SetFlags(log.Lshortfile) 60 | } else { 61 | log.SetFlags(0) 62 | } 63 | 64 | var ( 65 | // Reports whether we were called from go:generate. 66 | isGoGenerate bool 67 | 68 | gofile string 69 | workDir string 70 | err error 71 | ) 72 | if gofile = os.Getenv("GOFILE"); gofile != "" { 73 | // NOTE: GOFILE is always a filename without path. 74 | isGoGenerate = true 75 | workDir, err = os.Getwd() 76 | if err != nil { 77 | log.Fatal(err) 78 | } 79 | } else { 80 | args := flag.Args() 81 | if len(args) == 0 { 82 | log.Fatal("no $GOFILE env nor file parameter were given") 83 | } 84 | gofile = filepath.Base(args[0]) 85 | workDir = filepath.Dir(args[0]) 86 | } 87 | { 88 | prefix := filepath.Join(filepath.Base(workDir), gofile) 89 | log.SetPrefix("[" + prefix + "] ") 90 | } 91 | buildCtx := build.Default 92 | if verbose { 93 | var sb strings.Builder 94 | prettyPrint(&sb, buildCtx) 95 | log.Printf("build context:\n%s", sb.String()) 96 | } 97 | buildPkg, err := buildCtx.ImportDir(workDir, build.IgnoreVendor) 98 | if err != nil { 99 | log.Fatal(err) 100 | } 101 | 102 | srcFilePath := filepath.Join(workDir, gofile) 103 | if verbose { 104 | log.Printf("source file: %s", srcFilePath) 105 | log.Printf("package files: %v", buildPkg.GoFiles) 106 | } 107 | 108 | var writers []*Writer 109 | if isGoGenerate || write { 110 | // We should respect Go suffixes like `_linux.go`. 111 | name, tags, ext := splitOSArchTags(&buildCtx, gofile) 112 | if verbose { 113 | log.Printf( 114 | "split os/args tags of %q: %q %q %q", 115 | gofile, name, tags, ext, 116 | ) 117 | } 118 | openFile := func(name string) (*os.File, func()) { 119 | p := filepath.Join(workDir, name) 120 | if verbose { 121 | log.Printf("destination file path: %+v", p) 122 | } 123 | f, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) 124 | if err != nil { 125 | log.Fatal(err) 126 | } 127 | return f, func() { f.Close() } 128 | } 129 | f, clean := openFile(name + suffix + tags + ext) 130 | defer clean() 131 | writers = append(writers, &Writer{ 132 | Context: buildCtx, 133 | Output: f, 134 | BuildTag: buildTag, 135 | }) 136 | if buildTag != "" { 137 | f, clean := openFile(name + suffix + stubSuffix + tags + ext) 138 | defer clean() 139 | writers = append(writers, &Writer{ 140 | Context: buildCtx, 141 | Output: f, 142 | BuildTag: buildTag, 143 | Stub: true, 144 | }) 145 | } 146 | } else { 147 | writers = append(writers, &Writer{ 148 | Context: buildCtx, 149 | Output: os.Stdout, 150 | BuildTag: buildTag, 151 | Stub: true, 152 | }) 153 | } 154 | 155 | var ( 156 | pkgFiles = make([]*os.File, 0, len(buildPkg.GoFiles)) 157 | astFiles = make([]*ast.File, 0, len(buildPkg.GoFiles)) 158 | 159 | buildConstraints []string 160 | ) 161 | fset := token.NewFileSet() 162 | for _, name := range buildPkg.GoFiles { 163 | base, _, _ := splitOSArchTags(&buildCtx, name) 164 | if isGenerated(base, suffix) { 165 | // Skip gtrace generated files. 166 | if verbose { 167 | log.Printf("skipped package file: %q", name) 168 | } 169 | continue 170 | } 171 | if verbose { 172 | log.Printf("parsing package file: %q", name) 173 | } 174 | file, err := os.Open(filepath.Join(workDir, name)) 175 | if err != nil { 176 | log.Fatal(err) 177 | } 178 | defer file.Close() 179 | 180 | ast, err := parser.ParseFile(fset, file.Name(), file, parser.ParseComments) 181 | if err != nil { 182 | log.Fatalf("parse %q error: %v", file.Name(), err) 183 | } 184 | 185 | pkgFiles = append(pkgFiles, file) 186 | astFiles = append(astFiles, ast) 187 | 188 | if name == gofile { 189 | if _, err := file.Seek(0, io.SeekStart); err != nil { 190 | log.Fatal(err) 191 | } 192 | buildConstraints, err = scanBuildConstraints(file) 193 | if err != nil { 194 | log.Fatal(err) 195 | } 196 | } 197 | } 198 | info := types.Info{ 199 | Types: make(map[ast.Expr]types.TypeAndValue), 200 | Defs: make(map[*ast.Ident]types.Object), 201 | Uses: make(map[*ast.Ident]types.Object), 202 | } 203 | conf := types.Config{ 204 | IgnoreFuncBodies: true, 205 | DisableUnusedImportCheck: true, 206 | Importer: importer.ForCompiler(fset, "source", nil), 207 | } 208 | pkg, err := conf.Check(".", fset, astFiles, &info) 209 | if err != nil { 210 | log.Fatalf("type error: %v", err) 211 | } 212 | var items []*GenItem 213 | for i, astFile := range astFiles { 214 | if pkgFiles[i].Name() != srcFilePath { 215 | continue 216 | } 217 | var ( 218 | depth int 219 | item *GenItem 220 | ) 221 | logf := func(s string, args ...interface{}) { 222 | if !verbose { 223 | return 224 | } 225 | log.Print( 226 | strings.Repeat(" ", depth*4), 227 | fmt.Sprintf(s, args...), 228 | ) 229 | } 230 | ast.Inspect(astFile, func(n ast.Node) (next bool) { 231 | logf("%T", n) 232 | 233 | if n == nil { 234 | item = nil 235 | depth-- 236 | return true 237 | } 238 | defer func() { 239 | if next { 240 | depth++ 241 | } 242 | }() 243 | 244 | switch v := n.(type) { 245 | case 246 | *ast.FuncDecl, 247 | *ast.ValueSpec: 248 | return false 249 | 250 | case *ast.Ident: 251 | logf("ident %q", v.Name) 252 | if item != nil { 253 | item.Ident = v 254 | } 255 | return false 256 | 257 | case *ast.CommentGroup: 258 | for i, c := range v.List { 259 | logf("#%d comment %q", i, c.Text) 260 | 261 | text, ok := TrimConfigComment(c.Text) 262 | if ok { 263 | if item == nil { 264 | item = &GenItem{} 265 | } 266 | if err := item.ParseComment(text); err != nil { 267 | log.Fatalf( 268 | "malformed comment string: %q: %v", 269 | text, err, 270 | ) 271 | } 272 | } 273 | } 274 | return false 275 | 276 | case *ast.StructType: 277 | logf("struct %+v", v) 278 | if item != nil { 279 | item.StructType = v 280 | items = append(items, item) 281 | item = nil 282 | } 283 | return false 284 | } 285 | 286 | return true 287 | }) 288 | } 289 | p := Package{ 290 | Package: pkg, 291 | BuildConstraints: buildConstraints, 292 | } 293 | traces := make(map[string]*Trace) 294 | for _, item := range items { 295 | t := &Trace{ 296 | Name: item.Ident.Name, 297 | Flag: item.Flag, 298 | } 299 | p.Traces = append(p.Traces, t) 300 | traces[item.Ident.Name] = t 301 | } 302 | for i, item := range items { 303 | t := p.Traces[i] 304 | for _, field := range item.StructType.Fields.List { 305 | name := field.Names[0].Name 306 | fn, ok := field.Type.(*ast.FuncType) 307 | if !ok { 308 | continue 309 | } 310 | f, err := buildFunc(info, traces, fn) 311 | if err != nil { 312 | log.Printf( 313 | "skipping hook %s due to error: %v", 314 | name, err, 315 | ) 316 | continue 317 | } 318 | var config GenConfig 319 | if doc := field.Doc; doc != nil { 320 | for _, line := range doc.List { 321 | text, ok := TrimConfigComment(line.Text) 322 | if !ok { 323 | continue 324 | } 325 | err := config.ParseComment(text) 326 | if err != nil { 327 | log.Fatalf( 328 | "malformed comment string: %q: %v", 329 | text, err, 330 | ) 331 | } 332 | } 333 | } 334 | t.Hooks = append(t.Hooks, Hook{ 335 | Name: name, 336 | Func: f, 337 | Flag: item.GenConfig.Flag | config.Flag, 338 | }) 339 | } 340 | } 341 | for _, w := range writers { 342 | if err := w.Write(p); err != nil { 343 | log.Fatal(err) 344 | } 345 | } 346 | 347 | log.Println("OK") 348 | } 349 | 350 | func buildFunc(info types.Info, traces map[string]*Trace, fn *ast.FuncType) (ret *Func, err error) { 351 | ret = new(Func) 352 | for _, p := range fn.Params.List { 353 | t := info.TypeOf(p.Type) 354 | if t == nil { 355 | log.Fatalf("unknown type: %s", p.Type) 356 | } 357 | var names []string 358 | for _, n := range p.Names { 359 | name := n.Name 360 | if name == "_" { 361 | name = "" 362 | } 363 | names = append(names, name) 364 | } 365 | if len(names) == 0 { 366 | // Case where arg is not named. 367 | names = []string{""} 368 | } 369 | for _, name := range names { 370 | ret.Params = append(ret.Params, Param{ 371 | Name: name, 372 | Type: t, 373 | }) 374 | } 375 | } 376 | if fn.Results == nil { 377 | return ret, nil 378 | } 379 | if len(fn.Results.List) > 1 { 380 | return nil, fmt.Errorf( 381 | "unsupported number of function results", 382 | ) 383 | } 384 | 385 | r := fn.Results.List[0] 386 | 387 | switch x := r.Type.(type) { 388 | case *ast.FuncType: 389 | result, err := buildFunc(info, traces, x) 390 | if err != nil { 391 | return nil, err 392 | } 393 | ret.Result = append(ret.Result, result) 394 | return ret, nil 395 | 396 | case *ast.Ident: 397 | if t, ok := traces[x.Name]; ok { 398 | t.Nested = true 399 | ret.Result = append(ret.Result, t) 400 | return ret, nil 401 | } 402 | } 403 | 404 | return nil, fmt.Errorf( 405 | "unsupported function result type %s", 406 | info.TypeOf(r.Type), 407 | ) 408 | } 409 | 410 | func splitOSArchTags(ctx *build.Context, name string) (base, tags, ext string) { 411 | fileTags := make(map[string]bool) 412 | build_goodOSArchFile(ctx, name, fileTags) 413 | ext = filepath.Ext(name) 414 | switch len(fileTags) { 415 | case 0: // * 416 | base = strings.TrimSuffix(name, ext) 417 | 418 | case 1: // *_GOOS or *_GOARCH 419 | i := strings.LastIndexByte(name, '_') 420 | 421 | base = name[:i] 422 | tags = strings.TrimSuffix(name[i:], ext) 423 | 424 | case 2: // *_GOOS_GOARCH 425 | var i int 426 | i = strings.LastIndexByte(name, '_') 427 | i = strings.LastIndexByte(name[:i], '_') 428 | 429 | base = name[:i] 430 | tags = strings.TrimSuffix(name[i:], ext) 431 | 432 | default: 433 | panic(fmt.Sprintf( 434 | "gtrace: internal error: unexpected number of OS/arch tags: %d", 435 | len(fileTags), 436 | )) 437 | } 438 | return 439 | } 440 | 441 | type Package struct { 442 | *types.Package 443 | 444 | BuildConstraints []string 445 | Traces []*Trace 446 | } 447 | 448 | type Trace struct { 449 | Name string 450 | Hooks []Hook 451 | Flag GenFlag 452 | Nested bool 453 | } 454 | 455 | func (*Trace) isFuncResult() bool { return true } 456 | 457 | type Hook struct { 458 | Name string 459 | Func *Func 460 | Flag GenFlag 461 | } 462 | 463 | type Param struct { 464 | Name string // Might be empty. 465 | Type types.Type 466 | } 467 | 468 | type FuncResult interface { 469 | isFuncResult() bool 470 | } 471 | 472 | type Func struct { 473 | Params []Param 474 | Result []FuncResult // 0 or 1. 475 | } 476 | 477 | func (*Func) isFuncResult() bool { return true } 478 | 479 | func (f *Func) HasResult() bool { 480 | return len(f.Result) > 0 481 | } 482 | 483 | type GenFlag uint8 484 | 485 | func (f GenFlag) Has(x GenFlag) bool { 486 | return f&x != 0 487 | } 488 | 489 | const ( 490 | GenZero GenFlag = 1 << iota >> 1 491 | GenShortcut 492 | GenContext 493 | 494 | GenAll = ^GenFlag(0) 495 | ) 496 | 497 | type GenConfig struct { 498 | Flag GenFlag 499 | } 500 | 501 | func TrimConfigComment(text string) (string, bool) { 502 | s := strings.TrimPrefix(text, "//gtrace:") 503 | if text != s { 504 | return s, true 505 | } 506 | return "", false 507 | } 508 | 509 | func (g *GenConfig) ParseComment(text string) (err error) { 510 | prefix, text := split(text, ' ') 511 | switch prefix { 512 | case "gen": 513 | case "set": 514 | return g.ParseParameter(text) 515 | default: 516 | return fmt.Errorf("unknown prefix: %q", prefix) 517 | } 518 | return nil 519 | } 520 | 521 | func (g *GenConfig) ParseParameter(text string) (err error) { 522 | text = strings.TrimSpace(text) 523 | param, _ := split(text, '=') 524 | if param == "" { 525 | return nil 526 | } 527 | switch param { 528 | case "shortcut": 529 | g.Flag |= GenShortcut 530 | case "context": 531 | g.Flag |= GenContext 532 | default: 533 | return fmt.Errorf("unexpected parameter: %q", param) 534 | } 535 | return nil 536 | } 537 | 538 | type GenItem struct { 539 | GenConfig 540 | Ident *ast.Ident 541 | StructType *ast.StructType 542 | } 543 | 544 | func split(s string, c byte) (s1, s2 string) { 545 | i := strings.IndexByte(s, c) 546 | if i == -1 { 547 | return s, "" 548 | } 549 | return s[:i], s[i+1:] 550 | } 551 | 552 | func rsplit(s string, c byte) (s1, s2 string) { 553 | i := strings.LastIndexByte(s, c) 554 | if i == -1 { 555 | return s, "" 556 | } 557 | return s[:i], s[i+1:] 558 | } 559 | 560 | func scanBuildConstraints(r io.Reader) (cs []string, err error) { 561 | br := bufio.NewReader(r) 562 | for { 563 | line, err := br.ReadBytes('\n') 564 | if err != nil { 565 | return nil, err 566 | } 567 | line = bytes.TrimSpace(line) 568 | if comm := bytes.TrimPrefix(line, []byte("//")); !bytes.Equal(comm, line) { 569 | comm = bytes.TrimSpace(comm) 570 | if bytes.HasPrefix(comm, []byte("+build")) { 571 | cs = append(cs, string(line)) 572 | continue 573 | } 574 | } 575 | if bytes.HasPrefix(line, []byte("package ")) { 576 | break 577 | } 578 | } 579 | return cs, nil 580 | } 581 | 582 | func prettyPrint(w io.Writer, x interface{}) { 583 | tw := tabwriter.NewWriter(w, 0, 2, 2, ' ', 0) 584 | t := reflect.TypeOf(x) 585 | v := reflect.ValueOf(x) 586 | for i := 0; i < t.NumField(); i++ { 587 | if v.Field(i).IsZero() { 588 | continue 589 | } 590 | fmt.Fprintf(tw, "%s:\t%v\n", 591 | t.Field(i).Name, 592 | v.Field(i), 593 | ) 594 | } 595 | tw.Flush() 596 | } 597 | 598 | func isGenerated(base, suffix string) bool { 599 | i := strings.Index(base, suffix) 600 | if i == -1 { 601 | return false 602 | } 603 | n := len(base) 604 | m := i + len(suffix) 605 | return m == n || base[m] == '_' 606 | } 607 | -------------------------------------------------------------------------------- /cmd/gtrace/writer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "container/list" 6 | "crypto/md5" 7 | "encoding/hex" 8 | "fmt" 9 | "go/build" 10 | "go/token" 11 | "go/types" 12 | "io" 13 | "io/ioutil" 14 | "path/filepath" 15 | "runtime" 16 | "sort" 17 | "strconv" 18 | "strings" 19 | "sync" 20 | "unicode" 21 | "unicode/utf8" 22 | ) 23 | 24 | type Writer struct { 25 | Output io.Writer 26 | BuildTag string 27 | Stub bool 28 | Context build.Context 29 | 30 | once sync.Once 31 | bw *bufio.Writer 32 | 33 | atEOL bool 34 | depth int 35 | scope *list.List 36 | 37 | pkg *types.Package 38 | std map[string]bool 39 | } 40 | 41 | func (w *Writer) Write(p Package) error { 42 | w.pkg = p.Package 43 | 44 | w.init() 45 | w.line(`// Code generated by gtrace. DO NOT EDIT.`) 46 | 47 | var hasConstraint bool 48 | for i, line := range p.BuildConstraints { 49 | hasConstraint = true 50 | if i == 0 { 51 | w.line() 52 | } 53 | w.line(line) 54 | } 55 | if tag := w.BuildTag; tag != "" { 56 | if !hasConstraint { 57 | w.line() 58 | } 59 | w.code(`// +build `) 60 | if w.Stub { 61 | w.code(`!`) 62 | } 63 | w.line(w.BuildTag) 64 | } 65 | w.line() 66 | w.line(`package `, p.Name()) 67 | w.line() 68 | 69 | var deps []dep 70 | for _, trace := range p.Traces { 71 | deps = w.traceImports(deps, trace) 72 | } 73 | w.importDeps(deps) 74 | 75 | w.newScope(func() { 76 | for _, trace := range p.Traces { 77 | w.compose(trace) 78 | if trace.Nested { 79 | w.isZero(trace) 80 | } 81 | if trace.Flag.Has(GenContext) { 82 | w.context(trace) 83 | } 84 | for _, hook := range trace.Hooks { 85 | if w.Stub { 86 | w.stubHook(trace, hook) 87 | } else { 88 | w.hook(trace, hook) 89 | } 90 | } 91 | } 92 | for _, trace := range p.Traces { 93 | for _, hook := range trace.Hooks { 94 | if !hook.Flag.Has(GenShortcut) { 95 | continue 96 | } 97 | if w.Stub { 98 | w.stubHookShortcut(trace, hook) 99 | } else { 100 | w.hookShortcut(trace, hook) 101 | } 102 | } 103 | } 104 | }) 105 | 106 | return w.bw.Flush() 107 | } 108 | 109 | func (w *Writer) init() { 110 | w.once.Do(func() { 111 | w.bw = bufio.NewWriter(w.Output) 112 | w.scope = list.New() 113 | }) 114 | } 115 | 116 | func (w *Writer) mustDeclare(name string) { 117 | s := w.scope.Back().Value.(*scope) 118 | if !s.set(name) { 119 | where := s.where(name) 120 | panic(fmt.Sprintf( 121 | "gtrace: can't declare identifier: %q: already defined at %q", 122 | name, where, 123 | )) 124 | } 125 | } 126 | 127 | func (w *Writer) declare(name string) string { 128 | if isPredeclared(name) { 129 | name = firstChar(name) 130 | } 131 | s := w.scope.Back().Value.(*scope) 132 | for i := 0; ; i++ { 133 | v := name 134 | if i > 0 { 135 | v += strconv.Itoa(i) 136 | } 137 | if token.IsKeyword(v) { 138 | continue 139 | } 140 | if w.isGlobalScope() && w.pkg.Scope().Lookup(v) != nil { 141 | continue 142 | } 143 | if s.set(v) { 144 | return v 145 | } 146 | } 147 | } 148 | 149 | func isPredeclared(name string) bool { 150 | return types.Universe.Lookup(name) != nil 151 | } 152 | 153 | func (w *Writer) isGlobalScope() bool { 154 | return w.scope.Back().Prev() == nil 155 | } 156 | 157 | func (w *Writer) capture(vars ...string) { 158 | s := w.scope.Back().Value.(*scope) 159 | for _, v := range vars { 160 | if !s.set(v) { 161 | panic(fmt.Sprintf("can't capture variable %q", v)) 162 | } 163 | } 164 | } 165 | 166 | type dep struct { 167 | pkgPath string 168 | pkgName string 169 | typName string 170 | } 171 | 172 | func (w *Writer) typeImports(dst []dep, t types.Type) []dep { 173 | if p, ok := t.(*types.Pointer); ok { 174 | return w.typeImports(dst, p.Elem()) 175 | } 176 | n, ok := t.(*types.Named) 177 | if !ok { 178 | return dst 179 | } 180 | var ( 181 | obj = n.Obj() 182 | pkg = obj.Pkg() 183 | ) 184 | if pkg != nil && pkg.Path() != w.pkg.Path() { 185 | return append(dst, dep{ 186 | pkgPath: pkg.Path(), 187 | pkgName: pkg.Name(), 188 | typName: obj.Name(), 189 | }) 190 | } 191 | return dst 192 | } 193 | 194 | func forEachField(s *types.Struct, fn func(*types.Var)) { 195 | for i := 0; i < s.NumFields(); i++ { 196 | fn(s.Field(i)) 197 | } 198 | } 199 | 200 | func unwrapStruct(t types.Type) (n *types.Named, s *types.Struct) { 201 | var ok bool 202 | n, ok = t.(*types.Named) 203 | if ok { 204 | s, _ = n.Underlying().(*types.Struct) 205 | } 206 | return 207 | } 208 | 209 | func (w *Writer) funcImports(dst []dep, flag GenFlag, fn *Func) []dep { 210 | for _, p := range fn.Params { 211 | dst = w.typeImports(dst, p.Type) 212 | if !flag.Has(GenShortcut) { 213 | continue 214 | } 215 | if _, s := unwrapStruct(p.Type); s != nil { 216 | forEachField(s, func(v *types.Var) { 217 | if v.Exported() { 218 | dst = w.typeImports(dst, v.Type()) 219 | } 220 | }) 221 | } 222 | } 223 | for _, x := range fn.Result { 224 | if fn, ok := x.(*Func); ok { 225 | dst = w.funcImports(dst, flag, fn) 226 | } 227 | } 228 | return dst 229 | } 230 | 231 | func (w *Writer) traceImports(dst []dep, t *Trace) []dep { 232 | if t.Flag.Has(GenContext) { 233 | dst = append(dst, dep{ 234 | pkgPath: "context", 235 | pkgName: "context", 236 | typName: "Context", 237 | }) 238 | } 239 | for _, h := range t.Hooks { 240 | dst = w.funcImports(dst, h.Flag, h.Func) 241 | } 242 | return dst 243 | } 244 | 245 | func (w *Writer) importDeps(deps []dep) { 246 | seen := map[string]bool{} 247 | for i := 0; i < len(deps); { 248 | d := deps[i] 249 | if seen[d.pkgPath] { 250 | n := len(deps) 251 | deps[i], deps[n-1] = deps[n-1], deps[i] 252 | deps = deps[:n-1] 253 | continue 254 | } 255 | seen[d.pkgPath] = true 256 | i++ 257 | } 258 | if len(deps) == 0 { 259 | return 260 | } 261 | sort.Slice(deps, func(i, j int) bool { 262 | var ( 263 | d0 = deps[i] 264 | d1 = deps[j] 265 | std0 = w.isStdLib(d0.pkgPath) 266 | std1 = w.isStdLib(d1.pkgPath) 267 | ) 268 | if std0 != std1 { 269 | return std0 270 | } 271 | return d0.pkgPath < d1.pkgPath 272 | }) 273 | w.line(`import (`) 274 | var ( 275 | lastStd bool 276 | ) 277 | for _, d := range deps { 278 | if w.isStdLib(d.pkgPath) { 279 | lastStd = true 280 | } else if lastStd { 281 | lastStd = false 282 | w.line() 283 | } 284 | w.line("\t", `"`, d.pkgPath, `"`) 285 | } 286 | w.line(`)`) 287 | w.line() 288 | } 289 | 290 | func (w *Writer) isStdLib(pkg string) bool { 291 | w.ensureStdLibMapping() 292 | s := strings.Split(pkg, "/")[0] 293 | return w.std[s] 294 | } 295 | 296 | func (w *Writer) ensureStdLibMapping() { 297 | if w.std != nil { 298 | return 299 | } 300 | w.std = make(map[string]bool) 301 | 302 | src := filepath.Join(w.Context.GOROOT, "src") 303 | files, err := ioutil.ReadDir(src) 304 | if err != nil { 305 | panic(fmt.Sprintf("can't list GOROOT's src: %v", err)) 306 | } 307 | for _, file := range files { 308 | if !file.IsDir() { 309 | continue 310 | } 311 | name := filepath.Base(file.Name()) 312 | switch name { 313 | case 314 | "cmd", 315 | "internal": 316 | // Ignored. 317 | 318 | default: 319 | w.std[name] = true 320 | } 321 | } 322 | } 323 | 324 | func (w *Writer) call(args []string) { 325 | w.code(`(`) 326 | for i, name := range args { 327 | if i > 0 { 328 | w.code(`, `) 329 | } 330 | w.code(name) 331 | } 332 | w.line(`)`) 333 | } 334 | 335 | func (w *Writer) isZero(trace *Trace) { 336 | w.newScope(func() { 337 | t := w.declare("t") 338 | w.line(`// isZero checks whether `, t, ` is empty`) 339 | w.line(`func (`, t, ` `, trace.Name, `) isZero() bool {`) 340 | w.block(func() { 341 | for _, hook := range trace.Hooks { 342 | w.line(`if `, t, `.`, hook.Name, ` != nil {`) 343 | w.block(func() { 344 | w.line(`return false`) 345 | }) 346 | w.line(`}`) 347 | } 348 | w.line(`return true`) 349 | }) 350 | w.line(`}`) 351 | }) 352 | } 353 | 354 | func (w *Writer) compose(trace *Trace) { 355 | w.newScope(func() { 356 | t := w.declare("t") 357 | x := w.declare("x") 358 | ret := w.declare("ret") 359 | w.line(`// Compose returns a new `, trace.Name, ` which has functional fields composed`) 360 | w.line(`// both from `, t, ` and `, x, `.`) 361 | w.code(`func (`, t, ` `, trace.Name, `) Compose(`, x, ` `, trace.Name, `) `) 362 | w.line(`(`, ret, ` `, trace.Name, `) {`) 363 | w.block(func() { 364 | for _, hook := range trace.Hooks { 365 | w.composeHook(hook, t, x, ret+"."+hook.Name) 366 | } 367 | w.line(`return `, ret) 368 | }) 369 | w.line(`}`) 370 | }) 371 | } 372 | 373 | func (w *Writer) composeHook(hook Hook, t1, t2, dst string) { 374 | w.line(`switch {`) 375 | w.line(`case `, t1, `.`, hook.Name, ` == nil:`) 376 | w.line("\t", dst, ` = `, t2, `.`, hook.Name) 377 | w.line(`case `, t2, `.`, hook.Name, ` == nil:`) 378 | w.line("\t", dst, ` = `, t1, `.`, hook.Name) 379 | w.line(`default:`) 380 | w.block(func() { 381 | h1 := w.declare("h1") 382 | h2 := w.declare("h2") 383 | w.line(h1, ` := `, t1, `.`, hook.Name) 384 | w.line(h2, ` := `, t2, `.`, hook.Name) 385 | w.code(dst, ` = `) 386 | w.composeHookCall(hook.Func, h1, h2) 387 | }) 388 | w.line(`}`) 389 | } 390 | 391 | func (w *Writer) composeHookCall(fn *Func, h1, h2 string) { 392 | w.newScope(func() { 393 | w.capture(h1, h2) 394 | w.block(func() { 395 | w.capture(h1, h2) 396 | w.code(`func`) 397 | args := w.funcParams(fn.Params) 398 | w.funcResults(fn) 399 | w.line(`{`) 400 | var ( 401 | r1 string 402 | r2 string 403 | rs []string 404 | ) 405 | if fn.HasResult() { 406 | r1 = w.declare("r1") 407 | r2 = w.declare("r2") 408 | rs = []string{r1, r2} 409 | } 410 | for i, h := range []string{h1, h2} { 411 | if fn.HasResult() { 412 | w.code(rs[i], ` := `) 413 | } 414 | w.code(h) 415 | w.call(args) 416 | } 417 | if fn.HasResult() { 418 | w.line(`switch {`) 419 | 420 | w.code(`case `) 421 | w.isEmptyResult(r1, fn.Result[0]) 422 | w.line(`:`) 423 | w.line("\t", `return `, r2) 424 | 425 | w.code(`case `) 426 | w.isEmptyResult(r2, fn.Result[0]) 427 | w.line(`:`) 428 | w.line("\t", `return `, r1) 429 | 430 | w.line(`default:`) 431 | w.block(func() { 432 | w.code(`return `) 433 | switch x := fn.Result[0].(type) { 434 | case *Func: 435 | w.composeHookCall(x, r1, r2) 436 | case *Trace: 437 | w.line(r1, `.Compose(`, r2, `)`) 438 | default: 439 | panic("unknown result type") 440 | } 441 | }) 442 | w.line(`}`) 443 | } 444 | }) 445 | w.line(`}`) 446 | }) 447 | } 448 | 449 | func (w *Writer) isEmptyResult(name string, r FuncResult) { 450 | switch r.(type) { 451 | case *Func: 452 | w.code(name, ` == nil`) 453 | case *Trace: 454 | w.code(name, `.isZero()`) 455 | default: 456 | panic("unknown result type") 457 | } 458 | } 459 | 460 | var contextType = (func() types.Type { 461 | pkg := types.NewPackage("context", "context") 462 | typ := types.NewInterfaceType(nil, nil) 463 | name := types.NewTypeName(0, pkg, "Context", typ) 464 | return types.NewNamed(name, typ, nil) 465 | })() 466 | 467 | func (w *Writer) stubTrace(id string, t *Trace) (name string) { 468 | name = tempName("gtrace", "noop", t.Name, id) 469 | name = unexported(name) 470 | name = w.declare(name) 471 | w.line(`var `, name, ` `, t.Name) 472 | return name 473 | } 474 | 475 | func (w *Writer) stubFunc(id string, f *Func) (name string) { 476 | name = tempName("gtrace", "noop", id) 477 | name = unexported(name) 478 | name = w.declare(name) 479 | 480 | var res string 481 | for _, r := range f.Result { 482 | switch x := r.(type) { 483 | case *Func: 484 | res = w.stubFunc(id, x) 485 | case *Trace: 486 | res = w.stubTrace(id, x) 487 | default: 488 | panic("unknown result type") 489 | } 490 | } 491 | w.newScope(func() { 492 | w.code(`func `, name) 493 | w.funcParamsUnused(f.Params) 494 | w.funcResults(f) 495 | w.line(`{`) 496 | if f.HasResult() { 497 | w.block(func() { 498 | w.line(`return `, res) 499 | }) 500 | } 501 | w.line(`}`) 502 | }) 503 | 504 | return name 505 | } 506 | 507 | func (w *Writer) stubHook(trace *Trace, hook Hook) { 508 | var stubName string 509 | for _, r := range hook.Func.Result { 510 | switch x := r.(type) { 511 | case *Func: 512 | stubName = w.stubFunc(uniqueTraceHookID(trace, hook), x) 513 | case *Trace: 514 | stubName = w.stubTrace(uniqueTraceID(x), x) 515 | default: 516 | panic("unexpected result type") 517 | } 518 | } 519 | haveNames := haveNames(hook.Func.Params) 520 | w.newScope(func() { 521 | w.code(`func (`, trace.Name, `) `, unexported(hook.Name)) 522 | w.code(`(`) 523 | if trace.Flag.Has(GenContext) { 524 | if haveNames { 525 | ctx := w.declare("ctx") 526 | w.code(ctx, ` `) 527 | } 528 | w.code(`context.Context`) 529 | } 530 | for i, p := range hook.Func.Params { 531 | if i > 0 || trace.Flag.Has(GenContext) { 532 | w.code(`, `) 533 | } 534 | if haveNames { 535 | name := w.declare(nameParam(p)) 536 | w.code(name, ` `) 537 | } 538 | w.code(w.typeString(p.Type)) 539 | } 540 | w.code(`) `) 541 | w.funcResultsFlags(hook.Func, docs) 542 | w.line(`{`) 543 | if hook.Func.HasResult() { 544 | w.block(func() { 545 | w.line(`return `, stubName) 546 | }) 547 | } 548 | w.line(`}`) 549 | }) 550 | } 551 | 552 | func (w *Writer) stubShortcutFunc(id string, f *Func) (name string) { 553 | name = tempName("gtrace", "noop", id) 554 | name = w.declare(name) 555 | 556 | var res string 557 | for _, r := range f.Result { 558 | switch x := r.(type) { 559 | case *Func: 560 | res = w.stubShortcutFunc(id, x) 561 | case *Trace: 562 | res = w.stubTrace(id, x) 563 | default: 564 | panic("unexpected result type") 565 | } 566 | } 567 | w.newScope(func() { 568 | w.code(`func `, name) 569 | w.code(`(`) 570 | params := flattenParams(nil, f.Params) 571 | for i, p := range params { 572 | if i > 0 { 573 | w.code(`, `) 574 | } 575 | w.code(w.typeString(p.Type)) 576 | } 577 | w.code(`) `) 578 | for _, r := range f.Result { 579 | switch x := r.(type) { 580 | case *Func: 581 | w.shortcutFuncSign(x) 582 | case *Trace: 583 | w.line(x.Name, ` `) 584 | default: 585 | panic("unexpected result type") 586 | } 587 | } 588 | w.line(`{`) 589 | if f.HasResult() { 590 | w.block(func() { 591 | w.line(`return `, res) 592 | }) 593 | } 594 | w.line(`}`) 595 | }) 596 | 597 | return name 598 | } 599 | 600 | func (w *Writer) stubHookShortcut(trace *Trace, hook Hook) { 601 | name := tempName(trace.Name, hook.Name) 602 | name = unexported(name) 603 | w.mustDeclare(name) 604 | 605 | id := uniqueTraceHookID(trace, hook) 606 | 607 | var stubName string 608 | for _, r := range hook.Func.Result { 609 | switch x := r.(type) { 610 | case *Func: 611 | stubName = w.stubShortcutFunc(id, x) 612 | case *Trace: 613 | stubName = w.stubTrace(id, x) 614 | default: 615 | panic("unexpected result type") 616 | } 617 | } 618 | 619 | params := flattenParams(nil, hook.Func.Params) 620 | haveNames := haveNames(params) 621 | 622 | w.newScope(func() { 623 | w.code(`func `, name) 624 | w.code(`(`) 625 | if trace.Flag.Has(GenContext) { 626 | if haveNames { 627 | ctx := w.declare("ctx") 628 | w.code(ctx, ` `) 629 | } 630 | w.code(`context.Context, `) 631 | } 632 | 633 | if haveNames { 634 | t := w.declare("t") 635 | w.code(t, ` `) 636 | } 637 | w.code(trace.Name) 638 | 639 | for _, p := range params { 640 | w.code(`, `) 641 | if haveNames { 642 | name := w.declare(nameParam(p)) 643 | w.code(name, ` `) 644 | } 645 | w.code(w.typeString(p.Type)) 646 | } 647 | w.code(`) `) 648 | w.shortcutFuncResultsFlags(hook.Func, docs) 649 | w.line(`{`) 650 | if hook.Func.HasResult() { 651 | w.block(func() { 652 | w.line(`return `, stubName) 653 | }) 654 | } 655 | w.line(`}`) 656 | }) 657 | } 658 | 659 | func (w *Writer) hook(trace *Trace, hook Hook) { 660 | w.newScope(func() { 661 | t := w.declare("t") 662 | x := w.declare("c") // For context's trace. 663 | fn := w.declare("fn") 664 | 665 | w.code(`func (`, t, ` `, trace.Name, `) `, unexported(hook.Name)) 666 | 667 | w.code(`(`) 668 | var ctx string 669 | if trace.Flag.Has(GenContext) { 670 | ctx = w.declare("ctx") 671 | w.code(ctx, ` context.Context`) 672 | } 673 | var args []string 674 | for i, p := range hook.Func.Params { 675 | if i > 0 || ctx != "" { 676 | w.code(`, `) 677 | } 678 | args = append(args, w.funcParam(p)) 679 | } 680 | w.code(`) `) 681 | w.funcResultsFlags(hook.Func, docs) 682 | w.line(`{`) 683 | w.block(func() { 684 | if ctx != "" { 685 | w.line(x, ` := Context`, trace.Name, `(`, ctx, `)`) 686 | w.code(`var fn `) 687 | w.funcSignature(hook.Func) 688 | w.line() 689 | w.composeHook(hook, t, x, fn) 690 | } else { 691 | w.line(fn, ` := `, t, `.`, hook.Name) 692 | } 693 | w.line(`if `, fn, ` == nil {`) 694 | w.block(func() { 695 | w.zeroReturn(hook.Func) 696 | }) 697 | w.line(`}`) 698 | 699 | w.hookFuncCall(hook.Func, fn, args) 700 | }) 701 | w.line(`}`) 702 | }) 703 | } 704 | 705 | func (w *Writer) hookFuncCall(fn *Func, name string, args []string) { 706 | var res string 707 | if fn.HasResult() { 708 | res = w.declare("res") 709 | w.code(res, ` := `) 710 | } 711 | 712 | w.code(name) 713 | w.call(args) 714 | 715 | if !fn.HasResult() { 716 | return 717 | } 718 | 719 | r, isFunc := fn.Result[0].(*Func) 720 | if isFunc { 721 | w.line(`if `, res, ` == nil {`) 722 | w.block(func() { 723 | w.zeroReturn(fn) 724 | }) 725 | w.line(`}`) 726 | 727 | if r.HasResult() { 728 | w.newScope(func() { 729 | w.code(`return func`) 730 | args := w.funcParams(r.Params) 731 | w.funcResults(r) 732 | w.line(`{`) 733 | w.block(func() { 734 | w.hookFuncCall(r, res, args) 735 | }) 736 | w.line(`}`) 737 | }) 738 | return 739 | } 740 | } 741 | 742 | w.line(`return `, res) 743 | } 744 | 745 | func (w *Writer) context(trace *Trace) { 746 | w.line() 747 | w.line(`type `, unexported(trace.Name), `ContextKey struct{}`) 748 | w.line() 749 | 750 | w.newScope(func() { 751 | var ( 752 | ctx = w.declare("ctx") 753 | t = w.declare("t") 754 | ) 755 | w.line(`// With`, trace.Name, ` returns context which has associated `, trace.Name, ` with it.`) 756 | w.code(`func With`, trace.Name, `(`) 757 | w.code(ctx, ` context.Context, `) 758 | w.code(t, ` `, trace.Name, `) `) 759 | w.line(`context.Context {`) 760 | w.block(func() { 761 | w.line(`return context.WithValue(`, ctx, `,`) 762 | w.line("\t", unexported(trace.Name), `ContextKey{},`) 763 | w.line("\t", `Context`, trace.Name, `(`, ctx, `).Compose(`, t, `),`) 764 | w.line(`)`) 765 | }) 766 | w.line(`}`) 767 | w.line() 768 | }) 769 | w.newScope(func() { 770 | var ( 771 | ctx = w.declare("ctx") 772 | t = w.declare("t") 773 | ) 774 | w.line(`// Context`, trace.Name, ` returns `, trace.Name, ` associated with `, ctx, `.`) 775 | w.line(`// If there is no `, trace.Name, ` associated with `, ctx, ` then zero value `) 776 | w.line(`// of `, trace.Name, ` is returned.`) 777 | w.code(`func Context`, trace.Name, `(`, ctx, ` context.Context) `) 778 | w.line(trace.Name, ` {`) 779 | w.block(func() { 780 | w.code(t, `, _ := ctx.Value(`, unexported(trace.Name), `ContextKey{})`) 781 | w.line(`.(`, trace.Name, `)`) 782 | w.line(`return `, t) 783 | }) 784 | w.line(`}`) 785 | w.line() 786 | }) 787 | } 788 | 789 | func nameParam(p Param) (s string) { 790 | s = p.Name 791 | if s == "" { 792 | s = firstChar(ident(typeBasename(p.Type))) 793 | } 794 | return unexported(s) 795 | } 796 | 797 | func (w *Writer) declareParams(src []Param) (names []string) { 798 | names = make([]string, len(src)) 799 | for i, p := range src { 800 | names[i] = w.declare(nameParam(p)) 801 | } 802 | return names 803 | } 804 | 805 | func flattenParams(dst, src []Param) []Param { 806 | for _, p := range src { 807 | _, s := unwrapStruct(p.Type) 808 | if s != nil { 809 | dst = flattenStruct(dst, s) 810 | continue 811 | } 812 | dst = append(dst, p) 813 | } 814 | return dst 815 | } 816 | 817 | func typeBasename(t types.Type) (name string) { 818 | lo, name := rsplit(t.String(), '.') 819 | if name == "" { 820 | name = lo 821 | } 822 | return name 823 | } 824 | 825 | func flattenStruct(dst []Param, s *types.Struct) []Param { 826 | forEachField(s, func(f *types.Var) { 827 | if !f.Exported() { 828 | return 829 | } 830 | var ( 831 | name = f.Name() 832 | typ = f.Type() 833 | ) 834 | if name == typeBasename(typ) { 835 | // NOTE: field name essentially be empty for embeded structs or 836 | // fields called exactly as type. 837 | name = "" 838 | } 839 | dst = append(dst, Param{ 840 | Name: name, 841 | Type: typ, 842 | }) 843 | }) 844 | return dst 845 | } 846 | 847 | func (w *Writer) constructParams(params []Param, names []string) (res []string) { 848 | for _, p := range params { 849 | n, s := unwrapStruct(p.Type) 850 | if s != nil { 851 | var v string 852 | v, names = w.constructStruct(n, s, names) 853 | res = append(res, v) 854 | continue 855 | } 856 | name := names[0] 857 | names = names[1:] 858 | res = append(res, name) 859 | } 860 | return res 861 | } 862 | 863 | func (w *Writer) constructStruct(n *types.Named, s *types.Struct, vars []string) (string, []string) { 864 | p := w.declare("p") 865 | // TODO Ptr 866 | // maybe skip pointers from flattening to not allocate anyhing during trace. 867 | w.line(`var `, p, ` `, w.typeString(n)) 868 | for i := 0; i < s.NumFields(); i++ { 869 | v := s.Field(i) 870 | if !v.Exported() { 871 | continue 872 | } 873 | name := vars[0] 874 | vars = vars[1:] 875 | w.line(p, `.`, v.Name(), ` = `, name) 876 | } 877 | return p, vars 878 | } 879 | 880 | func (w *Writer) hookShortcut(trace *Trace, hook Hook) { 881 | name := tempName(trace.Name, hook.Name) 882 | name = unexported(name) 883 | w.mustDeclare(name) 884 | 885 | w.newScope(func() { 886 | t := w.declare("t") 887 | w.code(`func `, name) 888 | w.code(`(`) 889 | var ctx string 890 | if trace.Flag.Has(GenContext) { 891 | ctx = w.declare("ctx") 892 | w.code(ctx, ` context.Context`) 893 | w.code(`, `) 894 | } 895 | w.code(t, ` `, trace.Name) 896 | 897 | var ( 898 | params = flattenParams(nil, hook.Func.Params) 899 | names = w.declareParams(params) 900 | ) 901 | for i, p := range params { 902 | w.code(`, `) 903 | w.code(names[i], ` `, w.typeString(p.Type)) 904 | } 905 | w.code(`) `) 906 | w.shortcutFuncResultsFlags(hook.Func, docs) 907 | w.line(`{`) 908 | w.block(func() { 909 | for _, name := range names { 910 | w.capture(name) 911 | } 912 | vars := w.constructParams(hook.Func.Params, names) 913 | var res string 914 | if hook.Func.HasResult() { 915 | res = w.declare("res") 916 | w.code(res, ` := `) 917 | } 918 | w.code(t, `.`, unexported(hook.Name)) 919 | if ctx != "" { 920 | vars = append([]string{ctx}, vars...) 921 | } 922 | w.call(vars) 923 | if hook.Func.HasResult() { 924 | w.code(`return `) 925 | r := hook.Func.Result[0] 926 | switch x := r.(type) { 927 | case *Func: 928 | w.hookFuncShortcut(x, res) 929 | case *Trace: 930 | w.line(res) 931 | default: 932 | panic("unexpected result type") 933 | } 934 | } 935 | }) 936 | w.line(`}`) 937 | }) 938 | } 939 | 940 | func (w *Writer) hookFuncShortcut(fn *Func, name string) { 941 | w.newScope(func() { 942 | w.code(`func(`) 943 | var ( 944 | params = flattenParams(nil, fn.Params) 945 | names = w.declareParams(params) 946 | ) 947 | for i, p := range params { 948 | if i > 0 { 949 | w.code(`, `) 950 | } 951 | w.code(names[i], ` `, w.typeString(p.Type)) 952 | } 953 | w.code(`) `) 954 | w.shortcutFuncResults(fn) 955 | w.line(`{`) 956 | w.block(func() { 957 | for _, name := range names { 958 | w.capture(name) 959 | } 960 | params := w.constructParams(fn.Params, names) 961 | var res string 962 | if fn.HasResult() { 963 | res = w.declare("res") 964 | w.code(res, ` := `) 965 | } 966 | w.code(name) 967 | w.call(params) 968 | if fn.HasResult() { 969 | r := fn.Result[0] 970 | w.code(`return `) 971 | switch x := r.(type) { 972 | case *Func: 973 | w.hookFuncShortcut(x, res) 974 | case *Trace: 975 | w.line(res) 976 | default: 977 | panic("unexpected result type") 978 | } 979 | } 980 | }) 981 | w.line(`}`) 982 | }) 983 | } 984 | 985 | func (w *Writer) zeroReturn(fn *Func) { 986 | if !fn.HasResult() { 987 | w.line(`return`) 988 | return 989 | } 990 | w.code(`return `) 991 | switch x := fn.Result[0].(type) { 992 | case *Func: 993 | w.funcSignature(x) 994 | w.line(`{`) 995 | w.block(func() { 996 | w.zeroReturn(x) 997 | }) 998 | w.line(`}`) 999 | case *Trace: 1000 | w.line(x.Name, `{}`) 1001 | default: 1002 | panic("unexpected result type") 1003 | } 1004 | } 1005 | 1006 | func (w *Writer) funcParams(params []Param) (vars []string) { 1007 | w.code(`(`) 1008 | for i, p := range params { 1009 | if i > 0 { 1010 | w.code(`, `) 1011 | } 1012 | vars = append(vars, w.funcParam(p)) 1013 | } 1014 | w.code(`) `) 1015 | return 1016 | } 1017 | 1018 | func (w *Writer) funcParamsUnused(params []Param) { 1019 | w.code(`(`) 1020 | for i, p := range params { 1021 | if i > 0 { 1022 | w.code(`, `) 1023 | } 1024 | w.code(w.typeString(p.Type)) 1025 | } 1026 | w.code(`) `) 1027 | } 1028 | 1029 | func (w *Writer) funcParam(p Param) (name string) { 1030 | name = w.declare(nameParam(p)) 1031 | w.code(name, ` `) 1032 | w.code(w.typeString(p.Type)) 1033 | return name 1034 | } 1035 | 1036 | func (w *Writer) funcParamSign(p Param) { 1037 | name := nameParam(p) 1038 | if len(name) == 1 || isPredeclared(name) { 1039 | name = "_" 1040 | } 1041 | w.code(name, ` `) 1042 | w.code(w.typeString(p.Type)) 1043 | } 1044 | 1045 | type flags uint8 1046 | 1047 | func (f flags) has(x flags) bool { 1048 | return f&x != 0 1049 | } 1050 | 1051 | const ( 1052 | zeroFlags flags = 1 << iota >> 1 1053 | docs 1054 | ) 1055 | 1056 | func (w *Writer) funcResultsFlags(fn *Func, flags flags) { 1057 | for _, r := range fn.Result { 1058 | switch x := r.(type) { 1059 | case *Func: 1060 | w.funcSignatureFlags(x, flags) 1061 | case *Trace: 1062 | w.code(x.Name, ` `) 1063 | default: 1064 | panic("unexpected result type") 1065 | } 1066 | } 1067 | } 1068 | 1069 | func (w *Writer) funcResults(fn *Func) { 1070 | w.funcResultsFlags(fn, 0) 1071 | } 1072 | 1073 | func (w *Writer) funcSignatureFlags(fn *Func, flags flags) { 1074 | haveNames := haveNames(fn.Params) 1075 | w.code(`func(`) 1076 | for i, p := range fn.Params { 1077 | if i > 0 { 1078 | w.code(`, `) 1079 | } 1080 | if flags.has(docs) && haveNames { 1081 | w.funcParamSign(p) 1082 | } else { 1083 | w.code(w.typeString(p.Type)) 1084 | } 1085 | } 1086 | w.code(`) `) 1087 | w.funcResultsFlags(fn, flags) 1088 | } 1089 | 1090 | func (w *Writer) funcSignature(fn *Func) { 1091 | w.funcSignatureFlags(fn, 0) 1092 | } 1093 | 1094 | func (w *Writer) shortcutFuncSignFlags(fn *Func, flags flags) { 1095 | var ( 1096 | params = flattenParams(nil, fn.Params) 1097 | haveNames = haveNames(params) 1098 | ) 1099 | w.code(`func(`) 1100 | for i, p := range params { 1101 | if i > 0 { 1102 | w.code(`, `) 1103 | } 1104 | if flags.has(docs) && haveNames { 1105 | w.funcParamSign(p) 1106 | } else { 1107 | w.code(w.typeString(p.Type)) 1108 | } 1109 | } 1110 | w.code(`) `) 1111 | w.shortcutFuncResultsFlags(fn, flags) 1112 | } 1113 | 1114 | func (w *Writer) shortcutFuncSign(fn *Func) { 1115 | w.shortcutFuncSignFlags(fn, 0) 1116 | } 1117 | 1118 | func (w *Writer) shortcutFuncResultsFlags(fn *Func, flags flags) { 1119 | for _, r := range fn.Result { 1120 | switch x := r.(type) { 1121 | case *Func: 1122 | w.shortcutFuncSignFlags(x, flags) 1123 | case *Trace: 1124 | w.code(x.Name, ` `) 1125 | default: 1126 | panic("unexpected result type") 1127 | } 1128 | } 1129 | } 1130 | 1131 | func (w *Writer) shortcutFuncResults(fn *Func) { 1132 | w.shortcutFuncResultsFlags(fn, 0) 1133 | } 1134 | 1135 | func haveNames(params []Param) bool { 1136 | for _, p := range params { 1137 | name := nameParam(p) 1138 | if len(name) > 1 && !isPredeclared(name) { 1139 | return true 1140 | } 1141 | } 1142 | return false 1143 | } 1144 | 1145 | func (w *Writer) typeString(t types.Type) string { 1146 | return types.TypeString(t, func(pkg *types.Package) string { 1147 | if pkg.Path() == w.pkg.Path() { 1148 | return "" // same package; unqualified 1149 | } 1150 | return pkg.Name() 1151 | }) 1152 | } 1153 | 1154 | func (w *Writer) block(fn func()) { 1155 | w.depth++ 1156 | w.newScope(fn) 1157 | w.depth-- 1158 | } 1159 | 1160 | func (w *Writer) newScope(fn func()) { 1161 | w.scope.PushBack(new(scope)) 1162 | fn() 1163 | w.scope.Remove(w.scope.Back()) 1164 | } 1165 | 1166 | func (w *Writer) line(args ...string) { 1167 | w.code(args...) 1168 | w.bw.WriteByte('\n') 1169 | w.atEOL = true 1170 | } 1171 | 1172 | func (w *Writer) code(args ...string) { 1173 | if w.atEOL { 1174 | for i := 0; i < w.depth; i++ { 1175 | w.bw.WriteByte('\t') 1176 | } 1177 | w.atEOL = false 1178 | } 1179 | for _, arg := range args { 1180 | w.bw.WriteString(arg) 1181 | } 1182 | } 1183 | 1184 | func exported(s string) string { 1185 | r, size := utf8.DecodeRuneInString(s) 1186 | if r == utf8.RuneError { 1187 | panic("invalid string") 1188 | } 1189 | return string(unicode.ToUpper(r)) + s[size:] 1190 | } 1191 | 1192 | func unexported(s string) string { 1193 | r, size := utf8.DecodeRuneInString(s) 1194 | if r == utf8.RuneError { 1195 | panic("invalid string") 1196 | } 1197 | return string(unicode.ToLower(r)) + s[size:] 1198 | } 1199 | 1200 | func firstChar(s string) string { 1201 | r, _ := utf8.DecodeRuneInString(s) 1202 | if r == utf8.RuneError { 1203 | panic("invalid string") 1204 | } 1205 | return string(r) 1206 | } 1207 | 1208 | func ident(s string) string { 1209 | // Identifier must not begin with number. 1210 | for len(s) > 0 { 1211 | r, size := utf8.DecodeRuneInString(s) 1212 | if r == utf8.RuneError { 1213 | panic("invalid string") 1214 | } 1215 | if !unicode.IsNumber(r) { 1216 | break 1217 | } 1218 | s = s[size:] 1219 | } 1220 | 1221 | // Filter out non letter/number/underscore characters. 1222 | s = strings.Map(func(r rune) rune { 1223 | switch { 1224 | case r == '_' || 1225 | unicode.IsLetter(r) || 1226 | unicode.IsNumber(r): 1227 | 1228 | return r 1229 | default: 1230 | return -1 1231 | } 1232 | }, s) 1233 | 1234 | if !token.IsIdentifier(s) { 1235 | s = "_" + s 1236 | } 1237 | 1238 | return s 1239 | } 1240 | 1241 | func tempName(names ...string) string { 1242 | var sb strings.Builder 1243 | for i, name := range names { 1244 | if i == 0 { 1245 | name = unexported(name) 1246 | } else { 1247 | name = exported(name) 1248 | } 1249 | sb.WriteString(name) 1250 | } 1251 | return sb.String() 1252 | } 1253 | 1254 | type decl struct { 1255 | where string 1256 | } 1257 | 1258 | type scope struct { 1259 | vars map[string]decl 1260 | } 1261 | 1262 | func (s *scope) set(v string) bool { 1263 | if s.vars == nil { 1264 | s.vars = make(map[string]decl) 1265 | } 1266 | if _, has := s.vars[v]; has { 1267 | return false 1268 | } 1269 | _, file, line, _ := runtime.Caller(2) 1270 | s.vars[v] = decl{ 1271 | where: fmt.Sprintf("%s:%d", file, line), 1272 | } 1273 | return true 1274 | } 1275 | 1276 | func (s *scope) where(v string) string { 1277 | d := s.vars[v] 1278 | return d.where 1279 | } 1280 | 1281 | func uniqueTraceID(t *Trace) string { 1282 | hash := md5.New() 1283 | io.WriteString(hash, t.Name) 1284 | p := hash.Sum(nil) 1285 | s := hex.EncodeToString(p) 1286 | return s[:8] 1287 | } 1288 | 1289 | func uniqueTraceHookID(t *Trace, h Hook) string { 1290 | hash := md5.New() 1291 | io.WriteString(hash, t.Name) 1292 | io.WriteString(hash, h.Name) 1293 | p := hash.Sum(nil) 1294 | s := hex.EncodeToString(p) 1295 | return s[:8] 1296 | } 1297 | -------------------------------------------------------------------------------- /examples/buildtags/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | ) 7 | 8 | //go:generate gtrace -tag gtrace 9 | 10 | //gtrace:gen 11 | //gtrace:set shortcut 12 | type Trace struct { 13 | OnInput func(string) func() 14 | } 15 | 16 | func main() { 17 | log.SetFlags(0) 18 | log.SetPrefix("[logs] ") 19 | 20 | t := logTrace(Trace{}) 21 | done := traceOnInput(t, os.Args[1]) 22 | defer done() 23 | } 24 | 25 | func logTrace(t Trace) Trace { 26 | return t.Compose(Trace{ 27 | OnInput: func(s string) func() { 28 | log.Printf("processing %q", s) 29 | return func() { 30 | log.Printf("processed %q", s) 31 | } 32 | }, 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /examples/buildtags/main_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build gtrace 4 | 5 | package main 6 | 7 | // Compose returns a new Trace which has functional fields composed 8 | // both from t and x. 9 | func (t Trace) Compose(x Trace) (ret Trace) { 10 | switch { 11 | case t.OnInput == nil: 12 | ret.OnInput = x.OnInput 13 | case x.OnInput == nil: 14 | ret.OnInput = t.OnInput 15 | default: 16 | h1 := t.OnInput 17 | h2 := x.OnInput 18 | ret.OnInput = func(s string) func() { 19 | r1 := h1(s) 20 | r2 := h2(s) 21 | switch { 22 | case r1 == nil: 23 | return r2 24 | case r2 == nil: 25 | return r1 26 | default: 27 | return func() { 28 | r1() 29 | r2() 30 | } 31 | } 32 | } 33 | } 34 | return ret 35 | } 36 | func (t Trace) onInput(s string) func() { 37 | fn := t.OnInput 38 | if fn == nil { 39 | return func() { 40 | return 41 | } 42 | } 43 | res := fn(s) 44 | if res == nil { 45 | return func() { 46 | return 47 | } 48 | } 49 | return res 50 | } 51 | func traceOnInput(t Trace, s string) func() { 52 | res := t.onInput(s) 53 | return func() { 54 | res() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /examples/buildtags/main_gtrace_stub.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build !gtrace 4 | 5 | package main 6 | 7 | // Compose returns a new Trace which has functional fields composed 8 | // both from t and x. 9 | func (t Trace) Compose(x Trace) (ret Trace) { 10 | switch { 11 | case t.OnInput == nil: 12 | ret.OnInput = x.OnInput 13 | case x.OnInput == nil: 14 | ret.OnInput = t.OnInput 15 | default: 16 | h1 := t.OnInput 17 | h2 := x.OnInput 18 | ret.OnInput = func(s string) func() { 19 | r1 := h1(s) 20 | r2 := h2(s) 21 | switch { 22 | case r1 == nil: 23 | return r2 24 | case r2 == nil: 25 | return r1 26 | default: 27 | return func() { 28 | r1() 29 | r2() 30 | } 31 | } 32 | } 33 | } 34 | return ret 35 | } 36 | func gtraceNoopE065cdda() { 37 | } 38 | func (Trace) onInput(string) func() { 39 | return gtraceNoopE065cdda 40 | } 41 | func gtraceNoopE065cdda1() { 42 | } 43 | func traceOnInput(Trace, string) func() { 44 | return gtraceNoopE065cdda1 45 | } 46 | -------------------------------------------------------------------------------- /examples/pinger/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate gtrace 4 | 5 | import ( 6 | "bufio" 7 | "context" 8 | "flag" 9 | "fmt" 10 | "log" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "strings" 15 | "sync" 16 | "time" 17 | ) 18 | 19 | type ( 20 | //gtrace:gen 21 | //gtrace:set shortcut 22 | //gtrace:set context 23 | PingTrace struct { 24 | OnRequest func(PingTraceRequestStart) func(PingTraceRequestDone) 25 | } 26 | PingTraceRequestStart struct { 27 | Request *http.Request 28 | } 29 | PingTraceRequestDone struct { 30 | Response *http.Response 31 | Error error 32 | } 33 | ) 34 | 35 | type Pinger struct { 36 | Trace PingTrace 37 | 38 | wg sync.WaitGroup 39 | } 40 | 41 | func (x *Pinger) Ping(ctx context.Context, s string) error { 42 | u, err := url.ParseRequestURI(s) 43 | if err != nil { 44 | return err 45 | } 46 | req, err := http.NewRequest("HEAD", u.String(), nil) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | x.wg.Add(1) 52 | go func() { 53 | defer x.wg.Done() 54 | 55 | done := pingTraceOnRequest(ctx, x.Trace, req) 56 | resp, err := http.DefaultClient.Do(req) 57 | done(resp, err) 58 | }() 59 | 60 | return nil 61 | } 62 | 63 | func (x *Pinger) Close() { 64 | x.wg.Wait() 65 | } 66 | 67 | func main() { 68 | log.SetFlags(0) 69 | log.SetPrefix("[logs] ") 70 | 71 | var x Pinger 72 | x.Trace = logTrace(x.Trace) 73 | defer x.Close() 74 | 75 | s := bufio.NewScanner(os.Stdin) 76 | 77 | var ( 78 | prev string 79 | sampling int 80 | ) 81 | flag.StringVar(&prev, 82 | "u", "http://ya.ru", 83 | "default url to ping", 84 | ) 85 | flag.IntVar(&sampling, 86 | "sampling", 5, 87 | "sampling of stats probes", 88 | ) 89 | flag.Parse() 90 | 91 | fmt.Println("Welcome to pinger!") 92 | fmt.Println("Default url is set to:", prev) 93 | fmt.Println("Sampling for latency calculation is set to:", sampling) 94 | fmt.Println("Type new url or press enter...") 95 | 96 | for i := 0; s.Scan(); i++ { 97 | ctx := context.Background() 98 | if i%sampling == 0 { 99 | ctx = WithPingTrace(ctx, statsTrace(PingTrace{})) 100 | } 101 | text := strings.TrimSpace(s.Text()) 102 | if text == "" { 103 | text = prev 104 | } else { 105 | prev = text 106 | } 107 | err := x.Ping(ctx, text) 108 | if err != nil { 109 | log.Println(err) 110 | } 111 | } 112 | } 113 | 114 | func logTrace(s PingTrace) PingTrace { 115 | return s.Compose(PingTrace{ 116 | OnRequest: func(req PingTraceRequestStart) func(PingTraceRequestDone) { 117 | log.Printf("%s %s...", req.Request.Method, req.Request.URL) 118 | return func(res PingTraceRequestDone) { 119 | var ( 120 | status string 121 | size int64 122 | ) 123 | if resp := res.Response; resp != nil { 124 | status = resp.Status 125 | size = resp.ContentLength 126 | } 127 | log.Printf( 128 | "%s %s... done; status=%q size=%d err=%v", 129 | req.Request.Method, req.Request.URL, 130 | status, size, res.Error, 131 | ) 132 | } 133 | }, 134 | }) 135 | } 136 | 137 | func statsTrace(s PingTrace) PingTrace { 138 | return s.Compose(PingTrace{ 139 | OnRequest: func(req PingTraceRequestStart) func(PingTraceRequestDone) { 140 | start := time.Now() 141 | return func(res PingTraceRequestDone) { 142 | log.Printf( 143 | "%s %s latency: %.2fms", 144 | req.Request.Method, req.Request.URL, 145 | time.Since(start).Seconds()*1000, 146 | ) 147 | } 148 | }, 149 | }) 150 | } 151 | -------------------------------------------------------------------------------- /examples/pinger/main_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | package main 4 | 5 | import ( 6 | "context" 7 | "net/http" 8 | ) 9 | 10 | // Compose returns a new PingTrace which has functional fields composed 11 | // both from t and x. 12 | func (t PingTrace) Compose(x PingTrace) (ret PingTrace) { 13 | switch { 14 | case t.OnRequest == nil: 15 | ret.OnRequest = x.OnRequest 16 | case x.OnRequest == nil: 17 | ret.OnRequest = t.OnRequest 18 | default: 19 | h1 := t.OnRequest 20 | h2 := x.OnRequest 21 | ret.OnRequest = func(p PingTraceRequestStart) func(PingTraceRequestDone) { 22 | r1 := h1(p) 23 | r2 := h2(p) 24 | switch { 25 | case r1 == nil: 26 | return r2 27 | case r2 == nil: 28 | return r1 29 | default: 30 | return func(p PingTraceRequestDone) { 31 | r1(p) 32 | r2(p) 33 | } 34 | } 35 | } 36 | } 37 | return ret 38 | } 39 | 40 | type pingTraceContextKey struct{} 41 | 42 | // WithPingTrace returns context which has associated PingTrace with it. 43 | func WithPingTrace(ctx context.Context, t PingTrace) context.Context { 44 | return context.WithValue(ctx, 45 | pingTraceContextKey{}, 46 | ContextPingTrace(ctx).Compose(t), 47 | ) 48 | } 49 | 50 | // ContextPingTrace returns PingTrace associated with ctx. 51 | // If there is no PingTrace associated with ctx then zero value 52 | // of PingTrace is returned. 53 | func ContextPingTrace(ctx context.Context) PingTrace { 54 | t, _ := ctx.Value(pingTraceContextKey{}).(PingTrace) 55 | return t 56 | } 57 | 58 | func (t PingTrace) onRequest(ctx context.Context, p PingTraceRequestStart) func(PingTraceRequestDone) { 59 | c := ContextPingTrace(ctx) 60 | var fn func(PingTraceRequestStart) func(PingTraceRequestDone) 61 | switch { 62 | case t.OnRequest == nil: 63 | fn = c.OnRequest 64 | case c.OnRequest == nil: 65 | fn = t.OnRequest 66 | default: 67 | h1 := t.OnRequest 68 | h2 := c.OnRequest 69 | fn = func(p PingTraceRequestStart) func(PingTraceRequestDone) { 70 | r1 := h1(p) 71 | r2 := h2(p) 72 | switch { 73 | case r1 == nil: 74 | return r2 75 | case r2 == nil: 76 | return r1 77 | default: 78 | return func(p PingTraceRequestDone) { 79 | r1(p) 80 | r2(p) 81 | } 82 | } 83 | } 84 | } 85 | if fn == nil { 86 | return func(PingTraceRequestDone) { 87 | return 88 | } 89 | } 90 | res := fn(p) 91 | if res == nil { 92 | return func(PingTraceRequestDone) { 93 | return 94 | } 95 | } 96 | return res 97 | } 98 | func pingTraceOnRequest(ctx context.Context, t PingTrace, r *http.Request) func(*http.Response, error) { 99 | var p PingTraceRequestStart 100 | p.Request = r 101 | res := t.onRequest(ctx, p) 102 | return func(r *http.Response, e error) { 103 | var p PingTraceRequestDone 104 | p.Response = r 105 | p.Error = e 106 | res(p) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gobwas/gtrace 2 | 3 | go 1.14 4 | -------------------------------------------------------------------------------- /test/internal/types.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | type Type struct { 4 | } 5 | -------------------------------------------------------------------------------- /test/test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | //go:generate gtrace -v 4 | 5 | //gtrace:gen 6 | //gtrace:set context 7 | //gtrace:set shortcut 8 | type Trace struct { 9 | OnTest func(string) func(string) 10 | 11 | OnAction func(TraceActionStart) func(TraceActionDone) 12 | OnActionPtr func(*TraceActionStart) func(*TraceActionDone) 13 | OnSomething0 func(int8) func(int16) func(int32) func(int64) 14 | OnSomething1 func(int8, int16) func(int32, int64) 15 | OnSomething2 func(Type) func(Type) func(Type) 16 | OnAnother func() 17 | OnAnother1 func(named string) 18 | 19 | // Not supported signatures: 20 | Skipped0 func() string 21 | Skipped1 func() (func(), func()) 22 | } 23 | 24 | type TraceActionStart struct { 25 | String string 26 | Nested Type 27 | } 28 | 29 | type TraceActionDone struct { 30 | Error error 31 | } 32 | -------------------------------------------------------------------------------- /test/test_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin,!amd64,!arm64 2 | 3 | package test 4 | 5 | //go:generate gtrace -v 6 | 7 | //gtrace:gen 8 | type ConditionalBuildTrace struct { 9 | OnSomething func() 10 | } 11 | -------------------------------------------------------------------------------- /test/test_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | // +build darwin,amd64 2 | 3 | package test 4 | 5 | //go:generate gtrace -v 6 | 7 | //gtrace:gen 8 | type ConditionalBuildTrace struct { 9 | OnSomething func() 10 | } 11 | -------------------------------------------------------------------------------- /test/test_darwin_arm64.go: -------------------------------------------------------------------------------- 1 | // +build darwin,arm64 2 | 3 | package test 4 | 5 | //go:generate gtrace -v 6 | 7 | //gtrace:gen 8 | type ConditionalBuildTrace struct { 9 | OnSomething func() 10 | } 11 | -------------------------------------------------------------------------------- /test/test_gtrace_darwin_amd64.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build darwin,amd64 4 | 5 | package test 6 | 7 | // Compose returns a new ConditionalBuildTrace which has functional fields composed 8 | // both from t and x. 9 | func (t ConditionalBuildTrace) Compose(x ConditionalBuildTrace) (ret ConditionalBuildTrace) { 10 | switch { 11 | case t.OnSomething == nil: 12 | ret.OnSomething = x.OnSomething 13 | case x.OnSomething == nil: 14 | ret.OnSomething = t.OnSomething 15 | default: 16 | h1 := t.OnSomething 17 | h2 := x.OnSomething 18 | ret.OnSomething = func() { 19 | h1() 20 | h2() 21 | } 22 | } 23 | return ret 24 | } 25 | func (t ConditionalBuildTrace) onSomething() { 26 | fn := t.OnSomething 27 | if fn == nil { 28 | return 29 | } 30 | fn() 31 | } 32 | -------------------------------------------------------------------------------- /test/test_gtrace_darwin_arm64.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build darwin,arm64 4 | 5 | package test 6 | 7 | // Compose returns a new ConditionalBuildTrace which has functional fields composed 8 | // both from t and x. 9 | func (t ConditionalBuildTrace) Compose(x ConditionalBuildTrace) (ret ConditionalBuildTrace) { 10 | switch { 11 | case t.OnSomething == nil: 12 | ret.OnSomething = x.OnSomething 13 | case x.OnSomething == nil: 14 | ret.OnSomething = t.OnSomething 15 | default: 16 | h1 := t.OnSomething 17 | h2 := x.OnSomething 18 | ret.OnSomething = func() { 19 | h1() 20 | h2() 21 | } 22 | } 23 | return ret 24 | } 25 | func (t ConditionalBuildTrace) onSomething() { 26 | fn := t.OnSomething 27 | if fn == nil { 28 | return 29 | } 30 | fn() 31 | } 32 | -------------------------------------------------------------------------------- /test/test_gtrace_linux.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build linux 4 | 5 | package test 6 | 7 | // Compose returns a new ConditionalBuildTrace which has functional fields composed 8 | // both from t and x. 9 | func (t ConditionalBuildTrace) Compose(x ConditionalBuildTrace) (ret ConditionalBuildTrace) { 10 | switch { 11 | case t.OnSomething == nil: 12 | ret.OnSomething = x.OnSomething 13 | case x.OnSomething == nil: 14 | ret.OnSomething = t.OnSomething 15 | default: 16 | h1 := t.OnSomething 17 | h2 := x.OnSomething 18 | ret.OnSomething = func() { 19 | h1() 20 | h2() 21 | } 22 | } 23 | return ret 24 | } 25 | func (t ConditionalBuildTrace) onSomething() { 26 | fn := t.OnSomething 27 | if fn == nil { 28 | return 29 | } 30 | fn() 31 | } 32 | -------------------------------------------------------------------------------- /test/test_gtrace_windows.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build windows 4 | 5 | package test 6 | 7 | // Compose returns a new ConditionalBuildTrace which has functional fields composed 8 | // both from t and x. 9 | func (t ConditionalBuildTrace) Compose(x ConditionalBuildTrace) (ret ConditionalBuildTrace) { 10 | switch { 11 | case t.OnSomething == nil: 12 | ret.OnSomething = x.OnSomething 13 | case x.OnSomething == nil: 14 | ret.OnSomething = t.OnSomething 15 | default: 16 | h1 := t.OnSomething 17 | h2 := x.OnSomething 18 | ret.OnSomething = func() { 19 | h1() 20 | h2() 21 | } 22 | } 23 | return ret 24 | } 25 | func (t ConditionalBuildTrace) onSomething() { 26 | fn := t.OnSomething 27 | if fn == nil { 28 | return 29 | } 30 | fn() 31 | } 32 | -------------------------------------------------------------------------------- /test/test_imports.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "github.com/gobwas/gtrace/test/internal" 4 | 5 | //go:generate gtrace -v 6 | 7 | //gtrace:gen 8 | //gtrace:set context 9 | // NOTE: must compile without unused imports error. 10 | type TraceNoShortcut struct { 11 | OnSomethingA func(Type) 12 | OnSomethingB func(internal.Type) 13 | } 14 | -------------------------------------------------------------------------------- /test/test_imports_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | package test 4 | 5 | import ( 6 | "context" 7 | 8 | "github.com/gobwas/gtrace/test/internal" 9 | ) 10 | 11 | // Compose returns a new TraceNoShortcut which has functional fields composed 12 | // both from t and x. 13 | func (t TraceNoShortcut) Compose(x TraceNoShortcut) (ret TraceNoShortcut) { 14 | switch { 15 | case t.OnSomethingA == nil: 16 | ret.OnSomethingA = x.OnSomethingA 17 | case x.OnSomethingA == nil: 18 | ret.OnSomethingA = t.OnSomethingA 19 | default: 20 | h1 := t.OnSomethingA 21 | h2 := x.OnSomethingA 22 | ret.OnSomethingA = func(t Type) { 23 | h1(t) 24 | h2(t) 25 | } 26 | } 27 | switch { 28 | case t.OnSomethingB == nil: 29 | ret.OnSomethingB = x.OnSomethingB 30 | case x.OnSomethingB == nil: 31 | ret.OnSomethingB = t.OnSomethingB 32 | default: 33 | h1 := t.OnSomethingB 34 | h2 := x.OnSomethingB 35 | ret.OnSomethingB = func(t internal.Type) { 36 | h1(t) 37 | h2(t) 38 | } 39 | } 40 | return ret 41 | } 42 | 43 | type traceNoShortcutContextKey struct{} 44 | 45 | // WithTraceNoShortcut returns context which has associated TraceNoShortcut with it. 46 | func WithTraceNoShortcut(ctx context.Context, t TraceNoShortcut) context.Context { 47 | return context.WithValue(ctx, 48 | traceNoShortcutContextKey{}, 49 | ContextTraceNoShortcut(ctx).Compose(t), 50 | ) 51 | } 52 | 53 | // ContextTraceNoShortcut returns TraceNoShortcut associated with ctx. 54 | // If there is no TraceNoShortcut associated with ctx then zero value 55 | // of TraceNoShortcut is returned. 56 | func ContextTraceNoShortcut(ctx context.Context) TraceNoShortcut { 57 | t, _ := ctx.Value(traceNoShortcutContextKey{}).(TraceNoShortcut) 58 | return t 59 | } 60 | 61 | func (t TraceNoShortcut) onSomethingA(ctx context.Context, t1 Type) { 62 | c := ContextTraceNoShortcut(ctx) 63 | var fn func(Type) 64 | switch { 65 | case t.OnSomethingA == nil: 66 | fn = c.OnSomethingA 67 | case c.OnSomethingA == nil: 68 | fn = t.OnSomethingA 69 | default: 70 | h1 := t.OnSomethingA 71 | h2 := c.OnSomethingA 72 | fn = func(t Type) { 73 | h1(t) 74 | h2(t) 75 | } 76 | } 77 | if fn == nil { 78 | return 79 | } 80 | fn(t1) 81 | } 82 | func (t TraceNoShortcut) onSomethingB(ctx context.Context, t1 internal.Type) { 83 | c := ContextTraceNoShortcut(ctx) 84 | var fn func(internal.Type) 85 | switch { 86 | case t.OnSomethingB == nil: 87 | fn = c.OnSomethingB 88 | case c.OnSomethingB == nil: 89 | fn = t.OnSomethingB 90 | default: 91 | h1 := t.OnSomethingB 92 | h2 := c.OnSomethingB 93 | fn = func(t internal.Type) { 94 | h1(t) 95 | h2(t) 96 | } 97 | } 98 | if fn == nil { 99 | return 100 | } 101 | fn(t1) 102 | } 103 | -------------------------------------------------------------------------------- /test/test_linux.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package test 4 | 5 | //go:generate gtrace -v 6 | 7 | //gtrace:gen 8 | type ConditionalBuildTrace struct { 9 | OnSomething func() 10 | } 11 | -------------------------------------------------------------------------------- /test/test_returned.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | //go:generate gtrace -v 4 | 5 | //gtrace:gen 6 | //gtrace:set shortcut 7 | type TraceReturningTrace struct { 8 | OnReturnedTrace func() ReturnedTrace 9 | } 10 | 11 | //gtrace:gen 12 | type ReturnedTrace struct { 13 | OnSomething func(a, b int) 14 | 15 | OnFoo func(_ int, _ int) 16 | OnBar func(_, _ int) 17 | OnBaz func(int, int) 18 | } 19 | -------------------------------------------------------------------------------- /test/test_returned_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | package test 4 | 5 | // Compose returns a new TraceReturningTrace which has functional fields composed 6 | // both from t and x. 7 | func (t TraceReturningTrace) Compose(x TraceReturningTrace) (ret TraceReturningTrace) { 8 | switch { 9 | case t.OnReturnedTrace == nil: 10 | ret.OnReturnedTrace = x.OnReturnedTrace 11 | case x.OnReturnedTrace == nil: 12 | ret.OnReturnedTrace = t.OnReturnedTrace 13 | default: 14 | h1 := t.OnReturnedTrace 15 | h2 := x.OnReturnedTrace 16 | ret.OnReturnedTrace = func() ReturnedTrace { 17 | r1 := h1() 18 | r2 := h2() 19 | switch { 20 | case r1.isZero(): 21 | return r2 22 | case r2.isZero(): 23 | return r1 24 | default: 25 | return r1.Compose(r2) 26 | } 27 | } 28 | } 29 | return ret 30 | } 31 | func (t TraceReturningTrace) onReturnedTrace() ReturnedTrace { 32 | fn := t.OnReturnedTrace 33 | if fn == nil { 34 | return ReturnedTrace{} 35 | } 36 | res := fn() 37 | return res 38 | } 39 | // Compose returns a new ReturnedTrace which has functional fields composed 40 | // both from t and x. 41 | func (t ReturnedTrace) Compose(x ReturnedTrace) (ret ReturnedTrace) { 42 | switch { 43 | case t.OnSomething == nil: 44 | ret.OnSomething = x.OnSomething 45 | case x.OnSomething == nil: 46 | ret.OnSomething = t.OnSomething 47 | default: 48 | h1 := t.OnSomething 49 | h2 := x.OnSomething 50 | ret.OnSomething = func(a int, b int) { 51 | h1(a, b) 52 | h2(a, b) 53 | } 54 | } 55 | switch { 56 | case t.OnFoo == nil: 57 | ret.OnFoo = x.OnFoo 58 | case x.OnFoo == nil: 59 | ret.OnFoo = t.OnFoo 60 | default: 61 | h1 := t.OnFoo 62 | h2 := x.OnFoo 63 | ret.OnFoo = func(i int, i1 int) { 64 | h1(i, i1) 65 | h2(i, i1) 66 | } 67 | } 68 | switch { 69 | case t.OnBar == nil: 70 | ret.OnBar = x.OnBar 71 | case x.OnBar == nil: 72 | ret.OnBar = t.OnBar 73 | default: 74 | h1 := t.OnBar 75 | h2 := x.OnBar 76 | ret.OnBar = func(i int, i1 int) { 77 | h1(i, i1) 78 | h2(i, i1) 79 | } 80 | } 81 | switch { 82 | case t.OnBaz == nil: 83 | ret.OnBaz = x.OnBaz 84 | case x.OnBaz == nil: 85 | ret.OnBaz = t.OnBaz 86 | default: 87 | h1 := t.OnBaz 88 | h2 := x.OnBaz 89 | ret.OnBaz = func(i int, i1 int) { 90 | h1(i, i1) 91 | h2(i, i1) 92 | } 93 | } 94 | return ret 95 | } 96 | // isZero checks whether t is empty 97 | func (t ReturnedTrace) isZero() bool { 98 | if t.OnSomething != nil { 99 | return false 100 | } 101 | if t.OnFoo != nil { 102 | return false 103 | } 104 | if t.OnBar != nil { 105 | return false 106 | } 107 | if t.OnBaz != nil { 108 | return false 109 | } 110 | return true 111 | } 112 | func (t ReturnedTrace) onSomething(a int, b int) { 113 | fn := t.OnSomething 114 | if fn == nil { 115 | return 116 | } 117 | fn(a, b) 118 | } 119 | func (t ReturnedTrace) onFoo(i int, i1 int) { 120 | fn := t.OnFoo 121 | if fn == nil { 122 | return 123 | } 124 | fn(i, i1) 125 | } 126 | func (t ReturnedTrace) onBar(i int, i1 int) { 127 | fn := t.OnBar 128 | if fn == nil { 129 | return 130 | } 131 | fn(i, i1) 132 | } 133 | func (t ReturnedTrace) onBaz(i int, i1 int) { 134 | fn := t.OnBaz 135 | if fn == nil { 136 | return 137 | } 138 | fn(i, i1) 139 | } 140 | func traceReturningTraceOnReturnedTrace(t TraceReturningTrace) ReturnedTrace { 141 | res := t.onReturnedTrace() 142 | return res 143 | } 144 | -------------------------------------------------------------------------------- /test/test_returned_tags.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | //go:generate gtrace -v -tag gtrace 4 | 5 | //gtrace:gen 6 | //gtrace:set shortcut 7 | type TraceReturningTraceTags struct { 8 | OnReturnedTrace func() ReturnedTrace 9 | } 10 | -------------------------------------------------------------------------------- /test/test_returned_tags_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build gtrace 4 | 5 | package test 6 | 7 | // Compose returns a new TraceReturningTraceTags which has functional fields composed 8 | // both from t and x. 9 | func (t TraceReturningTraceTags) Compose(x TraceReturningTraceTags) (ret TraceReturningTraceTags) { 10 | return ret 11 | } 12 | -------------------------------------------------------------------------------- /test/test_returned_tags_gtrace_stub.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build !gtrace 4 | 5 | package test 6 | 7 | // Compose returns a new TraceReturningTraceTags which has functional fields composed 8 | // both from t and x. 9 | func (t TraceReturningTraceTags) Compose(x TraceReturningTraceTags) (ret TraceReturningTraceTags) { 10 | return ret 11 | } 12 | -------------------------------------------------------------------------------- /test/test_shortcut.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | //go:generate gtrace -v 4 | 5 | //gtrace:gen 6 | type ShortcutPerFieldTrace struct { 7 | //gtrace:set shortcut 8 | OnFoo func() 9 | OnBar func() 10 | } 11 | -------------------------------------------------------------------------------- /test/test_shortcut_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | package test 4 | 5 | // Compose returns a new ShortcutPerFieldTrace which has functional fields composed 6 | // both from t and x. 7 | func (t ShortcutPerFieldTrace) Compose(x ShortcutPerFieldTrace) (ret ShortcutPerFieldTrace) { 8 | switch { 9 | case t.OnFoo == nil: 10 | ret.OnFoo = x.OnFoo 11 | case x.OnFoo == nil: 12 | ret.OnFoo = t.OnFoo 13 | default: 14 | h1 := t.OnFoo 15 | h2 := x.OnFoo 16 | ret.OnFoo = func() { 17 | h1() 18 | h2() 19 | } 20 | } 21 | switch { 22 | case t.OnBar == nil: 23 | ret.OnBar = x.OnBar 24 | case x.OnBar == nil: 25 | ret.OnBar = t.OnBar 26 | default: 27 | h1 := t.OnBar 28 | h2 := x.OnBar 29 | ret.OnBar = func() { 30 | h1() 31 | h2() 32 | } 33 | } 34 | return ret 35 | } 36 | func (t ShortcutPerFieldTrace) onFoo() { 37 | fn := t.OnFoo 38 | if fn == nil { 39 | return 40 | } 41 | fn() 42 | } 43 | func (t ShortcutPerFieldTrace) onBar() { 44 | fn := t.OnBar 45 | if fn == nil { 46 | return 47 | } 48 | fn() 49 | } 50 | func shortcutPerFieldTraceOnFoo(t ShortcutPerFieldTrace) { 51 | t.onFoo() 52 | } 53 | -------------------------------------------------------------------------------- /test/test_tags.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | //go:generate gtrace -v -tag gtrace 4 | 5 | //gtrace:gen 6 | //gtrace:set context 7 | //gtrace:set shortcut 8 | type BuildTagTrace struct { 9 | OnSomethingA func() func() 10 | OnSomethingB func(int8, int16) func(int32, int64) 11 | OnSomethingC func(Type) func(Type) 12 | } 13 | -------------------------------------------------------------------------------- /test/test_tags_dup.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | //go:generate gtrace -v -tag gtrace 4 | 5 | //gtrace:gen 6 | //gtrace:set context 7 | //gtrace:set shortcut 8 | type AnotherBuildTagTrace struct { 9 | OnSomethingA func() func() 10 | OnSomethingB func(int8, int16) func(int32, int64) 11 | OnSomethingC func(Type) func(Type) 12 | } 13 | -------------------------------------------------------------------------------- /test/test_tags_dup_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build gtrace 4 | 5 | package test 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | ) 11 | 12 | // Compose returns a new AnotherBuildTagTrace which has functional fields composed 13 | // both from t and x. 14 | func (t AnotherBuildTagTrace) Compose(x AnotherBuildTagTrace) (ret AnotherBuildTagTrace) { 15 | switch { 16 | case t.OnSomethingA == nil: 17 | ret.OnSomethingA = x.OnSomethingA 18 | case x.OnSomethingA == nil: 19 | ret.OnSomethingA = t.OnSomethingA 20 | default: 21 | h1 := t.OnSomethingA 22 | h2 := x.OnSomethingA 23 | ret.OnSomethingA = func() func() { 24 | r1 := h1() 25 | r2 := h2() 26 | switch { 27 | case r1 == nil: 28 | return r2 29 | case r2 == nil: 30 | return r1 31 | default: 32 | return func() { 33 | r1() 34 | r2() 35 | } 36 | } 37 | } 38 | } 39 | switch { 40 | case t.OnSomethingB == nil: 41 | ret.OnSomethingB = x.OnSomethingB 42 | case x.OnSomethingB == nil: 43 | ret.OnSomethingB = t.OnSomethingB 44 | default: 45 | h1 := t.OnSomethingB 46 | h2 := x.OnSomethingB 47 | ret.OnSomethingB = func(i int8, i1 int16) func(int32, int64) { 48 | r1 := h1(i, i1) 49 | r2 := h2(i, i1) 50 | switch { 51 | case r1 == nil: 52 | return r2 53 | case r2 == nil: 54 | return r1 55 | default: 56 | return func(i int32, i1 int64) { 57 | r1(i, i1) 58 | r2(i, i1) 59 | } 60 | } 61 | } 62 | } 63 | switch { 64 | case t.OnSomethingC == nil: 65 | ret.OnSomethingC = x.OnSomethingC 66 | case x.OnSomethingC == nil: 67 | ret.OnSomethingC = t.OnSomethingC 68 | default: 69 | h1 := t.OnSomethingC 70 | h2 := x.OnSomethingC 71 | ret.OnSomethingC = func(t Type) func(Type) { 72 | r1 := h1(t) 73 | r2 := h2(t) 74 | switch { 75 | case r1 == nil: 76 | return r2 77 | case r2 == nil: 78 | return r1 79 | default: 80 | return func(t Type) { 81 | r1(t) 82 | r2(t) 83 | } 84 | } 85 | } 86 | } 87 | return ret 88 | } 89 | 90 | type anotherBuildTagTraceContextKey struct{} 91 | 92 | // WithAnotherBuildTagTrace returns context which has associated AnotherBuildTagTrace with it. 93 | func WithAnotherBuildTagTrace(ctx context.Context, t AnotherBuildTagTrace) context.Context { 94 | return context.WithValue(ctx, 95 | anotherBuildTagTraceContextKey{}, 96 | ContextAnotherBuildTagTrace(ctx).Compose(t), 97 | ) 98 | } 99 | 100 | // ContextAnotherBuildTagTrace returns AnotherBuildTagTrace associated with ctx. 101 | // If there is no AnotherBuildTagTrace associated with ctx then zero value 102 | // of AnotherBuildTagTrace is returned. 103 | func ContextAnotherBuildTagTrace(ctx context.Context) AnotherBuildTagTrace { 104 | t, _ := ctx.Value(anotherBuildTagTraceContextKey{}).(AnotherBuildTagTrace) 105 | return t 106 | } 107 | 108 | func (t AnotherBuildTagTrace) onSomethingA(ctx context.Context) func() { 109 | c := ContextAnotherBuildTagTrace(ctx) 110 | var fn func() func() 111 | switch { 112 | case t.OnSomethingA == nil: 113 | fn = c.OnSomethingA 114 | case c.OnSomethingA == nil: 115 | fn = t.OnSomethingA 116 | default: 117 | h1 := t.OnSomethingA 118 | h2 := c.OnSomethingA 119 | fn = func() func() { 120 | r1 := h1() 121 | r2 := h2() 122 | switch { 123 | case r1 == nil: 124 | return r2 125 | case r2 == nil: 126 | return r1 127 | default: 128 | return func() { 129 | r1() 130 | r2() 131 | } 132 | } 133 | } 134 | } 135 | if fn == nil { 136 | return func() { 137 | return 138 | } 139 | } 140 | res := fn() 141 | if res == nil { 142 | return func() { 143 | return 144 | } 145 | } 146 | return res 147 | } 148 | func (t AnotherBuildTagTrace) onSomethingB(ctx context.Context, i int8, i1 int16) func(int32, int64) { 149 | c := ContextAnotherBuildTagTrace(ctx) 150 | var fn func(int8, int16) func(int32, int64) 151 | switch { 152 | case t.OnSomethingB == nil: 153 | fn = c.OnSomethingB 154 | case c.OnSomethingB == nil: 155 | fn = t.OnSomethingB 156 | default: 157 | h1 := t.OnSomethingB 158 | h2 := c.OnSomethingB 159 | fn = func(i int8, i1 int16) func(int32, int64) { 160 | r1 := h1(i, i1) 161 | r2 := h2(i, i1) 162 | switch { 163 | case r1 == nil: 164 | return r2 165 | case r2 == nil: 166 | return r1 167 | default: 168 | return func(i int32, i1 int64) { 169 | r1(i, i1) 170 | r2(i, i1) 171 | } 172 | } 173 | } 174 | } 175 | if fn == nil { 176 | return func(int32, int64) { 177 | return 178 | } 179 | } 180 | res := fn(i, i1) 181 | if res == nil { 182 | return func(int32, int64) { 183 | return 184 | } 185 | } 186 | return res 187 | } 188 | func (t AnotherBuildTagTrace) onSomethingC(ctx context.Context, t1 Type) func(Type) { 189 | c := ContextAnotherBuildTagTrace(ctx) 190 | var fn func(Type) func(Type) 191 | switch { 192 | case t.OnSomethingC == nil: 193 | fn = c.OnSomethingC 194 | case c.OnSomethingC == nil: 195 | fn = t.OnSomethingC 196 | default: 197 | h1 := t.OnSomethingC 198 | h2 := c.OnSomethingC 199 | fn = func(t Type) func(Type) { 200 | r1 := h1(t) 201 | r2 := h2(t) 202 | switch { 203 | case r1 == nil: 204 | return r2 205 | case r2 == nil: 206 | return r1 207 | default: 208 | return func(t Type) { 209 | r1(t) 210 | r2(t) 211 | } 212 | } 213 | } 214 | } 215 | if fn == nil { 216 | return func(Type) { 217 | return 218 | } 219 | } 220 | res := fn(t1) 221 | if res == nil { 222 | return func(Type) { 223 | return 224 | } 225 | } 226 | return res 227 | } 228 | func anotherBuildTagTraceOnSomethingA(ctx context.Context, t AnotherBuildTagTrace) func() { 229 | res := t.onSomethingA(ctx) 230 | return func() { 231 | res() 232 | } 233 | } 234 | func anotherBuildTagTraceOnSomethingB(ctx context.Context, t AnotherBuildTagTrace, i int8, i1 int16) func(int32, int64) { 235 | res := t.onSomethingB(ctx, i, i1) 236 | return func(i int32, i1 int64) { 237 | res(i, i1) 238 | } 239 | } 240 | func anotherBuildTagTraceOnSomethingC(ctx context.Context, t AnotherBuildTagTrace, e Embeded, s string, integer int, boolean bool, e1 error, r bytes.Reader) func(_ Embeded, _ string, integer int, boolean bool, _ error, _ bytes.Reader) { 241 | var p Type 242 | p.Embeded = e 243 | p.String = s 244 | p.Integer = integer 245 | p.Boolean = boolean 246 | p.Error = e1 247 | p.Reader = r 248 | res := t.onSomethingC(ctx, p) 249 | return func(e Embeded, s string, integer int, boolean bool, e1 error, r bytes.Reader) { 250 | var p Type 251 | p.Embeded = e 252 | p.String = s 253 | p.Integer = integer 254 | p.Boolean = boolean 255 | p.Error = e1 256 | p.Reader = r 257 | res(p) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /test/test_tags_dup_gtrace_stub.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build !gtrace 4 | 5 | package test 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | ) 11 | 12 | // Compose returns a new AnotherBuildTagTrace which has functional fields composed 13 | // both from t and x. 14 | func (t AnotherBuildTagTrace) Compose(x AnotherBuildTagTrace) (ret AnotherBuildTagTrace) { 15 | switch { 16 | case t.OnSomethingA == nil: 17 | ret.OnSomethingA = x.OnSomethingA 18 | case x.OnSomethingA == nil: 19 | ret.OnSomethingA = t.OnSomethingA 20 | default: 21 | h1 := t.OnSomethingA 22 | h2 := x.OnSomethingA 23 | ret.OnSomethingA = func() func() { 24 | r1 := h1() 25 | r2 := h2() 26 | switch { 27 | case r1 == nil: 28 | return r2 29 | case r2 == nil: 30 | return r1 31 | default: 32 | return func() { 33 | r1() 34 | r2() 35 | } 36 | } 37 | } 38 | } 39 | switch { 40 | case t.OnSomethingB == nil: 41 | ret.OnSomethingB = x.OnSomethingB 42 | case x.OnSomethingB == nil: 43 | ret.OnSomethingB = t.OnSomethingB 44 | default: 45 | h1 := t.OnSomethingB 46 | h2 := x.OnSomethingB 47 | ret.OnSomethingB = func(i int8, i1 int16) func(int32, int64) { 48 | r1 := h1(i, i1) 49 | r2 := h2(i, i1) 50 | switch { 51 | case r1 == nil: 52 | return r2 53 | case r2 == nil: 54 | return r1 55 | default: 56 | return func(i int32, i1 int64) { 57 | r1(i, i1) 58 | r2(i, i1) 59 | } 60 | } 61 | } 62 | } 63 | switch { 64 | case t.OnSomethingC == nil: 65 | ret.OnSomethingC = x.OnSomethingC 66 | case x.OnSomethingC == nil: 67 | ret.OnSomethingC = t.OnSomethingC 68 | default: 69 | h1 := t.OnSomethingC 70 | h2 := x.OnSomethingC 71 | ret.OnSomethingC = func(t Type) func(Type) { 72 | r1 := h1(t) 73 | r2 := h2(t) 74 | switch { 75 | case r1 == nil: 76 | return r2 77 | case r2 == nil: 78 | return r1 79 | default: 80 | return func(t Type) { 81 | r1(t) 82 | r2(t) 83 | } 84 | } 85 | } 86 | } 87 | return ret 88 | } 89 | 90 | type anotherBuildTagTraceContextKey struct{} 91 | 92 | // WithAnotherBuildTagTrace returns context which has associated AnotherBuildTagTrace with it. 93 | func WithAnotherBuildTagTrace(ctx context.Context, t AnotherBuildTagTrace) context.Context { 94 | return context.WithValue(ctx, 95 | anotherBuildTagTraceContextKey{}, 96 | ContextAnotherBuildTagTrace(ctx).Compose(t), 97 | ) 98 | } 99 | 100 | // ContextAnotherBuildTagTrace returns AnotherBuildTagTrace associated with ctx. 101 | // If there is no AnotherBuildTagTrace associated with ctx then zero value 102 | // of AnotherBuildTagTrace is returned. 103 | func ContextAnotherBuildTagTrace(ctx context.Context) AnotherBuildTagTrace { 104 | t, _ := ctx.Value(anotherBuildTagTraceContextKey{}).(AnotherBuildTagTrace) 105 | return t 106 | } 107 | 108 | func gtraceNoopBdbd803d() { 109 | } 110 | func (AnotherBuildTagTrace) onSomethingA(context.Context) func() { 111 | return gtraceNoopBdbd803d 112 | } 113 | func gtraceNoop86ffa8ac(int32, int64) { 114 | } 115 | func (AnotherBuildTagTrace) onSomethingB(context.Context, int8, int16) func(int32, int64) { 116 | return gtraceNoop86ffa8ac 117 | } 118 | func gtraceNoop8a9c608a(Type) { 119 | } 120 | func (AnotherBuildTagTrace) onSomethingC(context.Context, Type) func(Type) { 121 | return gtraceNoop8a9c608a 122 | } 123 | func gtraceNoopBdbd803d1() { 124 | } 125 | func anotherBuildTagTraceOnSomethingA(context.Context, AnotherBuildTagTrace) func() { 126 | return gtraceNoopBdbd803d1 127 | } 128 | func gtraceNoop86ffa8ac1(int32, int64) { 129 | } 130 | func anotherBuildTagTraceOnSomethingB(context.Context, AnotherBuildTagTrace, int8, int16) func(int32, int64) { 131 | return gtraceNoop86ffa8ac1 132 | } 133 | func gtraceNoop8a9c608a1(Embeded, string, int, bool, error, bytes.Reader) { 134 | } 135 | func anotherBuildTagTraceOnSomethingC(ctx context.Context, t AnotherBuildTagTrace, e Embeded, s string, integer int, boolean bool, e1 error, r bytes.Reader) func(_ Embeded, _ string, integer int, boolean bool, _ error, _ bytes.Reader) { 136 | return gtraceNoop8a9c608a1 137 | } 138 | -------------------------------------------------------------------------------- /test/test_tags_gtrace.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build gtrace 4 | 5 | package test 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | ) 11 | 12 | // Compose returns a new BuildTagTrace which has functional fields composed 13 | // both from t and x. 14 | func (t BuildTagTrace) Compose(x BuildTagTrace) (ret BuildTagTrace) { 15 | switch { 16 | case t.OnSomethingA == nil: 17 | ret.OnSomethingA = x.OnSomethingA 18 | case x.OnSomethingA == nil: 19 | ret.OnSomethingA = t.OnSomethingA 20 | default: 21 | h1 := t.OnSomethingA 22 | h2 := x.OnSomethingA 23 | ret.OnSomethingA = func() func() { 24 | r1 := h1() 25 | r2 := h2() 26 | switch { 27 | case r1 == nil: 28 | return r2 29 | case r2 == nil: 30 | return r1 31 | default: 32 | return func() { 33 | r1() 34 | r2() 35 | } 36 | } 37 | } 38 | } 39 | switch { 40 | case t.OnSomethingB == nil: 41 | ret.OnSomethingB = x.OnSomethingB 42 | case x.OnSomethingB == nil: 43 | ret.OnSomethingB = t.OnSomethingB 44 | default: 45 | h1 := t.OnSomethingB 46 | h2 := x.OnSomethingB 47 | ret.OnSomethingB = func(i int8, i1 int16) func(int32, int64) { 48 | r1 := h1(i, i1) 49 | r2 := h2(i, i1) 50 | switch { 51 | case r1 == nil: 52 | return r2 53 | case r2 == nil: 54 | return r1 55 | default: 56 | return func(i int32, i1 int64) { 57 | r1(i, i1) 58 | r2(i, i1) 59 | } 60 | } 61 | } 62 | } 63 | switch { 64 | case t.OnSomethingC == nil: 65 | ret.OnSomethingC = x.OnSomethingC 66 | case x.OnSomethingC == nil: 67 | ret.OnSomethingC = t.OnSomethingC 68 | default: 69 | h1 := t.OnSomethingC 70 | h2 := x.OnSomethingC 71 | ret.OnSomethingC = func(t Type) func(Type) { 72 | r1 := h1(t) 73 | r2 := h2(t) 74 | switch { 75 | case r1 == nil: 76 | return r2 77 | case r2 == nil: 78 | return r1 79 | default: 80 | return func(t Type) { 81 | r1(t) 82 | r2(t) 83 | } 84 | } 85 | } 86 | } 87 | return ret 88 | } 89 | 90 | type buildTagTraceContextKey struct{} 91 | 92 | // WithBuildTagTrace returns context which has associated BuildTagTrace with it. 93 | func WithBuildTagTrace(ctx context.Context, t BuildTagTrace) context.Context { 94 | return context.WithValue(ctx, 95 | buildTagTraceContextKey{}, 96 | ContextBuildTagTrace(ctx).Compose(t), 97 | ) 98 | } 99 | 100 | // ContextBuildTagTrace returns BuildTagTrace associated with ctx. 101 | // If there is no BuildTagTrace associated with ctx then zero value 102 | // of BuildTagTrace is returned. 103 | func ContextBuildTagTrace(ctx context.Context) BuildTagTrace { 104 | t, _ := ctx.Value(buildTagTraceContextKey{}).(BuildTagTrace) 105 | return t 106 | } 107 | 108 | func (t BuildTagTrace) onSomethingA(ctx context.Context) func() { 109 | c := ContextBuildTagTrace(ctx) 110 | var fn func() func() 111 | switch { 112 | case t.OnSomethingA == nil: 113 | fn = c.OnSomethingA 114 | case c.OnSomethingA == nil: 115 | fn = t.OnSomethingA 116 | default: 117 | h1 := t.OnSomethingA 118 | h2 := c.OnSomethingA 119 | fn = func() func() { 120 | r1 := h1() 121 | r2 := h2() 122 | switch { 123 | case r1 == nil: 124 | return r2 125 | case r2 == nil: 126 | return r1 127 | default: 128 | return func() { 129 | r1() 130 | r2() 131 | } 132 | } 133 | } 134 | } 135 | if fn == nil { 136 | return func() { 137 | return 138 | } 139 | } 140 | res := fn() 141 | if res == nil { 142 | return func() { 143 | return 144 | } 145 | } 146 | return res 147 | } 148 | func (t BuildTagTrace) onSomethingB(ctx context.Context, i int8, i1 int16) func(int32, int64) { 149 | c := ContextBuildTagTrace(ctx) 150 | var fn func(int8, int16) func(int32, int64) 151 | switch { 152 | case t.OnSomethingB == nil: 153 | fn = c.OnSomethingB 154 | case c.OnSomethingB == nil: 155 | fn = t.OnSomethingB 156 | default: 157 | h1 := t.OnSomethingB 158 | h2 := c.OnSomethingB 159 | fn = func(i int8, i1 int16) func(int32, int64) { 160 | r1 := h1(i, i1) 161 | r2 := h2(i, i1) 162 | switch { 163 | case r1 == nil: 164 | return r2 165 | case r2 == nil: 166 | return r1 167 | default: 168 | return func(i int32, i1 int64) { 169 | r1(i, i1) 170 | r2(i, i1) 171 | } 172 | } 173 | } 174 | } 175 | if fn == nil { 176 | return func(int32, int64) { 177 | return 178 | } 179 | } 180 | res := fn(i, i1) 181 | if res == nil { 182 | return func(int32, int64) { 183 | return 184 | } 185 | } 186 | return res 187 | } 188 | func (t BuildTagTrace) onSomethingC(ctx context.Context, t1 Type) func(Type) { 189 | c := ContextBuildTagTrace(ctx) 190 | var fn func(Type) func(Type) 191 | switch { 192 | case t.OnSomethingC == nil: 193 | fn = c.OnSomethingC 194 | case c.OnSomethingC == nil: 195 | fn = t.OnSomethingC 196 | default: 197 | h1 := t.OnSomethingC 198 | h2 := c.OnSomethingC 199 | fn = func(t Type) func(Type) { 200 | r1 := h1(t) 201 | r2 := h2(t) 202 | switch { 203 | case r1 == nil: 204 | return r2 205 | case r2 == nil: 206 | return r1 207 | default: 208 | return func(t Type) { 209 | r1(t) 210 | r2(t) 211 | } 212 | } 213 | } 214 | } 215 | if fn == nil { 216 | return func(Type) { 217 | return 218 | } 219 | } 220 | res := fn(t1) 221 | if res == nil { 222 | return func(Type) { 223 | return 224 | } 225 | } 226 | return res 227 | } 228 | func buildTagTraceOnSomethingA(ctx context.Context, t BuildTagTrace) func() { 229 | res := t.onSomethingA(ctx) 230 | return func() { 231 | res() 232 | } 233 | } 234 | func buildTagTraceOnSomethingB(ctx context.Context, t BuildTagTrace, i int8, i1 int16) func(int32, int64) { 235 | res := t.onSomethingB(ctx, i, i1) 236 | return func(i int32, i1 int64) { 237 | res(i, i1) 238 | } 239 | } 240 | func buildTagTraceOnSomethingC(ctx context.Context, t BuildTagTrace, e Embeded, s string, integer int, boolean bool, e1 error, r bytes.Reader) func(_ Embeded, _ string, integer int, boolean bool, _ error, _ bytes.Reader) { 241 | var p Type 242 | p.Embeded = e 243 | p.String = s 244 | p.Integer = integer 245 | p.Boolean = boolean 246 | p.Error = e1 247 | p.Reader = r 248 | res := t.onSomethingC(ctx, p) 249 | return func(e Embeded, s string, integer int, boolean bool, e1 error, r bytes.Reader) { 250 | var p Type 251 | p.Embeded = e 252 | p.String = s 253 | p.Integer = integer 254 | p.Boolean = boolean 255 | p.Error = e1 256 | p.Reader = r 257 | res(p) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /test/test_tags_gtrace_stub.go: -------------------------------------------------------------------------------- 1 | // Code generated by gtrace. DO NOT EDIT. 2 | 3 | // +build !gtrace 4 | 5 | package test 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | ) 11 | 12 | // Compose returns a new BuildTagTrace which has functional fields composed 13 | // both from t and x. 14 | func (t BuildTagTrace) Compose(x BuildTagTrace) (ret BuildTagTrace) { 15 | switch { 16 | case t.OnSomethingA == nil: 17 | ret.OnSomethingA = x.OnSomethingA 18 | case x.OnSomethingA == nil: 19 | ret.OnSomethingA = t.OnSomethingA 20 | default: 21 | h1 := t.OnSomethingA 22 | h2 := x.OnSomethingA 23 | ret.OnSomethingA = func() func() { 24 | r1 := h1() 25 | r2 := h2() 26 | switch { 27 | case r1 == nil: 28 | return r2 29 | case r2 == nil: 30 | return r1 31 | default: 32 | return func() { 33 | r1() 34 | r2() 35 | } 36 | } 37 | } 38 | } 39 | switch { 40 | case t.OnSomethingB == nil: 41 | ret.OnSomethingB = x.OnSomethingB 42 | case x.OnSomethingB == nil: 43 | ret.OnSomethingB = t.OnSomethingB 44 | default: 45 | h1 := t.OnSomethingB 46 | h2 := x.OnSomethingB 47 | ret.OnSomethingB = func(i int8, i1 int16) func(int32, int64) { 48 | r1 := h1(i, i1) 49 | r2 := h2(i, i1) 50 | switch { 51 | case r1 == nil: 52 | return r2 53 | case r2 == nil: 54 | return r1 55 | default: 56 | return func(i int32, i1 int64) { 57 | r1(i, i1) 58 | r2(i, i1) 59 | } 60 | } 61 | } 62 | } 63 | switch { 64 | case t.OnSomethingC == nil: 65 | ret.OnSomethingC = x.OnSomethingC 66 | case x.OnSomethingC == nil: 67 | ret.OnSomethingC = t.OnSomethingC 68 | default: 69 | h1 := t.OnSomethingC 70 | h2 := x.OnSomethingC 71 | ret.OnSomethingC = func(t Type) func(Type) { 72 | r1 := h1(t) 73 | r2 := h2(t) 74 | switch { 75 | case r1 == nil: 76 | return r2 77 | case r2 == nil: 78 | return r1 79 | default: 80 | return func(t Type) { 81 | r1(t) 82 | r2(t) 83 | } 84 | } 85 | } 86 | } 87 | return ret 88 | } 89 | 90 | type buildTagTraceContextKey struct{} 91 | 92 | // WithBuildTagTrace returns context which has associated BuildTagTrace with it. 93 | func WithBuildTagTrace(ctx context.Context, t BuildTagTrace) context.Context { 94 | return context.WithValue(ctx, 95 | buildTagTraceContextKey{}, 96 | ContextBuildTagTrace(ctx).Compose(t), 97 | ) 98 | } 99 | 100 | // ContextBuildTagTrace returns BuildTagTrace associated with ctx. 101 | // If there is no BuildTagTrace associated with ctx then zero value 102 | // of BuildTagTrace is returned. 103 | func ContextBuildTagTrace(ctx context.Context) BuildTagTrace { 104 | t, _ := ctx.Value(buildTagTraceContextKey{}).(BuildTagTrace) 105 | return t 106 | } 107 | 108 | func gtraceNoopDe11bf52() { 109 | } 110 | func (BuildTagTrace) onSomethingA(context.Context) func() { 111 | return gtraceNoopDe11bf52 112 | } 113 | func gtraceNoop61afed37(int32, int64) { 114 | } 115 | func (BuildTagTrace) onSomethingB(context.Context, int8, int16) func(int32, int64) { 116 | return gtraceNoop61afed37 117 | } 118 | func gtraceNoop8bdd2eba(Type) { 119 | } 120 | func (BuildTagTrace) onSomethingC(context.Context, Type) func(Type) { 121 | return gtraceNoop8bdd2eba 122 | } 123 | func gtraceNoopDe11bf521() { 124 | } 125 | func buildTagTraceOnSomethingA(context.Context, BuildTagTrace) func() { 126 | return gtraceNoopDe11bf521 127 | } 128 | func gtraceNoop61afed371(int32, int64) { 129 | } 130 | func buildTagTraceOnSomethingB(context.Context, BuildTagTrace, int8, int16) func(int32, int64) { 131 | return gtraceNoop61afed371 132 | } 133 | func gtraceNoop8bdd2eba1(Embeded, string, int, bool, error, bytes.Reader) { 134 | } 135 | func buildTagTraceOnSomethingC(ctx context.Context, t BuildTagTrace, e Embeded, s string, integer int, boolean bool, e1 error, r bytes.Reader) func(_ Embeded, _ string, integer int, boolean bool, _ error, _ bytes.Reader) { 136 | return gtraceNoop8bdd2eba1 137 | } 138 | -------------------------------------------------------------------------------- /test/test_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "context" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestReturnedTrace(t *testing.T) { 10 | var called bool 11 | trace := TraceReturningTrace{ 12 | OnReturnedTrace: func() ReturnedTrace { 13 | return ReturnedTrace{ 14 | OnSomething: func(a, b int) { 15 | called = true 16 | }, 17 | } 18 | }, 19 | } 20 | trace.onReturnedTrace().onSomething(1, 2) 21 | if !called { 22 | t.Fatalf("not called") 23 | } 24 | } 25 | 26 | func TestConditional(t *testing.T) { 27 | var called bool 28 | trace := ConditionalBuildTrace{ 29 | OnSomething: func() { 30 | called = true 31 | }, 32 | } 33 | trace.onSomething() 34 | if !called { 35 | t.Fatalf("not called") 36 | } 37 | } 38 | 39 | func TestCompose(t *testing.T) { 40 | var act []string 41 | called := func(name, arg string) { 42 | act = append(act, name+":"+arg) 43 | } 44 | for _, test := range []struct { 45 | name string 46 | a Trace 47 | b Trace 48 | exp []string 49 | }{ 50 | { 51 | a: Trace{ 52 | OnTest: func(s string) func(string) { 53 | called("a", s) 54 | return nil 55 | }, 56 | }, 57 | b: Trace{}, 58 | 59 | exp: []string{ 60 | "a:start", 61 | }, 62 | }, 63 | { 64 | a: Trace{}, 65 | b: Trace{ 66 | OnTest: func(s string) func(string) { 67 | called("b", s) 68 | return nil 69 | }, 70 | }, 71 | exp: []string{ 72 | "b:start", 73 | }, 74 | }, 75 | { 76 | a: Trace{ 77 | OnTest: func(s string) func(string) { 78 | called("a", s) 79 | return nil 80 | }, 81 | }, 82 | b: Trace{ 83 | OnTest: func(s string) func(string) { 84 | called("b", s) 85 | return nil 86 | }, 87 | }, 88 | exp: []string{ 89 | "a:start", 90 | "b:start", 91 | }, 92 | }, 93 | { 94 | a: Trace{ 95 | OnTest: func(s string) func(string) { 96 | called("a", s) 97 | return func(s string) { 98 | called("a", s) 99 | } 100 | }, 101 | }, 102 | b: Trace{}, 103 | 104 | exp: []string{ 105 | "a:start", 106 | "a:done", 107 | }, 108 | }, 109 | { 110 | a: Trace{}, 111 | b: Trace{ 112 | OnTest: func(s string) func(string) { 113 | called("b", s) 114 | return func(s string) { 115 | called("b", s) 116 | } 117 | }, 118 | }, 119 | exp: []string{ 120 | "b:start", 121 | "b:done", 122 | }, 123 | }, 124 | { 125 | a: Trace{ 126 | OnTest: func(s string) func(string) { 127 | called("a", s) 128 | return func(s string) { 129 | called("a", s) 130 | } 131 | }, 132 | }, 133 | b: Trace{ 134 | OnTest: func(s string) func(string) { 135 | called("b", s) 136 | return func(s string) { 137 | called("b", s) 138 | } 139 | }, 140 | }, 141 | exp: []string{ 142 | "a:start", 143 | "b:start", 144 | "a:done", 145 | "b:done", 146 | }, 147 | }, 148 | { 149 | a: Trace{ 150 | OnTest: func(s string) func(string) { 151 | called("a", s) 152 | return func(s string) { 153 | called("a", s) 154 | } 155 | }, 156 | }, 157 | b: Trace{ 158 | OnTest: func(s string) func(string) { 159 | called("b", s) 160 | return nil 161 | }, 162 | }, 163 | exp: []string{ 164 | "a:start", 165 | "b:start", 166 | "a:done", 167 | }, 168 | }, 169 | { 170 | a: Trace{ 171 | OnTest: func(s string) func(string) { 172 | called("a", s) 173 | return nil 174 | }, 175 | }, 176 | b: Trace{ 177 | OnTest: func(s string) func(string) { 178 | called("b", s) 179 | return func(s string) { 180 | called("b", s) 181 | } 182 | }, 183 | }, 184 | exp: []string{ 185 | "a:start", 186 | "b:start", 187 | "b:done", 188 | }, 189 | }, 190 | } { 191 | t.Run(test.name, func(t *testing.T) { 192 | t.Cleanup(func() { 193 | act = nil 194 | }) 195 | c := test.a.Compose(test.b) 196 | done := c.onTest(context.Background(), "start") 197 | done("done") 198 | 199 | if exp := test.exp; !reflect.DeepEqual(act, exp) { 200 | t.Fatalf("unexpected calls: %v; want %v", act, exp) 201 | } 202 | }) 203 | } 204 | } 205 | 206 | func TestShortcutPerFieldTrace(t *testing.T) { 207 | var called bool 208 | t0 := ShortcutPerFieldTrace{ 209 | OnFoo: func() { 210 | called = true 211 | }, 212 | } 213 | shortcutPerFieldTraceOnFoo(t0) 214 | if !called { 215 | t.Fatalf("hook wasn't called") 216 | } 217 | } 218 | 219 | func TestBuildTagTrace(t *testing.T) { 220 | t0 := BuildTagTrace{ 221 | OnSomethingA: func() func() { 222 | panic("must not be called") 223 | }, 224 | OnSomethingB: func(int8, int16) func(int32, int64) { 225 | panic("must not be called") 226 | }, 227 | OnSomethingC: func(Type) func(Type) { 228 | panic("must not be called") 229 | }, 230 | } 231 | t1 := BuildTagTrace{ 232 | OnSomethingA: func() func() { 233 | panic("must not be called") 234 | }, 235 | OnSomethingB: func(int8, int16) func(int32, int64) { 236 | panic("must not be called") 237 | }, 238 | OnSomethingC: func(Type) func(Type) { 239 | panic("must not be called") 240 | }, 241 | } 242 | trace := t0.Compose(t1) 243 | trace.onSomethingA(context.Background())() 244 | trace.onSomethingB(context.Background(), 1, 2)(3, 4) 245 | trace.onSomethingC(context.Background(), Type{})(Type{}) 246 | } 247 | 248 | func BenchmarkBuildTagTrace(b *testing.B) { 249 | x := BuildTagTrace{ 250 | OnSomethingA: func() func() { 251 | return func() { 252 | // 253 | } 254 | }, 255 | OnSomethingB: func(int8, int16) func(int32, int64) { 256 | return func(int32, int64) { 257 | // 258 | } 259 | }, 260 | OnSomethingC: func(Type) func(Type) { 261 | return func(Type) { 262 | // 263 | } 264 | }, 265 | } 266 | 267 | t := x.Compose(x).Compose(x).Compose(x) 268 | 269 | b.Run("OnSomethingA", func(b *testing.B) { 270 | for i := 0; i < b.N; i++ { 271 | t.onSomethingA(context.Background())() 272 | } 273 | }) 274 | b.Run("OnSomethingB", func(b *testing.B) { 275 | for i := 0; i < b.N; i++ { 276 | t.onSomethingB(context.Background(), 1, 2)(3, 4) 277 | } 278 | }) 279 | b.Run("OnSomethingC", func(b *testing.B) { 280 | for i := 0; i < b.N; i++ { 281 | t.onSomethingC(context.Background(), Type{})(Type{}) 282 | } 283 | }) 284 | } 285 | -------------------------------------------------------------------------------- /test/test_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package test 4 | 5 | //go:generate gtrace -v 6 | 7 | //gtrace:gen 8 | type ConditionalBuildTrace struct { 9 | OnSomething func() 10 | } 11 | -------------------------------------------------------------------------------- /test/types.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "bytes" 4 | 5 | type Embeded struct { 6 | } 7 | 8 | type Type struct { 9 | Embeded 10 | String string 11 | Integer int 12 | Boolean bool 13 | Error error 14 | Reader bytes.Reader 15 | } 16 | --------------------------------------------------------------------------------