-
Notifications
You must be signed in to change notification settings - Fork 24
Description
This proposal suggests replacing the current YAML-based instrumentation rule definitions with a Go-based internal DSL. This would provide greater flexibility, type safety, and enable advanced use cases that are difficult or impossible to express in YAML.
Motivation
The current YAML approach works well for simple declarative scenarios:
server_hook:
target: net/http
func: ServeHTTP
recv: serverHandler
before: BeforeServeHTTP
after: AfterServeHTTP
path: "github.com/.../nethttp/server"However, YAML becomes limiting when more sophisticated rewriting logic is required:
- No programmatic control: Cannot express conditional logic or dynamic transformations
- Limited AST manipulation: The raw field allows code injection but cannot modify function signatures, rename parameters, or perform context-sensitive transformations
- No type safety: YAML is stringly-typed, errors are only caught at runtime
- Duplication: Similar rules require copy-paste with minor variations
Proposed Solution
Define instrumentation rules as plain Go code using an internal DSL:
package nethttp_hooks
import "github.com/open-telemetry/opentelemetry-go-compile-instrumentation/hooks"
func ProvideHooks() []*hooks.Hook {
return []*hooks.Hook{
{
Target: hooks.InjectTarget{
Package: "net/http",
Function: "ServeHTTP",
Receiver: "serverHandler",
},
Hooks: &hooks.InjectFunctions{
Before: "BeforeServeHTTP",
After: "AfterServeHTTP",
From: "github.com/.../nethttp/server",
},
},
}
}For advanced cases like runtime instrumentation, the Go DSL enables full AST manipulation:
func (r *RuntimeHookProvider) ProvideHooks() []*hooks.Hook {
return []*hooks.Hook{
{
Target: hooks.InjectTarget{
Package: "runtime",
Function: "newproc1",
},
Rewrite: func(node ast.Node) (ast.Node, error) {
funcDecl := node.(*ast.FuncDecl)
// Rename unnamed return values
renameReturnValues(funcDecl)
// Inject context propagation
stmts, _ := parseSnippet(`defer func(){...}()`)
funcDecl.Body.List = append(stmts, funcDecl.Body.List...)
return funcDecl, nil
},
},
}
}
func (r *RuntimeHookProvider) GetStructModifications() []hooks.StructModification {
return []hooks.StructModification{
{
Package: "runtime",
StructName: "g",
AddFields: []hooks.StructField{
{Name: "otel_trace_context", Type: "interface{}"},
{Name: "otel_baggage_container", Type: "interface{}"},
},
},
}
}Benefits
| Aspect | YAML | Go DSL |
|---|---|---|
| Type safety | Runtime errors | Compile-time checks |
| Expressiveness | Limited to predefined fields | Full language power |
| AST manipulation | Only raw code injection | Complete control via go/ast |
| Conditional logic | Not supported | Native Go conditionals |
| Code reuse | Copy-paste | Functions, interfaces, composition |
| IDE support | Basic YAML highlighting | Full Go tooling (autocomplete, refactoring) |
| Testing | Difficult | Standard Go tests |
Backwards Compatibility
YAML support could be maintained as a convenience layer that generates Go DSL internally. Simple rules would continue to work in YAML, while advanced use cases would use Go directly.
Reference Implementation
A working implementation of this approach exists in https://github.com/pdelewski/go-build-interceptor, demonstrating:
- Before/After hooks
- Function rewriting with full AST access
- Struct modification
- File generation
Next Steps
- Define the hooks package API
- Implement hook provider parsing/loading
- Add YAML-to-Go DSL converter for migration
- Update existing instrumentations to use Go DSL