tools/go-agent/instrument/logger/instrument.go (295 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 logger import ( // for file import _ "embed" "fmt" "go/token" "os" "path/filepath" "strings" "github.com/apache/skywalking-go/tools/go-agent/config" "github.com/apache/skywalking-go/tools/go-agent/instrument/api" "github.com/apache/skywalking-go/tools/go-agent/instrument/consts" "github.com/apache/skywalking-go/tools/go-agent/instrument/logger/frameworks" "github.com/apache/skywalking-go/tools/go-agent/instrument/plugins/rewrite" "github.com/apache/skywalking-go/tools/go-agent/tools" "github.com/dave/dst" "github.com/dave/dst/dstutil" "golang.org/x/text/cases" "golang.org/x/text/language" ) var logFrameworks = []frameworks.LogFramework{ frameworks.NewLogrus(), frameworks.NewZap(), } //go:embed context.go var contextFile string type Instrument struct { compileOpts *api.CompileOptions framework frameworks.LogFramework packageConf *frameworks.PackageConfiguration automaticFunc []*AutomaticFunctionInfo customizedReplace map[string]map[string]string } func NewInstrument() *Instrument { return &Instrument{ customizedReplace: make(map[string]map[string]string), } } func (i *Instrument) CouldHandle(opts *api.CompileOptions) bool { for _, f := range logFrameworks { for p, conf := range f.PackagePaths() { if p == opts.Package { i.compileOpts = opts i.framework = f i.packageConf = conf return true } } } return false } func (i *Instrument) FilterAndEdit(path string, curFile *dst.File, cursor *dstutil.Cursor, allFiles []*dst.File) bool { // add to invoke check, if the method should add automatic bind if fun, ok := cursor.Node().(*dst.FuncDecl); ok { if body := i.framework.AutomaticBindFunctions(fun); body != "" { i.addAutomaticBindFunc(path, curFile, fun, body) return true } } replacements, needs := i.framework.CustomizedEnhance(path, curFile, cursor, allFiles) // adding replacements if needs if needs && len(replacements) > 0 { r := i.customizedReplace[path] if r == nil { r = make(map[string]string) i.customizedReplace[path] = r } for k, v := range replacements { r[k] = v } } return needs } func (i *Instrument) addAutomaticBindFunc(path string, curFile dst.Node, fun *dst.FuncDecl, body string) { automaticBind, err := frameworks.FrameworkFS.ReadFile("templates/automatic_bind.tmpl") if err != nil { panic(err) } funcID := tools.BuildFuncIdentity(i.compileOpts.Package, fun) var generateFuncName = fmt.Sprintf("%sautomaticLoggerBind%s", rewrite.GenerateMethodPrefix, funcID) var replaceName = fmt.Sprintf("//goagent:bind_%s\n", funcID) importAnalyzer := tools.CreateImportAnalyzer() importAnalyzer.AnalyzeFileImports(path, curFile) funcInvoker := tools.ExecuteTemplate(string(automaticBind), struct { AutomaticBindFuncName string Recvs []*tools.ParameterInfo Parameters []*tools.ParameterInfo Results []*tools.ParameterInfo }{ AutomaticBindFuncName: generateFuncName, Recvs: tools.EnhanceParameterNames(fun.Recv, tools.FieldListTypeRecv), Parameters: tools.EnhanceParameterNames(fun.Type.Params, tools.FieldListTypeParam), Results: tools.EnhanceParameterNames(fun.Type.Results, tools.FieldListTypeResult), }) importAnalyzer.AnalyzeNeedsImports(path, fun.Recv) importAnalyzer.AnalyzeNeedsImports(path, fun.Type.Params) importAnalyzer.AnalyzeNeedsImports(path, fun.Type.Results) i.automaticFunc = append(i.automaticFunc, &AutomaticFunctionInfo{ Path: path, Func: fun, FuncName: generateFuncName, RealInvoker: funcInvoker, ReplaceName: replaceName, DelegateBody: body, ImportAnalyzer: importAnalyzer, }) fun.Body.Decs.Lbrace.Prepend("\n", replaceName) } func (i *Instrument) AfterEnhanceFile(fromPath, newPath string) error { contentBytes, err := os.ReadFile(newPath) if err != nil { return err } // update the file content if needed content := string(contentBytes) var oldContent = content for _, enhance := range i.automaticFunc { if enhance.Path == fromPath { content = strings.Replace(content, enhance.ReplaceName, enhance.RealInvoker, 1) } } if replacements, existing := i.customizedReplace[fromPath]; existing { for k, v := range replacements { content = strings.Replace(content, k, v, 1) } } if oldContent == content { return nil } return os.WriteFile(newPath, []byte(content), 0o600) } func (i *Instrument) WriteExtraFiles(dir string) ([]string, error) { packageName := filepath.Base(i.compileOpts.Package) var contextRewriteFile *rewrite.FileInfo if i.compileOpts.DebugDir == "" { contextRewriteFile = rewrite.NewFile(packageName, "context.go", contextFile) } else { contextRewriteFile = rewrite.NewFileWithDebug(packageName, "context.go", contextFile, filepath.Join(i.compileOpts.DebugDir, "tools", "go-agent", "instrument", "logger")) } delegatorContent, err := i.writeDelegatorFile(packageName) if err != nil { return nil, err } delegatorFile := rewrite.NewFile(packageName, "delegator.go", delegatorContent) ctx := rewrite.NewContext(i.compileOpts.Package, packageName) files := []*rewrite.FileInfo{ contextRewriteFile, delegatorFile, } generateExtraFiles, err := i.framework.GenerateExtraFiles(i.compileOpts.Package, i.compileOpts.DebugDir) if err != nil { return nil, err } files = append(files, generateExtraFiles...) if i.packageConf.NeedsHelpers { files = append(files, rewrite.NewFile(packageName, "skywalking_init.go", i.generateInitLoggerFileContent(packageName))) } extraFiles, err := ctx.MultipleFilesWithWritten("skywalking_", dir, packageName, files) if err != nil { return nil, err } return extraFiles, nil } func (i *Instrument) writeDelegatorFile(pkgName string) (string, error) { importDecl := &dst.GenDecl{ Tok: token.IMPORT, Specs: []dst.Spec{ &dst.ImportSpec{Name: dst.NewIdent("_"), Path: &dst.BasicLit{ Kind: token.STRING, Value: fmt.Sprintf("%q", "unsafe"), }}, &dst.ImportSpec{Path: &dst.BasicLit{ Kind: token.STRING, Value: fmt.Sprintf("%q", i.compileOpts.Package), }}, }, } delegator := &dst.File{ Name: dst.NewIdent(pkgName), Decls: []dst.Decl{ importDecl, }, } // add automatic function delegators i.addAutomaticFuncDelegators(delegator, importDecl) return tools.GenerateDSTFileContent(delegator, nil) } func (i *Instrument) generateInitLoggerFileContent(pkgName string) string { initTmpl, err := frameworks.FrameworkFS.ReadFile("templates/init.tmpl") if err != nil { panic(fmt.Sprintf("cannot found init template in logger framerwork: %v", err)) } initFunctions := i.framework.InitFunctions() initFuncNames := make([]string, 0) for _, initFunc := range initFunctions { initFuncNames = append(initFuncNames, initFunc.Name.Name) } importsMap := make(map[string]string) for _, imp := range i.framework.InitImports() { name := filepath.Base(strings.TrimSuffix(imp.Path.Value, "\"")) if imp.Name != nil && imp.Name.Name != "" { name = imp.Name.Name } importsMap[name] = imp.Path.Value } initDecls := tools.GoStringToDecls(tools.ExecuteTemplate(string(initTmpl), struct { Imports map[string]string NeedsVariables bool NeedsChangeLoggerFunc bool GetGlobalOperatorLinkMethod string SetGlobalLoggerLinkMethod string OperatorTypeName string LogTypeInConfig *config.Log ConfigTypeAutomaticValue string CurrentLogTypeName string GetOperatorMethodName string ChangeLoggerMethodName string LogTracingEnableVarName string LogTracingContextKeyVarName string LogReporterEnableVarName string LogReporterLabelsVarName string LogReportFuncName string InitFunctionNames []string }{ Imports: importsMap, NeedsVariables: i.packageConf.NeedsVariables, NeedsChangeLoggerFunc: i.packageConf.NeedsChangeLoggerFunc, GetGlobalOperatorLinkMethod: consts.GlobalTracerGetMethodName, SetGlobalLoggerLinkMethod: consts.GlobalLoggerSetMethodName, OperatorTypeName: "Operator", LogTypeInConfig: &config.GetConfig().Log, ConfigTypeAutomaticValue: config.ConfigTypeAutomatic, CurrentLogTypeName: i.framework.Name(), GetOperatorMethodName: "GetOperator", ChangeLoggerMethodName: "ChangeLogger", LogTracingEnableVarName: "LogTracingContextEnable", LogTracingContextKeyVarName: "LogTracingContextKey", LogReporterEnableVarName: "LogReporterEnable", LogReporterLabelsVarName: "LogReporterLabelKeys", LogReportFuncName: "ReportLog", InitFunctionNames: initFuncNames, })) for _, f := range initFunctions { initDecls = append(initDecls, f) } f := &dst.File{Name: dst.NewIdent(pkgName), Decls: initDecls} if c, err1 := tools.GenerateDSTFileContent(f, nil); err1 != nil { panic(fmt.Errorf("generate logger init file error: %v", err)) } else { return c } } func (i *Instrument) generatePackageNameWithTitle() string { return cases.Title(language.English).String(filepath.Base(i.compileOpts.Package)) } func (i *Instrument) addAutomaticFuncDelegators(f *dst.File, importDecl *dst.GenDecl) { packageName := filepath.Base(i.compileOpts.Package) for _, fun := range i.automaticFunc { fun.ImportAnalyzer.AppendUsedImports(importDecl) delegatorFunc := &dst.FuncDecl{ Name: dst.NewIdent(fun.FuncName), Type: &dst.FuncType{ Params: &dst.FieldList{}, }, } for i, recv := range tools.EnhanceParameterNamesWithPackagePrefix(packageName, fun.Func.Recv, tools.FieldListTypeRecv) { delegatorFunc.Type.Params.List = append(delegatorFunc.Type.Params.List, &dst.Field{ Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("recv_%d", i))}, Type: &dst.StarExpr{X: recv.PackagedType()}, }) } for i, parameter := range tools.EnhanceParameterNamesWithPackagePrefix(packageName, fun.Func.Type.Params, tools.FieldListTypeParam) { packagedType := parameter.PackagedType() // if the parameter is dynamic list, then change it to the array type if el, ok := packagedType.(*dst.Ellipsis); ok { packagedType = &dst.ArrayType{Elt: el.Elt} } delegatorFunc.Type.Params.List = append(delegatorFunc.Type.Params.List, &dst.Field{ Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("param_%d", i))}, Type: &dst.StarExpr{X: packagedType}, }) } for i, result := range tools.EnhanceParameterNamesWithPackagePrefix(packageName, fun.Func.Type.Results, tools.FieldListTypeResult) { delegatorFunc.Type.Params.List = append(delegatorFunc.Type.Params.List, &dst.Field{ Names: []*dst.Ident{dst.NewIdent(fmt.Sprintf("ret_%d", i))}, Type: &dst.StarExpr{X: result.PackagedType()}, }) } delegatorFunc.Body = &dst.BlockStmt{ List: tools.GoStringToStats(fun.DelegateBody), } f.Decls = append(f.Decls, delegatorFunc) } } type AutomaticFunctionInfo struct { Path string Func *dst.FuncDecl FuncName string RealInvoker string ReplaceName string DelegateBody string ImportAnalyzer *tools.ImportAnalyzer }