Custom instrumentation
This document is for advanced users. In most cases, Basic usage and Configuration guide are sufficient.
When the default instrumentation of whatap-go-inst is insufficient, you can add custom instrumentation rules.
5 instrumentation methods
| Method | Description | Main use cases |
|---|---|---|
| add | Create new file/function | Add helper functions |
| inject | Insert code inside function | Add trace to all functions |
| replace | Replace function call | sql.Open → whatapsql.Open |
| hook | Code before/after function call | Instrument legacy code |
| transform | Pattern transformation (advanced) | Complex transformations |
Execution order: add → inject → replace → hook → transform
Basic configuration structure
# .whatap/config.yaml
custom:
add: [] # Create new file/function
inject: [] # Insert code inside function definition
replace: [] # Replace function call
hook: [] # Insert code before/after function call
transform: [] # Code pattern -> template transformation
When to use?
Custom instrumentation is needed in the following cases:
- Add monitoring to internal common libraries
- Instrument unsupported frameworks
- Add monitoring to legacy code without direct modification
- Selectively instrument specific functions only
add - Create new file/function
Create helper functions or wrapper files. Can be used later in replace.
Basic usage
custom:
add:
- package: "myapp/helper" # Target package
file: "whatap_helper.go" # File to create
content: |
package helper
import "github.com/whatap/go-api/trace"
func WrapQuery(ctx context.Context, query string) string {
ctx = trace.StartMethod(ctx, "WrapQuery")
defer trace.EndMethod(ctx, nil)
return query
}
Transformation result
// Created file: myapp/helper/whatap_helper.go
package helper
import "github.com/whatap/go-api/trace"
func WrapQuery(ctx context.Context, query string) string {
ctx = trace.StartMethod(ctx, "WrapQuery")
defer trace.EndMethod(ctx, nil)
return query
}
More examples
Using template file
custom:
add:
- package: "myapp/db"
file: "whatap_db.go"
content_file: "templates/db-helper.go.tmpl" # External template file
Add function to existing file
custom:
add:
- package: "myapp/service"
file: "service.go" # Existing file
append: true # Append to end of file
content: |
func traceMethod(ctx context.Context, name string) (context.Context, func()) {
return trace.StartMethod(ctx, name), func() { trace.EndMethod(ctx, nil) }
}
add + replace combination
custom:
# Step 1: Create helper function
add:
- package: "myapp/db"
file: "whatap_wrapper.go"
content: |
package db
func TracedQuery(ctx context.Context, sql string) (*Result, error) {
ctx = trace.StartMethod(ctx, "db.query")
defer trace.EndMethod(ctx, nil)
return OriginalQuery(sql)
}
# Step 2: Replace original function call with helper
replace:
- package: "myapp/db"
function: "Query"
with: "TracedQuery"
inject - Insert code inside function definition
Insert code at the start/end of function body.
- Can only target user-defined functions in the current module
- Cannot target Go standard library or external package functions
Basic usage
custom:
inject:
- package: "myapp/service" # Go import path
function: "*" # Function name (* = all)
start: |
ctx = trace.StartMethod(ctx)
defer trace.EndMethod(ctx, err)
imports:
- "github.com/whatap/go-api/trace"
Transformation result
// Before
func ProcessOrder(ctx context.Context) error {
// Business logic
}
// After
func ProcessOrder(ctx context.Context) error {
ctx = trace.StartMethod(ctx) // <- start inserted
defer trace.EndMethod(ctx, err) // <- start inserted
// Business logic
}
If there are returns in the middle of the function, use the defer pattern in start instead of end.
More examples
Target specific function only
custom:
inject:
- package: "myapp/service"
function: "ProcessOrder" # Specific function name
start: |
ctx = trace.StartMethod(ctx, "ProcessOrder")
defer trace.EndMethod(ctx, nil)
imports:
- "github.com/whatap/go-api/trace"
replace - Replace function call
Replace function calls with another function.
Basic usage
custom:
replace:
- package: "database/sql"
function: "Open"
with: "whatapsql.Open"
imports:
- "github.com/whatap/go-api/instrumentation/database/sql/whatapsql"
Transformation result
// Before
db, err := sql.Open("mysql", dsn)
// After
db, err := whatapsql.Open("mysql", dsn)
More examples
Replace multiple functions
custom:
replace:
- package: "database/sql"
function: "Open"
with: "whatapsql.Open"
imports:
- "github.com/whatap/go-api/instrumentation/database/sql/whatapsql"
- package: "github.com/jmoiron/sqlx"
function: "Connect"
with: "whatapsqlx.Connect"
imports:
- "github.com/whatap/go-api/instrumentation/github.com/jmoiron/sqlx/whatapsqlx"
hook - Insert code before and after function call
Insert code before and after function calls.
You can use only before or only after.
Basic usage
custom:
hook:
- package: "mycompany/mydb"
function: "Query"
before: "ctx, span := trace.Start(ctx, \"db.query\")"
after: "span.End()"
imports:
- "github.com/whatap/go-api/trace"
Transformation result
// Before
result, err := mydb.Query(sql)
// After
ctx, span := trace.Start(ctx, "db.query") // <- before inserted
result, err := mydb.Query(sql)
span.End() // <- after inserted
More examples
Using before only
custom:
hook:
- package: "mycompany/cache"
function: "Get"
before: "log.Printf(\"cache.Get called: %s\", key)"
imports:
- "log"
transform - Transform code pattern
Transform complex code patterns with templates. The most flexible method.
Basic usage: Add middleware
custom:
transform:
- package: "github.com/gin-gonic/gin"
function: "Default"
template: |
{{.Original}}
{{.Var}}.Use(whatapgin.Middleware())
imports:
- "github.com/whatap/go-api/instrumentation/github.com/gin-gonic/gin/whatapgin"
Transformation result:
// Before
r := gin.Default()
// After
r := gin.Default()
r.Use(whatapgin.Middleware())
Advanced usage: Wrap with closure
custom:
transform:
- package: "github.com/aerospike/aerospike-client-go"
function: "NewClient"
template: |
func() (*as.Client, error) {
ctx, done := whatapsql.Start(context.Background(), "aerospike")
defer done()
return {{.Original}}
}()
imports:
- "context"
- "github.com/whatap/go-api/instrumentation/database/sql/whatapsql"
Transformation result:
// Before
client, err := as.NewClient(policy, hosts...)
// After
client, err := func() (*as.Client, error) {
ctx, done := whatapsql.Start(context.Background(), "aerospike")
defer done()
return as.NewClient(policy, hosts...)
}()
More examples
HTTP client wrapping
custom:
transform:
- package: "net/http"
function: "Get"
template: |
func() (*http.Response, error) {
ctx, done := httpc.Start(context.Background(), {{.Arg0}})
defer done()
return {{.Original}}
}()
imports:
- "context"
- "github.com/whatap/go-api/httpc"
Template variables
Template variables available in transform.
| Variable | Description | Example |
|---|---|---|
{{.Original}} | Matched original code | gin.Default() |
{{.Var}} | Assigned variable name | r (from r := ...) |
{{.Args}} | All function arguments | policy, hosts... |
{{.Arg0}}, {{.Arg1}} | Individual arguments | First, second argument |
{{.FuncName}} | Function name | Default |
{{.PkgName}} | Package name | gin |
Usage example
custom:
transform:
- package: "mycompany/db"
function: "Query"
template: |
func() (*Result, error) {
ctx := trace.StartMethod(context.Background(), "{{.PkgName}}.{{.FuncName}}")
defer trace.EndMethod(ctx, nil)
return {{.Original}}
}()
Transformation result:
// Before
result, err := db.Query("SELECT * FROM users")
// After
result, err := func() (*Result, error) {
ctx := trace.StartMethod(context.Background(), "db.Query")
defer trace.EndMethod(ctx, nil)
return db.Query("SELECT * FROM users")
}()
Practical examples
Example 1: Instrument internal library
Add monitoring to internal common libraries.
# .whatap/config.yaml
custom:
# Add tracing to all service functions
inject:
- package: "mycompany/service"
function: "*"
start: |
ctx, span := trace.Start(ctx, "service")
defer span.End()
imports:
- "github.com/whatap/go-api/trace"
# Replace internal DB library
replace:
- package: "mycompany/db"
function: "Connect"
with: "whatapsql.Open"
imports:
- "github.com/whatap/go-api/instrumentation/database/sql/whatapsql"
Example 2: Instrument legacy code
Add monitoring to legacy code that's difficult to modify directly.
custom:
# Add tracing to legacy HTTP client calls
hook:
- package: "legacy/httpclient"
function: "Do"
before: |
ctx, span := httpc.Start(ctx, req.URL.String())
after: |
span.End()
imports:
- "github.com/whatap/go-api/httpc"
# Log legacy cache calls
hook:
- package: "legacy/cache"
function: "Get"
before: |
startTime := time.Now()
after: |
trace.Step(ctx, "cache.Get", time.Since(startTime).Milliseconds(), nil)
imports:
- "time"
- "github.com/whatap/go-api/trace"
Example 3: Custom middleware
Automatically add custom middleware.
custom:
# Create helper middleware
add:
- package: "myapp/middleware"
file: "whatap_middleware.go"
content: |
package middleware
import (
"github.com/gin-gonic/gin"
"github.com/whatap/go-api/trace"
)
func CustomTracing() gin.HandlerFunc {
return func(c *gin.Context) {
ctx := trace.Start(c.Request.Context(), c.Request.URL.Path)
c.Request = c.Request.WithContext(ctx)
defer trace.End(ctx, nil)
c.Next()
}
}
# Add custom middleware to Gin router
transform:
- package: "github.com/gin-gonic/gin"
function: "Default"
template: |
{{.Original}}
{{.Var}}.Use(middleware.CustomTracing())
imports:
- "myapp/middleware"
Example 4: Conditional instrumentation
Activate instrumentation only under specific conditions.
custom:
transform:
- package: "mycompany/db"
function: "Query"
template: |
func() (*Result, error) {
if os.Getenv("WHATAP_ENABLED") == "true" {
ctx, done := whatapsql.Start(context.Background(), "db.query")
defer done()
}
return {{.Original}}
}()
imports:
- "os"
- "context"
- "github.com/whatap/go-api/instrumentation/database/sql/whatapsql"