tools/go-agent/instrument/plugins/enhance_config.go (204 lines of code) (raw):

// Licensed to Apache Software Foundation (ASF) under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Apache Software Foundation (ASF) licenses this file to you 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 plugins import ( "fmt" "go/token" "reflect" "strings" "unicode" "github.com/apache/skywalking-go/plugins/core/instrument" "github.com/apache/skywalking-go/tools/go-agent/config" "github.com/apache/skywalking-go/tools/go-agent/instrument/consts" "github.com/apache/skywalking-go/tools/go-agent/tools" "github.com/dave/dst" ) var ( configFieldKeyTag = "config" toolsImports = "github.com/apache/skywalking-go/plugins/core/tools" ) type ConfigEnhance struct { VarSpec *dst.ValueSpec ConfigPrefix string VarName string Fields []*ConfigField GenerateConfigFunName string } func NewConfigEnhance(varSpec *dst.ValueSpec, node dst.Node, inst instrument.Instrument) (*ConfigEnhance, error) { configDirective := tools.FindDirective(node, consts.DirectiveConfig) if configDirective == "" { return nil, fmt.Errorf("cannot find the config directive") } enhance := &ConfigEnhance{VarSpec: varSpec} info := strings.SplitN(configDirective, " ", 2) if len(info) == 2 { enhance.ConfigPrefix = info[1] } else { // default using the plugin name as the prefix enhance.ConfigPrefix = inst.Name() } enhance.VarName = varSpec.Names[0].Name varType, ok := varSpec.Type.(*dst.StructType) if !ok { return nil, fmt.Errorf("the config type of %s under %s plugin must be a structure", varSpec.Names[0].Name, inst.Name()) } fs, err := NewConfigFields(varType) if err != nil { return nil, fmt.Errorf("analyzing the config %s under %s plugin failed: %v", varSpec.Names[0].Name, inst.Name(), err) } enhance.Fields = fs enhance.GenerateConfigFunName = fmt.Sprintf("initConfig%s", enhance.VarName) return enhance, nil } func (e *ConfigEnhance) PackageName() string { return "" } func (e *ConfigEnhance) BuildImports(decl *dst.GenDecl) { for _, spec := range decl.Specs { imp, ok := spec.(*dst.ImportSpec) if !ok { continue } if imp.Path.Value == fmt.Sprintf("%q", toolsImports) { return } } decl.Specs = append(decl.Specs, &dst.ImportSpec{ Path: &dst.BasicLit{ Kind: token.STRING, Value: fmt.Sprintf("%q", toolsImports), }, }) } func (e *ConfigEnhance) BuildForDelegator() []dst.Decl { fun := &dst.FuncDecl{ Name: dst.NewIdent(e.GenerateConfigFunName), Type: &dst.FuncType{}, Body: &dst.BlockStmt{}, } for _, field := range e.Fields { fun.Body.List = append(fun.Body.List, field.GenerateAssignFieldValue(e.VarName, []string{}, []string{e.ConfigPrefix})...) } return []dst.Decl{fun} } func (e *ConfigEnhance) ReplaceFileContent(path, content string) string { return content } func (e *ConfigEnhance) InitFunctions() []*EnhanceInitFunction { return []*EnhanceInitFunction{NewEnhanceInitFunction(e.GenerateConfigFunName, true)} } type ConfigField struct { Name string Type string Key string ChildFields []*ConfigField } func NewConfigFields(structType *dst.StructType) ([]*ConfigField, error) { if structType.Fields == nil || len(structType.Fields.List) == 0 { return nil, fmt.Errorf("the config structure must have at least one field") } fields := make([]*ConfigField, 0, len(structType.Fields.List)) for _, field := range structType.Fields.List { configField, err := NewConfigField(field) if err != nil { return nil, err } fields = append(fields, configField) } return fields, nil } func NewConfigField(f *dst.Field) (*ConfigField, error) { if len(f.Names) == 0 { return nil, fmt.Errorf("the config structure must have named field") } conf := &ConfigField{ Name: f.Names[0].Name, } switch t := f.Type.(type) { case *dst.Ident: conf.Type = t.Name case *dst.StructType: fs, err := NewConfigFields(t) if err != nil { return nil, err } conf.ChildFields = fs default: return nil, fmt.Errorf("the config structure field %s type %T is not supported", conf.Name, t) } conf.initFlags(f.Tag) return conf, nil } func (f *ConfigField) initFlags(flagLit *dst.BasicLit) { if flagLit == nil { f.Key = f.generateDefaultKey(f.Name) return } tag := reflect.StructTag(flagLit.Value) value, ok := tag.Lookup(configFieldKeyTag) if ok { f.Key = value return } f.Key = f.generateDefaultKey(f.Name) } func (f *ConfigField) generateDefaultKey(keyName string) string { var result []rune for i, r := range keyName { if unicode.IsUpper(r) { if i != 0 { result = append(result, '_') } result = append(result, []rune(strings.ToLower(string(r)))...) } else { result = append(result, r) } } return string(result) } func (f *ConfigField) GenerateAssignFieldValue(varName string, field, path []string) []dst.Stmt { field = append(field, f.Name) path = append(path, f.Key) if len(f.ChildFields) > 0 { result := make([]dst.Stmt, 0) for _, child := range f.ChildFields { result = append(result, child.GenerateAssignFieldValue(varName, field, path)...) } return result } fieldKeyPathStr := strings.Join(path, ".") fieldPathStr := strings.Join(field, ".") pluginConfig := config.GetConfig().Plugin.Config.ParseToStringValue(path...) if pluginConfig == nil { panic(fmt.Errorf("cannot find the config %s", fieldKeyPathStr)) } resultType := f.Type getFromEnvStr := "" if pluginConfig.EnvKey != "" { getFromEnvStr = fmt.Sprintf("if v := tools.GetEnvValue(%q); v != \"\" { result = v };", pluginConfig.EnvKey) } parseResStr := "" parseErrorMessage := "cannot parse the config " + fieldKeyPathStr + ": err.Error()" switch f.Type { case "string": parseResStr = "return result" case "bool": parseResStr = "return tools.ParseBool(result)" case "int": parseResStr = "if v, err := tools.Atoi(result); err != nil { panic(" + parseErrorMessage + ") } else { return v }" case "int16": parseResStr = "if v, err := tools.ParseInt(result, 10, 16); err != nil { panic(" + parseErrorMessage + ") } else { return v }" case "int32": parseResStr = "if v, err := tools.ParseInt(result, 10, 32); err != nil { panic(" + parseErrorMessage + ") } else { return v }" case "int64": parseResStr = "if v, err := tools.ParseInt(result, 10, 64); err != nil { panic(" + parseErrorMessage + ") } else { return v }" case "float32": parseResStr = "if v, err := tools.ParseFloat(result, 32); err != nil { panic(" + parseErrorMessage + ") } else { return v }" case "float64": parseResStr = "if v, err := tools.ParseFloat(result, 64); err != nil { panic(" + parseErrorMessage + ") } else { return v }" case "float": parseResStr = "if v, err := tools.ParseFloat(result, 64); err != nil { panic(" + parseErrorMessage + ") } else { return v }" default: panic("unsupported config type " + f.Type) } stmtStr := fmt.Sprintf("%s.%s = func () %s { result := %q; %s%s }()", varName, fieldPathStr, resultType, pluginConfig.Default, getFromEnvStr, parseResStr) return tools.GoStringToStats(stmtStr) }