Skip to main content

Custom instrumentation

Advanced feature

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

MethodDescriptionMain use cases
addCreate new file/functionAdd helper functions
injectInsert code inside functionAdd trace to all functions
replaceReplace function callsql.Openwhatapsql.Open
hookCode before/after function callInstrument legacy code
transformPattern 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.

Limitations
  • 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
}
Defer pattern recommended

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.

Note

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.

VariableDescriptionExample
{{.Original}}Matched original codegin.Default()
{{.Var}}Assigned variable namer (from r := ...)
{{.Args}}All function argumentspolicy, hosts...
{{.Arg0}}, {{.Arg1}}Individual argumentsFirst, second argument
{{.FuncName}}Function nameDefault
{{.PkgName}}Package namegin

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"