tool/resource/ruledef.go (140 lines of code) (raw):
// Copyright (c) 2024 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package resource
import (
"encoding/json"
"strings"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/errc"
"github.com/alibaba/opentelemetry-go-auto-instrumentation/tool/util"
)
// -----------------------------------------------------------------------------
// Instrumentation Rule
//
// Instrumentation rules are used to define the behavior of the instrumentation
// for a specific function call. The rules are defined in the init() function
// of rule.go in each package directory. The rules are then used by the instrument
// package to generate the instrumentation code. Multiple rules can be defined
// for a single function call, and the rules are executed in the order of their
// priority. The rules are executed
// in the order of their priority, from high to low.
// There are several types of rules for different purposes:
// - InstFuncRule: Instrumentation rule for a specific function call
// - InstStructRule: Instrumentation rule for a specific struct type
// - InstFileRule: Instrumentation rule for a specific file
type InstRule interface {
GetVersion() string // GetVersion returns the version of the rule
GetGoVersion() string // GetGoVersion returns the go version of the rule
GetImportPath() string // GetImportPath returns import path of the rule
GetPath() string // GetPath returns the local path of the rule
SetPath(path string) // SetPath sets the local path of the rule
String() string // String returns string representation of rule
Verify() error // Verify checks the rule is valid
}
type InstBaseRule struct {
// Local path of the rule, it desginates where we can found the hook code
Path string `json:"Path,omitempty"`
// Version of the rule, e.g. "[1.9.1,1.9.2)" or "", it desginates the
// version range of rule, all other version will not be instrumented
Version string `json:"Version,omitempty"`
// Go version of the rule, e.g. "[1.22.0,)" or "", it desginates the go
// version range of rule, all other go version will not be instrumented
GoVersion string `json:"GoVersion,omitempty"`
// Import path of the rule, e.g. "github.com/gin-gonic/gin", it desginates
// the import path of rule, all other import path will not be instrumented
ImportPath string `json:"ImportPath,omitempty"`
}
func (rule *InstBaseRule) GetVersion() string {
return rule.Version
}
func (rule *InstBaseRule) GetGoVersion() string {
return rule.GoVersion
}
func (rule *InstBaseRule) GetImportPath() string {
return rule.ImportPath
}
func (rule *InstBaseRule) GetPath() string {
return rule.Path
}
func (rule *InstBaseRule) SetPath(path string) {
rule.Path = path
}
// InstFuncRule finds specific function call and instrument by adding new code
type InstFuncRule struct {
InstBaseRule
// Function name, e.g. "New"
Function string `json:"Function,omitempty"`
// Receiver type name, e.g. "*gin.Engine"
ReceiverType string `json:"ReceiverType,omitempty"`
// Order of the rule, higher is executed first
Order int `json:"Order,omitempty"`
// UseRaw indicates whether to insert raw code string
UseRaw bool `json:"UseRaw,omitempty"`
// OnEnter callback, called before original function
OnEnter string `json:"OnEnter,omitempty"`
// OnExit callback, called after original function
OnExit string `json:"OnExit,omitempty"`
}
// InstStructRule finds specific struct type and instrument by adding new field
type InstStructRule struct {
InstBaseRule
// Struct type name, e.g. "Engine"
StructType string `json:"StructType,omitempty"`
// New field name, e.g. "Logger"
FieldName string `json:"FieldName,omitempty"`
// New field type, e.g. "zap.Logger"
FieldType string `json:"FieldType,omitempty"`
}
// InstFileRule adds user file into compilation unit and do further compilation
type InstFileRule struct {
InstBaseRule
// File name, e.g. "engine.go"
FileName string `json:"FileName,omitempty"`
// Replace indicates whether to replace the original file
Replace bool `json:"Replace,omitempty"`
}
// String returns string representation of the rule
func (rule *InstFuncRule) String() string {
bs, _ := json.Marshal(rule)
return string(bs)
}
func (rule *InstStructRule) String() string {
bs, _ := json.Marshal(rule)
return string(bs)
}
func (rule *InstFileRule) String() string {
bs, _ := json.Marshal(rule)
return string(bs)
}
// Verify checks the rule is valid
func verifyRule(rule *InstBaseRule, checkPath bool) error {
if checkPath {
if rule.Path == "" {
return errc.New(errc.ErrInvalidRule, "local path is empty")
}
}
// Import path should not be empty
if rule.ImportPath == "" {
return errc.New(errc.ErrInvalidRule, "import path is empty")
}
// If version is specified, it should be in the format of [start,end)
for _, v := range []string{rule.Version, rule.GoVersion} {
if v != "" {
if !strings.Contains(v, "[") ||
!strings.Contains(v, ")") ||
!strings.Contains(v, ",") ||
strings.Contains(v, "v") {
return errc.New(errc.ErrInvalidRule, "bad version "+v)
}
}
}
return nil
}
func verifyRuleBase(rule *InstBaseRule) error {
return verifyRule(rule, false)
}
func verifyRuleBaseWithoutPath(rule *InstBaseRule) error {
return verifyRule(rule, true)
}
func (rule *InstFileRule) Verify() error {
err := verifyRuleBase(&rule.InstBaseRule)
if err != nil {
return err
}
if rule.FileName == "" {
return errc.New(errc.ErrInvalidRule, "empty file name")
}
if !util.IsGoFile(rule.FileName) {
return errc.New(errc.ErrInvalidRule, "not a go file")
}
return nil
}
func (rule *InstFuncRule) Verify() error {
var err error
if rule.UseRaw {
err = verifyRuleBaseWithoutPath(&rule.InstBaseRule)
} else {
err = verifyRuleBase(&rule.InstBaseRule)
}
if err != nil {
return err
}
if rule.Function == "" {
return errc.New(errc.ErrInvalidRule, "empty function name")
}
if rule.OnEnter == "" && rule.OnExit == "" {
return errc.New(errc.ErrInvalidRule, "empty hook")
}
return nil
}
func (rule *InstStructRule) Verify() error {
err := verifyRuleBaseWithoutPath(&rule.InstBaseRule)
if err != nil {
return err
}
if rule.StructType == "" {
return errc.New(errc.ErrInvalidRule, "empty struct type")
}
if rule.FieldName == "" || rule.FieldType == "" {
return errc.New(errc.ErrInvalidRule, "empty field name or type")
}
return nil
}