blob: e0b8a0300901f20f82bae395145190670690b674 [file] [log] [blame]
// 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 instrument
import (
"go/parser"
"os"
"path/filepath"
"strings"
"github.com/dave/dst"
"github.com/dave/dst/decorator"
"github.com/dave/dst/dstutil"
"github.com/sirupsen/logrus"
"github.com/apache/skywalking-go/tools/go-agent/instrument/agentcore"
"github.com/apache/skywalking-go/tools/go-agent/instrument/api"
"github.com/apache/skywalking-go/tools/go-agent/instrument/entry"
"github.com/apache/skywalking-go/tools/go-agent/instrument/logger"
"github.com/apache/skywalking-go/tools/go-agent/instrument/plugins"
"github.com/apache/skywalking-go/tools/go-agent/instrument/reporter"
"github.com/apache/skywalking-go/tools/go-agent/instrument/runtime"
"github.com/apache/skywalking-go/tools/go-agent/tools"
)
var instruments = []api.Instrument{
runtime.NewInstrument(),
agentcore.NewInstrument(),
reporter.NewGRPCInstrument(),
entry.NewInstrument(),
logger.NewInstrument(),
plugins.NewInstrument(),
}
func Execute(opts *api.CompileOptions, args []string) ([]string, error) {
// if the options is invalid, just ignore
if !opts.IsValid() {
return args, nil
}
// init the logger for the instrument
loggerFile, err := initLogger(opts)
if err != nil {
return nil, err
}
defer loggerFile.Close()
logrus.Infof("executing instrument with args: %v", args)
return execute0(opts, args)
}
func execute0(opts *api.CompileOptions, args []string) ([]string, error) {
// find the instrument
var inst api.Instrument
for _, ins := range instruments {
if ins.CouldHandle(opts) {
inst = ins
break
}
}
if inst == nil {
return args, nil
}
var buildDir = filepath.Dir(opts.Output)
// instrument existing files
if err := instrumentFiles(buildDir, inst, args); err != nil {
return nil, err
}
// write extra files if exist
files, err := inst.WriteExtraFiles(buildDir)
if err != nil {
return nil, err
}
if len(files) > 0 {
args = append(args, files...)
}
return args, nil
}
func instrumentFiles(buildDir string, inst api.Instrument, args []string) error {
// parse files
parsedFiles, err := parseFilesInArgs(args)
if err != nil {
return err
}
allFiles := make([]*dst.File, 0)
for _, f := range parsedFiles {
allFiles = append(allFiles, f.dstFile)
}
// filter and edit the files
instrumentedFiles := make([]string, 0)
for path, info := range parsedFiles {
hasInstruted := false
dstutil.Apply(info.dstFile, func(cursor *dstutil.Cursor) bool {
if inst.FilterAndEdit(path, info.dstFile, cursor, allFiles) {
hasInstruted = true
}
return true
}, func(cursor *dstutil.Cursor) bool {
return true
})
if hasInstruted {
instrumentedFiles = append(instrumentedFiles, path)
}
}
// write instrumented files to the build directory
for _, updateFileSrc := range instrumentedFiles {
info := parsedFiles[updateFileSrc]
filename := filepath.Base(updateFileSrc)
dest := filepath.Join(buildDir, filename)
debugInfo, err := tools.BuildDSTDebugInfo(updateFileSrc, nil)
if err != nil {
return err
}
if err := tools.WriteDSTFile(dest, info.dstFile, debugInfo); err != nil {
return err
}
if err := inst.AfterEnhanceFile(updateFileSrc, dest); err != nil {
return err
}
args[info.argsIndex] = dest
}
return nil
}
func parseFilesInArgs(args []string) (map[string]*fileInfo, error) {
parsedFiles := make(map[string]*fileInfo)
for inx, path := range args {
// only process the go file
if !strings.HasSuffix(path, ".go") {
continue
}
// parse the file
file, err := decorator.ParseFile(nil, path, nil, parser.ParseComments)
if err != nil {
return nil, err
}
parsedFiles[path] = &fileInfo{
argsIndex: inx,
dstFile: file,
}
}
return parsedFiles, nil
}
func initLogger(opts *api.CompileOptions) (*os.File, error) {
logrus.SetFormatter(&logrus.TextFormatter{
DisableColors: true,
FullTimestamp: true,
})
file, err := os.OpenFile(filepath.Join(opts.CompileBaseDir(), "instrument.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o666)
if err != nil {
return nil, err
}
logrus.SetOutput(file)
return file, nil
}
type fileInfo struct {
argsIndex int
dstFile *dst.File
}