Go compile-time code instrumentation tool that uses -toolexec to intercept the Go compiler and apply AST transformations to standard library packages during build.
Currently ships with an net/http transformer that injects request duration logging into (*Client).Do.
- Go's
-toolexecflag lets you wrap every tool invocation (compile, link, asm, etc.) with a custom binary. - When the Go toolchain calls
compilefor a target package (e.g.net/http), the interceptor:- Parses the original source file using dave/dst (Decorated Syntax Tree).
- Loads a template with the replacement function body.
- Swaps the target function's body with the template version.
- Writes a modified
.gofile and passes it to the real compiler instead of the original.
- For all other tools (link, asm, etc.) the interceptor proxies the call unchanged.
- Cache busting is handled automatically — the interceptor modifies
-V=fulloutput sogo builddoesn't serve stale cached artifacts.
- Go 1.25+
- macOS arm64 (paths in Makefile assume Homebrew Go; adjust
COMPILERfor your platform)
make buildThis produces dist/interceptor.
Build any Go program with the interceptor as -toolexec:
go build -toolexec '/path/to/dist/interceptor' -o myapp ./cmd/myappOr use the provided Makefile targets with the included example app:
# build interceptor, then compile example/main.go with instrumentation
make build && make testThe test target runs:
go build -work -toolexec './dist/interceptor' -o dist/example example/main.goAfter building, run the instrumented binary:
./dist/exampleYou should see the injected duration output for every http.Client.Do call:
Hello, World!
duration 123.456ms
Response: 200
The local target lets you invoke the interceptor directly against a single compile command, useful for debugging transformations without a full build:
make build && make localThis simulates the compiler invocation for net/http using artifacts from testdata/.
make tests- Create a new file in
transform/(e.g.transform/dns.go). - Define a struct embedding
*transformerand implement theSupport(importPath string) boolmethod. - Create a template file in
transform/templates/with the replacement function body. - Register the transformer in an
init()function usingRegister(...).
Example:
package transform
import _ "embed"
//go:embed templates/dns.go.tpl
var templateDnsSourceCode string
func init() {
Register(&dnsTransformer{
transformer: &transformer{
SourceFile: "net/dns/lookup.go",
TemplateCode: templateDnsSourceCode,
TargetFunc: "LookupHost",
},
})
}
type dnsTransformer struct {
*transformer
}
func (t *dnsTransformer) Support(importPath string) bool {
return importPath == "net/dns"
}| Variable | Description |
|---|---|
TOOLEXEC_IMPORTPATH |
Set automatically by go build -toolexec; contains the import path of the package being compiled. |
WORK |
Build work directory; set automatically by go build -work or manually for make local. |
See LICENSE for details.