├── .github ├── release.yml └── workflows │ ├── reviewdog.yml │ ├── tag-and-release.yml │ └── test.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .octocov.yml ├── .tagpr ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cmd └── nrseg │ └── main.go ├── go.mod ├── go.sum ├── inspect.go ├── nrseg.go ├── nrseg_test.go ├── process.go ├── process_test.go └── testdata ├── input ├── advance.go ├── auto_generated.go ├── basic.go ├── basic_test.go ├── go.mod ├── go.sum ├── ignore │ └── must_not_change.go └── testdata │ └── must_not_change.go └── want ├── advance.go ├── auto_generated.go ├── basic.go ├── basic_test.go ├── go.mod ├── go.sum ├── ignore └── must_not_change.go └── testdata └── must_not_change.go /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - tagpr 5 | -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | on: [pull_request] 3 | jobs: 4 | golangci-lint: 5 | name: runner / golangci-lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out code into the Go module directory 9 | uses: actions/checkout@v4 10 | with: 11 | fetch-depth: 0 12 | - name: golangci-lint 13 | uses: reviewdog/action-golangci-lint@v2 14 | -------------------------------------------------------------------------------- /.github/workflows/tag-and-release.yml: -------------------------------------------------------------------------------- 1 | name: tag-and-release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | permissions: 8 | contents: write 9 | pull-requests: write 10 | 11 | jobs: 12 | tagpr: 13 | runs-on: ubuntu-latest 14 | outputs: 15 | tagpr-tag: ${{ steps.run-tagpr.outputs.tag }} 16 | steps: 17 | - uses: actions/create-github-app-token@v1 18 | id: app-token 19 | with: 20 | app-id: ${{ secrets.APP_ID }} 21 | private-key: ${{ secrets.PRIVATE_KEY }} 22 | - name: Check out source code 23 | uses: actions/checkout@v4 24 | with: 25 | token: ${{ steps.app-token.outputs.token }} 26 | - id: run-tagpr 27 | name: Run tagpr 28 | uses: Songmu/tagpr@v1 29 | env: 30 | GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} 31 | goreleaser: 32 | needs: tagpr 33 | if: needs.tagpr.outputs.tagpr-tag != '' 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/create-github-app-token@v1 37 | id: app-token 38 | with: 39 | app-id: ${{ secrets.APP_ID }} 40 | private-key: ${{ secrets.PRIVATE_KEY }} 41 | owner: "budougumi0617" 42 | repositories: | 43 | nrseg 44 | homebrew-tap 45 | - name: Checkout 46 | uses: actions/checkout@v4 47 | with: 48 | fetch-depth: 0 49 | token: ${{ steps.app-token.outputs.token }} 50 | - name: Set up Go 51 | uses: actions/setup-go@v5 52 | with: 53 | go-version-file: go.mod 54 | cache: true 55 | - name: Run GoReleaser 56 | uses: goreleaser/goreleaser-action@v6 57 | with: 58 | # either 'goreleaser' (default) or 'goreleaser-pro' 59 | distribution: goreleaser 60 | # 'latest', 'nightly', or a semver 61 | version: "latest" 62 | args: release --clean 63 | env: 64 | # need to access other repository for brew-tap 65 | GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} 66 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | pull_request: 7 | branches: ["master"] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up Go 15 | uses: actions/setup-go@v5 16 | with: 17 | go-version-file: "go.mod" 18 | cache: true 19 | - name: Install dependencies 20 | run: | 21 | go get . 22 | - name: Test 23 | run: go test ./... -race -coverprofile=coverage.out -covermode=atomic 24 | - name: Run octocov 25 | uses: k1LoW/octocov-action@v1 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/218a941be92679ce67d0484547e3e142b2f5f6f0/Go.gitignore 2 | 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Dependency directories (remove the comment below to include it) 17 | # vendor/ 18 | tmp/ 19 | nrseg 20 | dist/ 21 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | govet: 3 | check-shadowing: false 4 | golint: 5 | min-confidence: 0 6 | gocyclo: 7 | min-complexity: 30 8 | maligned: 9 | suggest-new: true 10 | misspell: 11 | locale: US 12 | ignore-words: 13 | - cancelled 14 | - Cancelled 15 | 16 | linters: 17 | disable-all: true 18 | enable: 19 | - goimports 20 | - bodyclose 21 | - deadcode 22 | - errcheck 23 | - gochecknoinits 24 | - gocognit 25 | - gocritic 26 | - gocyclo 27 | - gofmt 28 | - golint 29 | - govet 30 | - ineffassign 31 | - interfacer 32 | - maligned 33 | - misspell 34 | - nakedret 35 | - prealloc 36 | - staticcheck 37 | - structcheck 38 | - stylecheck 39 | - typecheck 40 | - unconvert 41 | - unparam 42 | - unused 43 | - varcheck 44 | - whitespace 45 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | before: 4 | hooks: 5 | - go mod download 6 | - go mod tidy 7 | 8 | builds: 9 | - id: nrseg-darwin 10 | ldflags: 11 | - -s -w -X main.Version={{.Version}} -X main.Revision={{.ShortCommit}} 12 | env: 13 | - CGO_ENABLED=0 14 | goos: 15 | - darwin 16 | goarch: 17 | - amd64 18 | - arm64 19 | main: ./cmd/nrseg/main.go 20 | - id: nrseg-linux 21 | env: 22 | - CGO_ENABLED=0 23 | goos: 24 | - linux 25 | goarch: 26 | - amd64 27 | - arm64 28 | main: ./cmd/nrseg/main.go 29 | - id: nrseg-windows 30 | env: 31 | - CGO_ENABLED=0 32 | goos: 33 | - windows 34 | goarch: 35 | - amd64 36 | main: ./cmd/nrseg/main.go 37 | 38 | archives: 39 | - format: "tar.gz" 40 | name_template: "{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}" 41 | files: 42 | - CHANGELOG.md 43 | - LICENSE 44 | - README.md 45 | checksum: 46 | name_template: "checksums.txt" 47 | 48 | changelog: 49 | sort: asc 50 | filters: 51 | exclude: 52 | - "^docs:" 53 | - "^test:" 54 | 55 | brews: 56 | - name: nrseg 57 | url_template: "https://github.com/budougumi0617/nrseg/releases/download/{{ .Tag }}/{{ .ArtifactName }}" 58 | repository: 59 | owner: budougumi0617 60 | name: homebrew-tap 61 | token: "{{ .Env.GITHUB_TOKEN }}" 62 | commit_author: 63 | name: "github-actions[bot]" 64 | email: "github-actions[bot]@users.noreply.github.com" 65 | homepage: "https://github.com/budougumi0617/nrseg" 66 | description: "Insert function segments into any function/method for Newrelic APM." 67 | license: "MIT" 68 | install: | 69 | bin.install "nrseg" 70 | test: | 71 | system "#{bin}/nrseg -h" 72 | -------------------------------------------------------------------------------- /.octocov.yml: -------------------------------------------------------------------------------- 1 | # generated by octocov init 2 | coverage: 3 | if: true 4 | codeToTestRatio: 5 | code: 6 | - "**/*.go" 7 | - "!**/*_test.go" 8 | test: 9 | - "**/*_test.go" 10 | testExecutionTime: 11 | if: true 12 | diff: 13 | datastores: 14 | - artifact://${GITHUB_REPOSITORY} 15 | comment: 16 | if: is_pull_request 17 | summary: 18 | if: true 19 | report: 20 | if: is_default_branch 21 | datastores: 22 | - artifact://${GITHUB_REPOSITORY} 23 | -------------------------------------------------------------------------------- /.tagpr: -------------------------------------------------------------------------------- 1 | # config file for the tagpr in git config format 2 | # The tagpr generates the initial configuration, which you can rewrite to suit your environment. 3 | # CONFIGURATIONS: 4 | # tagpr.releaseBranch 5 | # Generally, it is "main." It is the branch for releases. The tagpr tracks this branch, 6 | # creates or updates a pull request as a release candidate, or tags when they are merged. 7 | # 8 | # tagpr.versionFile 9 | # Versioning file containing the semantic version needed to be updated at release. 10 | # It will be synchronized with the "git tag". 11 | # Often this is a meta-information file such as gemspec, setup.cfg, package.json, etc. 12 | # Sometimes the source code file, such as version.go or Bar.pm, is used. 13 | # If you do not want to use versioning files but only git tags, specify the "-" string here. 14 | # You can specify multiple version files by comma separated strings. 15 | # 16 | # tagpr.vPrefix 17 | # Flag whether or not v-prefix is added to semver when git tagging. (e.g. v1.2.3 if true) 18 | # This is only a tagging convention, not how it is described in the version file. 19 | # 20 | # tagpr.changelog (Optional) 21 | # Flag whether or not changelog is added or changed during the release. 22 | # 23 | # tagpr.command (Optional) 24 | # Command to change files just before release. 25 | # 26 | # tagpr.template (Optional) 27 | # Pull request template in go template format 28 | # 29 | # tagpr.release (Optional) 30 | # GitHub Release creation behavior after tagging [true, draft, false] 31 | # If this value is not set, the release is to be created. 32 | # 33 | # tagpr.majorLabels (Optional) 34 | # Label of major update targets. Default is [major] 35 | # 36 | # tagpr.minorLabels (Optional) 37 | # Label of minor update targets. Default is [minor] 38 | # 39 | [tagpr] 40 | vPrefix = true 41 | releaseBranch = master 42 | versionFile = - 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [v0.0.10](https://github.com/budougumi0617/nrseg/compare/v0.0.9...v0.0.10) - 2024-09-19 4 | - ci: fix setting by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/34 5 | 6 | ## [v0.0.9](https://github.com/budougumi0617/nrseg/compare/v0.0.8...v0.0.9) - 2024-09-19 7 | - ci: fix permission by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/32 8 | 9 | ## [v0.0.8](https://github.com/budougumi0617/nrseg/compare/v0.0.7...v0.0.8) - 2024-09-18 10 | - fix: fix goreleaser settings by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/30 11 | 12 | ## [v0.0.7](https://github.com/budougumi0617/nrseg/compare/v0.0.6...v0.0.7) - 2024-09-18 13 | - build: bump up go version by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/28 14 | 15 | ## [v0.0.6](https://github.com/budougumi0617/nrseg/compare/v0.0.5...v0.0.6) - 2024-09-18 16 | - refactoring by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/16 17 | - fix typos by @kdnakt in https://github.com/budougumi0617/nrseg/pull/19 18 | - Add support for import alias by @kdnakt in https://github.com/budougumi0617/nrseg/pull/18 19 | - fix: 🐛 Ensure identifier name length greater than zero by @mpyw in https://github.com/budougumi0617/nrseg/pull/21 20 | - test: add tests by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/22 21 | - ci: upgrade goreleaser and use tagpr by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/23 22 | - ci: generate token by GitHub App by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/24 23 | - ci: allow write by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/25 24 | - ci: allow write PR by @budougumi0617 in https://github.com/budougumi0617/nrseg/pull/26 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Yoichiro Shimizu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nrseg 2 | === 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/budougumi0617/nrseg.svg)](https://pkg.go.dev/github.com/budougumi0617/nrseg) 4 | [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE) 5 | [![test](https://github.com/budougumi0617/nrseg/workflows/test/badge.svg)](https://github.com/budougumi0617/nrseg/actions?query=workflow%3Atest) 6 | [![reviewdog](https://github.com/budougumi0617/nrseg/workflows/reviewdog/badge.svg)](https://github.com/budougumi0617/nrseg/actions?query=workflow%3Areviewdog) 7 | 8 | ## Background 9 | https://docs.newrelic.com/docs/agents/go-agent/instrumentation/instrument-go-segments 10 | 11 | NewRelic is excellent o11y service, but if we use Newrelic in Go app, we need to insert `segment` into every function/method to measure the time taken by functions and code blocks. 12 | For example, we can use with `newrelic.FromContext` and `defer` statement. 13 | 14 | ```go 15 | func SampleFunc(ctx context.Context) { 16 | defer newrelic.FromContext(ctx).StartSegment("sample_func").End() 17 | // do anything... 18 | } 19 | ``` 20 | 21 | If there is your application in production already, you must add a segment into any function/method. It is a very time-consuming and tedious task. 22 | 23 | ## Description 24 | `nrseg` is cli tool for insert segment into all function/method in specified directory. 25 | 26 | Before code is below, 27 | ```go 28 | package input 29 | 30 | import ( 31 | "context" 32 | "fmt" 33 | "net/http" 34 | ) 35 | 36 | type FooBar struct{} 37 | 38 | func (f *FooBar) SampleMethod(ctx context.Context) { 39 | fmt.Println("Hello, playground") 40 | fmt.Println("end function") 41 | } 42 | 43 | func SampleFunc(ctx context.Context) { 44 | fmt.Println("Hello, playground") 45 | fmt.Println("end function") 46 | } 47 | 48 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 49 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 50 | } 51 | 52 | // nrseg:ignore you can be ignored if you want to not insert segment. 53 | func IgnoreHandler(w http.ResponseWriter, req *http.Request) { 54 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 55 | } 56 | ``` 57 | 58 | After execute `nrseg`, modified code is below. 59 | 60 | ```go 61 | package input 62 | 63 | import ( 64 | "context" 65 | "fmt" 66 | "net/http" 67 | 68 | "github.com/newrelic/go-agent/v3/newrelic" 69 | ) 70 | 71 | type FooBar struct{} 72 | 73 | func (f *FooBar) SampleMethod(ctx context.Context) { 74 | defer newrelic.FromContext(ctx).StartSegment("foo_bar_sample_method").End() 75 | fmt.Println("Hello, playground") 76 | fmt.Println("end function") 77 | } 78 | 79 | func SampleFunc(ctx context.Context) { 80 | defer newrelic.FromContext(ctx).StartSegment("sample_func").End() 81 | fmt.Println("Hello, playground") 82 | fmt.Println("end function") 83 | } 84 | 85 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 86 | defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() 87 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 88 | } 89 | 90 | // nrseg:ignore you can be ignored if you want to not insert segment. 91 | func IgnoreHandler(w http.ResponseWriter, req *http.Request) { 92 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 93 | } 94 | ``` 95 | 96 | ### Features 97 | - [x] Insert `Function segments` into the function with following arguments. 98 | - The function/method signature has `context.Context`. 99 | - `defer newrelic.FromContext(ctx).StartSegment("func_name").End()` 100 | - The function/method signature has `*http.Request`. 101 | - `defer newrelic.FromContext(req.Context()).StartSegment("func_name").End()` 102 | - [x] Support any variable name of `context.Context`/`*http.Request`. 103 | - [x] Support import alias of `"context"`/`"net/http"`. 104 | - [x] Use function/method name to segment name. 105 | - [x] This processing is recursively repeated. 106 | - [x] Able to ignore function/method by `nrseg:ignore` comment. 107 | - [x] Ignore specified directories with cli option `-i`/`-ignore`. 108 | - [ ] Remove all `Function segments` 109 | - [ ] Add: `dry-run` option 110 | - [ ] Validate: Show a function that doesn't call the segment. 111 | - [ ] Support anonymous function 112 | 113 | ## Synopsis 114 | ``` 115 | $ nrseg -i testuitl ./ 116 | ``` 117 | 118 | ## Options 119 | 120 | ``` 121 | $ nrseg -h 122 | Insert function segments into any function/method for Newrelic APM. 123 | 124 | Usage of nrseg: 125 | -i string 126 | ignore directory names. ex: foo,bar,baz 127 | (testdata directory is always ignored.) 128 | -ignore string 129 | ignore directory names. ex: foo,bar,baz 130 | (testdata directory is always ignored.) 131 | -v print version information and quit. 132 | -version 133 | print version information and quit. 134 | exit status 1 135 | 136 | ``` 137 | 138 | ## Limitation 139 | nrseg inserts only `function segments`, so we need the initialize of Newrelic manually. 140 | 141 | - [Install New Relic for Go][segment] 142 | - [Monitor a transaction by wrapping an HTTP handler][nr_handler] 143 | 144 | [nr_handler]: https://docs.newrelic.com/docs/agents/go-agent/instrumentation/instrument-go-transactions#http-handler-txns 145 | [segment]: https://docs.newrelic.com/docs/agents/go-agent/installation/install-new-relic-go 146 | 147 | If we want to adopt Newrelic to our application, , we write initialize, and newrelic.WrapHandleFunc manually before execute this tool. 148 | ```go 149 | app, err := newrelic.NewApplication( 150 | newrelic.ConfigAppName("my_application"), 151 | newrelic.ConfigLicense(newrelicLicenseKey), 152 | newrelic.ConfigDistributedTracerEnabled(true), 153 | ) 154 | ``` 155 | 156 | ## Installation 157 | 158 | ``` 159 | $ go install github.com/budougumi0617/nrseg/cmd/nrseg 160 | ``` 161 | 162 | Built binaries are available on github releases. https://github.com/budougumi0617/nrseg/releases 163 | 164 | ### MacOS 165 | If you want to install on MacOS, you can use Homebrew. 166 | ``` 167 | brew install budougumi0617/tap/nrseg 168 | ``` 169 | 170 | ## Contribution 171 | 1. Fork ([https://github.com/budougumi0617/nrseg/fork](https://github.com/budougumi0617/nrseg/fork)) 172 | 2. Create a feature branch 173 | 3. Commit your changes 174 | 4. Rebase your local changes against the master branch 175 | 5. Run test suite with the `go test ./...` command and confirm that it passes 176 | 6. Run `gofmt -s` 177 | 7. Create new Pull Request 178 | 179 | ## License 180 | 181 | [MIT](https://github.com/budougumi0617/nrseg/blob/master/LICENSE) 182 | 183 | ## Author 184 | [budougumi0617](https://github.com/budougumi0617) 185 | 186 | -------------------------------------------------------------------------------- /cmd/nrseg/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/budougumi0617/nrseg" 7 | ) 8 | 9 | var ( 10 | Version = "devel" 11 | Revision = "unset" 12 | ) 13 | 14 | func main() { 15 | if err := nrseg.Run(os.Args, os.Stdout, os.Stderr, Version, Revision); err != nil { 16 | os.Exit(1) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/budougumi0617/nrseg 2 | 3 | go 1.23 4 | 5 | require ( 6 | github.com/google/go-cmp v0.5.4 7 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c 8 | ) 9 | 10 | require ( 11 | golang.org/x/mod v0.3.0 // indirect 12 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= 2 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 3 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 4 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 5 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 6 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 7 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 8 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 9 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 10 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 11 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 12 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 13 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 14 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 15 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 17 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 18 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 19 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 20 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 21 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c h1:dS09fXwOFF9cXBnIzZexIuUBj95U1NyQjkEhkgidDow= 22 | golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 23 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 24 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 25 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 26 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 27 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 28 | -------------------------------------------------------------------------------- /inspect.go: -------------------------------------------------------------------------------- 1 | package nrseg 2 | 3 | import ( 4 | "errors" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | ) 9 | 10 | func (nrseg *nrseg) Inspect(filename string, src []byte) error { 11 | if len(src) != 0 && c.Match(src) { 12 | return nil 13 | } 14 | fs := token.NewFileSet() 15 | f, err := parser.ParseFile(fs, filename, src, parser.ParseComments) 16 | if err != nil { 17 | return err 18 | } 19 | // import newrelic pkg 20 | pkg := "newrelic" 21 | name, err := findImport(fs, f) // importされたpkgの名前 22 | if err != nil && !errors.Is(err, ErrNoImportNrPkg) { 23 | return err 24 | } 25 | if len(name) != 0 { 26 | // change name if named import. 27 | pkg = name 28 | } 29 | 30 | ast.Inspect(f, func(n ast.Node) bool { 31 | if fd, ok := n.(*ast.FuncDecl); ok { 32 | if findIgnoreComment(fd.Doc) { 33 | return false 34 | } 35 | if fd.Body != nil && len(fd.Body.List) > 0 { 36 | if _, t := parseParams(f.Imports, fd.Type); !(t == TypeContext || t == TypeHttpRequest) { 37 | return false 38 | } 39 | if !existFromContext(pkg, fd.Body.List[0]) { 40 | nrseg.errFlag = true 41 | nrseg.reportf(filename, fs, fd.Pos(), fd) 42 | } 43 | return false 44 | } 45 | } 46 | return true 47 | }) 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /nrseg.go: -------------------------------------------------------------------------------- 1 | package nrseg 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "go/ast" 9 | "go/token" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "regexp" 15 | "strings" 16 | ) 17 | 18 | var ( 19 | // ErrShowVersion returns when set version flag. 20 | ErrShowVersion = errors.New("show version") 21 | ErrFlagTrue = errors.New("find error") 22 | ) 23 | 24 | type nrseg struct { 25 | inspectMode bool 26 | in, dest string 27 | ignoreDirs []string 28 | outStream, errStream io.Writer 29 | errFlag bool 30 | } 31 | 32 | func fill(args []string, outStream, errStream io.Writer, version, revision string) (*nrseg, error) { 33 | cn := args[0] 34 | flags := flag.NewFlagSet(cn, flag.ContinueOnError) 35 | flags.SetOutput(errStream) 36 | flags.Usage = func() { 37 | fmt.Fprintf( 38 | flag.CommandLine.Output(), 39 | "Insert function segments into any function/method for Newrelic APM.\n\nUsage of %s:\n", 40 | os.Args[0], 41 | ) 42 | flags.PrintDefaults() 43 | } 44 | 45 | var v bool 46 | vdesc := "print version information and quit." 47 | flags.BoolVar(&v, "version", false, vdesc) 48 | flags.BoolVar(&v, "v", false, vdesc) 49 | 50 | var ignoreDirs string 51 | idesc := "ignore directory names. ex: foo,bar,baz\n(testdata directory is always ignored.)" 52 | flags.StringVar(&ignoreDirs, "ignore", "", idesc) 53 | flags.StringVar(&ignoreDirs, "i", "", idesc) 54 | 55 | var destDir string 56 | odesc := "destination directory." 57 | flags.StringVar(&destDir, "destination", "", odesc) 58 | 59 | if err := flags.Parse(args[1:]); err != nil { 60 | return nil, err 61 | } 62 | if v { 63 | fmt.Fprintf(errStream, "%s version %q, revision %q\n", cn, version, revision) 64 | return nil, ErrShowVersion 65 | } 66 | 67 | dirs := []string{"testdata"} 68 | if len(ignoreDirs) != 0 { 69 | dirs = append(dirs, strings.Split(ignoreDirs, ",")...) 70 | } 71 | 72 | dir := "./" 73 | nargs := flags.Args() 74 | if len(nargs) > 1 { 75 | msg := "execution path must be only one or no-set(current directory)." 76 | return nil, fmt.Errorf(msg) 77 | } 78 | if len(nargs) == 1 { 79 | dir = nargs[0] 80 | } 81 | 82 | return &nrseg{ 83 | in: dir, 84 | dest: destDir, 85 | ignoreDirs: dirs, 86 | outStream: outStream, 87 | errStream: errStream, 88 | }, nil 89 | } 90 | 91 | func fill2(args []string, outStream, errStream io.Writer, version, revision string) (*nrseg, error) { 92 | cn := args[0] 93 | flags := flag.NewFlagSet(cn, flag.ContinueOnError) 94 | flags.SetOutput(errStream) 95 | flags.Usage = func() { 96 | fmt.Fprintf( 97 | flag.CommandLine.Output(), 98 | "Insert function segments into any function/method for Newrelic APM.\n\nUsage of %s:\n", 99 | os.Args[0], 100 | ) 101 | flags.PrintDefaults() 102 | } 103 | 104 | var v bool 105 | vdesc := "print version information and quit." 106 | flags.BoolVar(&v, "version", false, vdesc) 107 | flags.BoolVar(&v, "v", false, vdesc) 108 | 109 | var ignoreDirs string 110 | idesc := "ignore directory names. ex: foo,bar,baz\n(testdata directory is always ignored.)" 111 | flags.StringVar(&ignoreDirs, "ignore", "", idesc) 112 | flags.StringVar(&ignoreDirs, "i", "", idesc) 113 | 114 | if err := flags.Parse(args[2:]); err != nil { 115 | return nil, err 116 | } 117 | if v { 118 | fmt.Fprintf(errStream, "%s version %q, revision %q\n", cn, version, revision) 119 | return nil, ErrShowVersion 120 | } 121 | 122 | dirs := []string{"testdata"} 123 | if len(ignoreDirs) != 0 { 124 | dirs = append(dirs, strings.Split(ignoreDirs, ",")...) 125 | } 126 | 127 | dir := "./" 128 | nargs := flags.Args() 129 | if len(nargs) > 1 { 130 | msg := "execution path must be only one or no-set(current directory)." 131 | return nil, fmt.Errorf(msg) 132 | } 133 | if len(nargs) == 1 { 134 | dir = nargs[0] 135 | } 136 | 137 | return &nrseg{ 138 | inspectMode: true, 139 | in: dir, 140 | ignoreDirs: dirs, 141 | outStream: outStream, 142 | errStream: errStream, 143 | }, nil 144 | } 145 | 146 | var c = regexp.MustCompile("(?m)^// Code generated .* DO NOT EDIT\\.$") 147 | 148 | func (n *nrseg) skipDir(p string) bool { 149 | for _, dir := range n.ignoreDirs { 150 | if filepath.Base(p) == dir { 151 | return true 152 | } 153 | } 154 | return false 155 | } 156 | 157 | func (n *nrseg) run() error { 158 | return filepath.Walk(n.in, func(path string, info os.FileInfo, err error) error { 159 | if info.IsDir() && n.skipDir(path) { 160 | return filepath.SkipDir 161 | } 162 | if info.IsDir() { 163 | return nil 164 | } 165 | if filepath.Ext(path) != ".go" { 166 | return nil 167 | } 168 | 169 | if strings.HasSuffix(filepath.Base(path), "_test.go") { 170 | return nil 171 | } 172 | 173 | f, err := os.OpenFile(path, os.O_RDWR, 0664) 174 | if err != nil { 175 | return err 176 | } 177 | defer f.Close() 178 | org, err := ioutil.ReadAll(f) 179 | if err != nil { 180 | return err 181 | } 182 | 183 | if n.inspectMode { 184 | return n.Inspect(path, org) 185 | } else { 186 | got, err := Process(path, org) 187 | if err != nil { 188 | return err 189 | } 190 | if !bytes.Equal(org, got) { 191 | if len(n.dest) != 0 && n.in != n.dest { 192 | return n.writeOtherPath(n.in, n.dest, path, got) 193 | } 194 | if _, err := f.WriteAt(got, 0); err != nil { 195 | return err 196 | } 197 | } 198 | } 199 | return nil 200 | }) 201 | } 202 | 203 | func (n *nrseg) writeOtherPath(in, dist, path string, got []byte) error { 204 | p, err := filepath.Rel(in, path) 205 | if err != nil { 206 | return err 207 | } 208 | distabs, err := filepath.Abs(dist) 209 | if err != nil { 210 | return err 211 | } 212 | dp := filepath.Join(distabs, p) 213 | dpd := filepath.Dir(dp) 214 | if _, err := os.Stat(dpd); os.IsNotExist(err) { 215 | if err := os.Mkdir(dpd, 0777); err != nil { 216 | fmt.Fprintf(n.outStream, "create dir failed at %q: %v\n", dpd, err) 217 | return err 218 | } 219 | } 220 | 221 | fmt.Fprintf(n.outStream, "update file %q\n", dp) 222 | f, err := os.OpenFile(dp, os.O_RDWR|os.O_CREATE, 0644) 223 | if err != nil { 224 | return nil 225 | } 226 | defer f.Close() 227 | _, err = f.Write(got) 228 | if err != nil { 229 | fmt.Fprintf(n.outStream, "write file failed %v\n", err) 230 | } 231 | fmt.Printf("created at %q\n", dp) 232 | return err 233 | } 234 | 235 | func (n *nrseg) reportf(filename string, fs *token.FileSet, pos token.Pos, fd *ast.FuncDecl) { 236 | var rcv string 237 | if fd.Recv != nil && len(fd.Recv.List) > 0 { 238 | if rn, ok := fd.Recv.List[0].Type.(*ast.StarExpr); ok { 239 | if idt, ok := rn.X.(*ast.Ident); ok { 240 | rcv = idt.Name 241 | } 242 | } else if idt, ok := fd.Recv.List[0].Type.(*ast.Ident); ok { 243 | rcv = idt.Name 244 | } 245 | } 246 | 247 | p := fs.File(pos).Position(pos) 248 | if len(rcv) != 0 { 249 | fmt.Fprintf(n.outStream, "%s:%d:%d: %s.%s no insert segment\n", p.Filename, p.Line, p.Column, rcv, fd.Name.Name) 250 | return 251 | } 252 | fmt.Fprintf(n.outStream, "%s:%d:%d: %s no insert segment\n", p.Filename, p.Line, p.Column, fd.Name.Name) 253 | } 254 | 255 | // Run is entry point. 256 | func Run(args []string, outStream, errStream io.Writer, version, revision string) error { 257 | var nrseg *nrseg 258 | var err error 259 | if len(args) >= 2 && args[1] == "inspect" { 260 | nrseg, err = fill2(args, outStream, errStream, version, revision) 261 | } else { 262 | nrseg, err = fill(args, outStream, errStream, version, revision) 263 | } 264 | if err != nil { 265 | return err 266 | } 267 | err = nrseg.run() 268 | if nrseg.errFlag { 269 | err = ErrFlagTrue 270 | } 271 | return err 272 | } 273 | -------------------------------------------------------------------------------- /nrseg_test.go: -------------------------------------------------------------------------------- 1 | package nrseg 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "testing" 11 | 12 | "github.com/google/go-cmp/cmp" 13 | ) 14 | 15 | func TestNrseg_Run_Default(t *testing.T) { 16 | dest := t.TempDir() 17 | tests := [...]struct { 18 | name string 19 | want string 20 | args []string 21 | }{ 22 | { 23 | name: "basic", 24 | want: "./testdata/want", 25 | args: []string{"nrseg", "-destination", dest, "-i", "ignore", "./testdata/input"}, 26 | }, 27 | } 28 | for _, tt := range tests { 29 | tt := tt 30 | t.Run(tt.name, func(t *testing.T) { 31 | out := &bytes.Buffer{} 32 | errs := &bytes.Buffer{} 33 | if err := Run(tt.args, out, errs, "", ""); err != nil { 34 | t.Fatalf("run() error = %v", err) 35 | } 36 | validate(t, dest, tt.want) 37 | }) 38 | } 39 | } 40 | 41 | func TestNrseg_Run_Inspect(t *testing.T) { 42 | tests := [...]struct { 43 | name string 44 | want string 45 | args []string 46 | }{ 47 | { 48 | name: "basic", 49 | args: []string{"nrseg", "inspect", "./testdata/input"}, 50 | want: `testdata/input/basic.go:11:1: S.SampleMethod no insert segment 51 | testdata/input/basic.go:16:1: SampleFunc no insert segment 52 | testdata/input/basic.go:21:1: SampleHandler no insert segment 53 | testdata/input/ignore/must_not_change.go:11:1: MustNotChange.SampleMethod no insert segment 54 | testdata/input/ignore/must_not_change.go:16:1: SampleFunc no insert segment 55 | testdata/input/ignore/must_not_change.go:21:1: SampleHandler no insert segment 56 | `, 57 | }, 58 | { 59 | name: "ignoreDir", 60 | args: []string{"nrseg", "inspect", "-i", "ignore", "./testdata/input"}, 61 | want: `testdata/input/basic.go:11:1: S.SampleMethod no insert segment 62 | testdata/input/basic.go:16:1: SampleFunc no insert segment 63 | testdata/input/basic.go:21:1: SampleHandler no insert segment 64 | `, 65 | }, 66 | } 67 | for _, tt := range tests { 68 | tt := tt 69 | t.Run(tt.name, func(t *testing.T) { 70 | out := &bytes.Buffer{} 71 | errs := &bytes.Buffer{} 72 | if err := Run(tt.args, out, errs, "", ""); !errors.Is(err, ErrFlagTrue) { 73 | t.Fatalf("want %v, but got %v", ErrFlagTrue, err) 74 | } 75 | if out.String() != tt.want { 76 | t.Errorf("want\n%s\nbut got\n%s", tt.want, out.String()) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func TestNrseg_run(t *testing.T) { 83 | type fields struct { 84 | path string 85 | outStream io.Writer 86 | errStream io.Writer 87 | } 88 | tests := [...]struct { 89 | name string 90 | want string 91 | fields fields 92 | }{ 93 | { 94 | name: "basic", 95 | want: "./testdata/want", 96 | fields: fields{ 97 | path: "./testdata/input", 98 | outStream: os.Stdout, 99 | errStream: os.Stderr, 100 | }, 101 | }, 102 | } 103 | for _, tt := range tests { 104 | tt := tt 105 | t.Run(tt.name, func(t *testing.T) { 106 | dest := t.TempDir() 107 | n := &nrseg{ 108 | in: tt.fields.path, 109 | dest: dest, 110 | ignoreDirs: []string{"testdata", "ignore"}, 111 | outStream: tt.fields.outStream, 112 | errStream: tt.fields.errStream, 113 | } 114 | if err := n.run(); err != nil { 115 | t.Fatalf("run() error = %v", err) 116 | } 117 | validate(t, dest, tt.want) 118 | }) 119 | } 120 | } 121 | 122 | func validate(t *testing.T, gotpath, wantpath string) { 123 | filepath.Walk(gotpath, func(path string, info os.FileInfo, err error) error { 124 | if info.IsDir() { 125 | return nil 126 | } 127 | if filepath.Ext(path) != ".go" { 128 | return nil 129 | } 130 | rel, err := filepath.Rel(gotpath, path) 131 | if err != nil { 132 | t.Errorf("filepath.Rel(): not want error at %q: %v", path, err) 133 | return err 134 | } 135 | wfp := filepath.Join(wantpath, rel) 136 | wf, err := os.Open(wfp) 137 | if err != nil { 138 | t.Errorf("cannot open the wanted file %q: %v", path, err) 139 | return err 140 | } 141 | defer wf.Close() 142 | want, err := ioutil.ReadAll(wf) 143 | if err != nil { 144 | t.Errorf("cannot read the wanted file %q: %v", path, err) 145 | return err 146 | } 147 | 148 | gf, err := os.Open(path) 149 | if err != nil { 150 | t.Errorf("cannot read the got file %q: %v", path, err) 151 | return err 152 | } 153 | got, err := ioutil.ReadAll(gf) 154 | if err != nil { 155 | t.Errorf("cannot read the got file %q: %v", path, err) 156 | return err 157 | } 158 | if diff := cmp.Diff(got, want); len(diff) != 0 { 159 | t.Errorf("%q -got +want %v", path, diff) 160 | } 161 | 162 | return nil 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package nrseg 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "go/ast" 7 | "go/format" 8 | "go/parser" 9 | "go/token" 10 | "regexp" 11 | "strconv" 12 | "strings" 13 | 14 | "golang.org/x/tools/go/ast/astutil" 15 | "golang.org/x/tools/imports" 16 | ) 17 | 18 | func Process(filename string, src []byte) ([]byte, error) { 19 | if len(src) != 0 && c.Match(src) { 20 | return src, nil 21 | } 22 | fs := token.NewFileSet() 23 | f, err := parser.ParseFile(fs, filename, src, parser.ParseComments) 24 | if err != nil { 25 | return nil, err 26 | } 27 | // import newrelic pkg 28 | pkg := "newrelic" 29 | name, err := addImport(fs, f) // importされたpkgの名前 30 | if err != nil { 31 | return nil, err 32 | } 33 | if len(name) != 0 { 34 | // change name if named import. 35 | pkg = name 36 | } 37 | 38 | ast.Inspect(f, func(n ast.Node) bool { 39 | if fd, ok := n.(*ast.FuncDecl); ok { 40 | if findIgnoreComment(fd.Doc) { 41 | return false 42 | } 43 | if fd.Body != nil && len(fd.Body.List) > 0 { 44 | sn := getSegName(fd) 45 | vn, t := parseParams(f.Imports, fd.Type) 46 | var ds ast.Stmt 47 | switch t { 48 | case TypeContext: 49 | ds = buildDeferStmt(fd.Body.Lbrace, pkg, vn, sn) 50 | case TypeHttpRequest: 51 | ds = buildDeferStmtWithHttpRequest(fd.Body.Lbrace, pkg, vn, sn) 52 | case TypeUnknown: 53 | return false 54 | } 55 | 56 | if !existFromContext(pkg, fd.Body.List[0]) { 57 | fd.Body.List = append([]ast.Stmt{ds}, fd.Body.List...) 58 | } 59 | return false 60 | } 61 | } 62 | return true 63 | }) 64 | 65 | // gofmt 66 | var fmtedBuf bytes.Buffer 67 | if err := format.Node(&fmtedBuf, fs, f); err != nil { 68 | return nil, err 69 | } 70 | 71 | // goimports 72 | igot, err := imports.Process(filename, fmtedBuf.Bytes(), nil) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | return igot, nil 78 | } 79 | 80 | const NewRelicV3Pkg = "github.com/newrelic/go-agent/v3/newrelic" 81 | 82 | func addImport(fs *token.FileSet, f *ast.File) (string, error) { 83 | pkg, err := findImport(fs, f) 84 | if err == nil { 85 | return pkg, nil 86 | } 87 | if errors.Is(err, ErrNoImportNrPkg) { 88 | astutil.AddImport(fs, f, NewRelicV3Pkg) 89 | return "", nil 90 | } 91 | 92 | return "", err 93 | } 94 | 95 | var ErrNoImportNrPkg = errors.New("not import newrelic pkg") 96 | 97 | func findImport(fs *token.FileSet, f *ast.File) (string, error) { 98 | for _, spec := range f.Imports { 99 | path, err := strconv.Unquote(spec.Path.Value) 100 | if err != nil { 101 | return "", err 102 | } 103 | // import already. 104 | if path == NewRelicV3Pkg { 105 | if spec.Name != nil { 106 | return spec.Name.Name, nil 107 | } 108 | return "", nil 109 | } 110 | } 111 | return "", ErrNoImportNrPkg 112 | } 113 | 114 | var nrignoreReg = regexp.MustCompile("(?m)^// nrseg:ignore .*$") 115 | 116 | func findIgnoreComment(cg *ast.CommentGroup) bool { 117 | if cg == nil { 118 | return false 119 | } 120 | for _, c := range cg.List { 121 | if nrignoreReg.MatchString(c.Text) { 122 | return true 123 | } 124 | } 125 | return false 126 | } 127 | 128 | func existFromContext(pn string, s ast.Stmt) bool { 129 | var result bool 130 | ast.Inspect(s, func(n ast.Node) bool { 131 | if se, ok := n.(*ast.SelectorExpr); ok { 132 | if idt, ok := se.X.(*ast.Ident); ok && idt.Name == pn && se.Sel.Name == "FromContext" { 133 | result = true 134 | return false 135 | } 136 | } 137 | return true 138 | }) 139 | return result 140 | } 141 | 142 | // buildDeferStmt builds the defer statement with args. 143 | // ex: 144 | // defer newrelic.FromContext(ctx).StartSegment("slow").End() 145 | func buildDeferStmt(pos token.Pos, pkgName, ctxName, segName string) *ast.DeferStmt { 146 | arg := &ast.Ident{NamePos: pos, Name: ctxName} 147 | return skeletonDeferStmt(pos, arg, pkgName, segName) 148 | } 149 | 150 | // buildDeferStmt builds the defer statement with *http.Request. 151 | // ex: 152 | // defer newrelic.FromContext(req.Context()).StartSegment("slow").End() 153 | func buildDeferStmtWithHttpRequest(pos token.Pos, pkgName, reqName, segName string) *ast.DeferStmt { 154 | arg := &ast.CallExpr{ 155 | Fun: &ast.SelectorExpr{ 156 | X: &ast.Ident{NamePos: pos, Name: reqName}, 157 | Sel: &ast.Ident{NamePos: pos, Name: "Context"}, 158 | }, 159 | Rparen: pos, 160 | } 161 | return skeletonDeferStmt(pos, arg, pkgName, segName) 162 | } 163 | 164 | func skeletonDeferStmt(pos token.Pos, fcArg ast.Expr, pkgName, segName string) *ast.DeferStmt { 165 | return &ast.DeferStmt{ 166 | Call: &ast.CallExpr{ 167 | Fun: &ast.SelectorExpr{ 168 | X: &ast.CallExpr{ 169 | Fun: &ast.SelectorExpr{ 170 | X: &ast.CallExpr{ 171 | Fun: &ast.SelectorExpr{ 172 | X: &ast.Ident{NamePos: pos, Name: pkgName}, 173 | Sel: &ast.Ident{NamePos: pos, Name: "FromContext"}, 174 | }, 175 | Lparen: pos, 176 | Args: []ast.Expr{fcArg}, 177 | Rparen: pos, 178 | }, 179 | Sel: &ast.Ident{NamePos: pos, Name: "StartSegment"}, 180 | }, 181 | Args: []ast.Expr{&ast.BasicLit{ 182 | ValuePos: pos, 183 | Kind: token.STRING, 184 | Value: strconv.Quote(segName), 185 | }}, 186 | }, 187 | Sel: &ast.Ident{NamePos: pos, Name: "End"}, 188 | }, 189 | Rparen: pos, 190 | }, 191 | } 192 | } 193 | 194 | func getSegName(fd *ast.FuncDecl) string { 195 | var prefix string 196 | if fd.Recv != nil && len(fd.Recv.List) > 0 { 197 | if rn, ok := fd.Recv.List[0].Type.(*ast.StarExpr); ok { 198 | if idt, ok := rn.X.(*ast.Ident); ok { 199 | prefix = toSnake(idt.Name) 200 | } 201 | } else if idt, ok := fd.Recv.List[0].Type.(*ast.Ident); ok { 202 | prefix = toSnake(idt.Name) 203 | } 204 | } 205 | sn := toSnake(fd.Name.Name) 206 | if len(prefix) != 0 { 207 | sn = prefix + "_" + sn 208 | } 209 | return sn 210 | } 211 | 212 | // https://www.golangprograms.com/golang-convert-string-into-snake-case.html 213 | var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") 214 | var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") 215 | 216 | func toSnake(n string) string { 217 | snake := matchFirstCap.ReplaceAllString(n, "${1}_${2}") 218 | snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") 219 | return strings.ToLower(snake) 220 | } 221 | 222 | const ( 223 | TypeContext = "context.Context" 224 | TypeHttpRequest = "*http.Request" 225 | TypeUnknown = "Unknown" 226 | ) 227 | 228 | var types = map[string]string{ 229 | TypeContext: "\"context\"", 230 | TypeHttpRequest: "\"net/http\"", 231 | } 232 | 233 | func parseParams(is []*ast.ImportSpec, t *ast.FuncType) (string, string) { 234 | var cname = getImportName(is, TypeContext) 235 | var hname = getImportName(is, TypeHttpRequest) 236 | n, typ := "", TypeUnknown 237 | for _, f := range t.Params.List { 238 | if se, ok := f.Type.(*ast.SelectorExpr); ok { 239 | if idt, ok := se.X.(*ast.Ident); ok && idt.Name == cname && se.Sel.Name == "Context" { 240 | if len(f.Names) > 0 && len(f.Names[0].Name) > 0 { 241 | return f.Names[0].Name, TypeContext 242 | } 243 | } 244 | } 245 | if se, ok := f.Type.(*ast.StarExpr); ok { 246 | if se, ok := se.X.(*ast.SelectorExpr); ok { 247 | if idt, ok := se.X.(*ast.Ident); ok && idt.Name == hname && se.Sel.Name == "Request" { 248 | if len(f.Names) > 0 && len(f.Names[0].Name) > 0 { 249 | n = f.Names[0].Name 250 | typ = TypeHttpRequest 251 | } 252 | } 253 | } 254 | } 255 | } 256 | return n, typ 257 | } 258 | 259 | func getImportName(is []*ast.ImportSpec, typ string) string { 260 | var def = strings.Replace(strings.Split(typ, ".")[0], "*", "", 1) 261 | for _, i := range is { 262 | if i.Name != nil && i.Path != nil && i.Path.Value == types[typ] { 263 | return i.Name.Name 264 | } 265 | } 266 | return def 267 | } 268 | -------------------------------------------------------------------------------- /process_test.go: -------------------------------------------------------------------------------- 1 | package nrseg 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "testing" 8 | 9 | "github.com/google/go-cmp/cmp" 10 | ) 11 | 12 | func TestProcess(t *testing.T) { 13 | t.Parallel() 14 | tests := [...]struct { 15 | name, src, want string 16 | }{ 17 | { 18 | name: "BasicProcess", 19 | src: `package main 20 | 21 | import ( 22 | "context" 23 | "fmt" 24 | "net/http" 25 | ) 26 | 27 | type Foo struct{} 28 | 29 | func (f *Foo) SampleMethod(ctx context.Context) { 30 | fmt.Println("Hello, playground") 31 | fmt.Println("end function") 32 | } 33 | 34 | type BarBar struct{} 35 | 36 | func (b BarBar) BazMethod(ctx context.Context) { 37 | fmt.Println("Hello, playground") 38 | fmt.Println("end function") 39 | } 40 | 41 | func SampleFunc(ctx context.Context) { 42 | // comment 1 43 | fmt.Println("Hello, playground") 44 | // comment 2 45 | fmt.Println("end function") 46 | // comment 3 47 | } 48 | 49 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 50 | // comment 1 51 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 52 | } 53 | `, 54 | want: `package main 55 | 56 | import ( 57 | "context" 58 | "fmt" 59 | "net/http" 60 | 61 | "github.com/newrelic/go-agent/v3/newrelic" 62 | ) 63 | 64 | type Foo struct{} 65 | 66 | func (f *Foo) SampleMethod(ctx context.Context) { 67 | defer newrelic.FromContext(ctx).StartSegment("foo_sample_method").End() 68 | fmt.Println("Hello, playground") 69 | fmt.Println("end function") 70 | } 71 | 72 | type BarBar struct{} 73 | 74 | func (b BarBar) BazMethod(ctx context.Context) { 75 | defer newrelic.FromContext(ctx).StartSegment("bar_bar_baz_method").End() 76 | fmt.Println("Hello, playground") 77 | fmt.Println("end function") 78 | } 79 | 80 | func SampleFunc(ctx context.Context) { 81 | defer newrelic.FromContext(ctx).StartSegment("sample_func").End() 82 | // comment 1 83 | fmt.Println("Hello, playground") 84 | // comment 2 85 | fmt.Println("end function") 86 | // comment 3 87 | } 88 | 89 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 90 | defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() 91 | // comment 1 92 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 93 | } 94 | `, 95 | }, 96 | { 97 | name: "SkipIfIgnoreComment", 98 | src: `package main 99 | 100 | import ( 101 | "context" 102 | "fmt" 103 | "net/http" 104 | ) 105 | 106 | type S struct{} 107 | 108 | func (s *S) SampleMethod(ctx context.Context) { 109 | fmt.Println("Hello, playground") 110 | } 111 | 112 | // SampleFunc is sample function. 113 | // nrseg:ignore it does not be needed to insert segment in this function. 114 | func SampleFunc(ctx context.Context) { 115 | fmt.Println("Hello, playground") 116 | } 117 | 118 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 119 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 120 | } 121 | `, 122 | want: `package main 123 | 124 | import ( 125 | "context" 126 | "fmt" 127 | "net/http" 128 | 129 | "github.com/newrelic/go-agent/v3/newrelic" 130 | ) 131 | 132 | type S struct{} 133 | 134 | func (s *S) SampleMethod(ctx context.Context) { 135 | defer newrelic.FromContext(ctx).StartSegment("s_sample_method").End() 136 | fmt.Println("Hello, playground") 137 | } 138 | 139 | // SampleFunc is sample function. 140 | // nrseg:ignore it does not be needed to insert segment in this function. 141 | func SampleFunc(ctx context.Context) { 142 | fmt.Println("Hello, playground") 143 | } 144 | 145 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 146 | defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() 147 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 148 | } 149 | `, 150 | }, 151 | { 152 | name: "AlreadySegmentCall", 153 | src: `package main 154 | 155 | import ( 156 | "context" 157 | "fmt" 158 | "net/http" 159 | 160 | "github.com/newrelic/go-agent/v3/newrelic" 161 | ) 162 | 163 | func SampleFunc(ctx context.Context) { 164 | defer newrelic.FromContext(ctx).StartSegment("sample_func").End() 165 | fmt.Println("Hello, playground") 166 | } 167 | 168 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 169 | defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() 170 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 171 | } 172 | `, 173 | want: `package main 174 | 175 | import ( 176 | "context" 177 | "fmt" 178 | "net/http" 179 | 180 | "github.com/newrelic/go-agent/v3/newrelic" 181 | ) 182 | 183 | func SampleFunc(ctx context.Context) { 184 | defer newrelic.FromContext(ctx).StartSegment("sample_func").End() 185 | fmt.Println("Hello, playground") 186 | } 187 | 188 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 189 | defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() 190 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 191 | } 192 | `, 193 | }, 194 | { 195 | name: "UseApplication", 196 | src: `package router 197 | 198 | import ( 199 | "fmt" 200 | "net/http" 201 | 202 | "github.com/gorilla/mux" 203 | "github.com/newrelic/go-agent/v3/integrations/nrgorilla" 204 | "github.com/newrelic/go-agent/v3/newrelic" 205 | ) 206 | 207 | func NewRouter( 208 | nrapp *newrelic.Application, 209 | ) http.Handler { 210 | router := mux.NewRouter() 211 | router.Use(nrgorilla.Middleware(nrapp)) 212 | 213 | return router 214 | } 215 | 216 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 217 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 218 | } 219 | `, 220 | want: `package router 221 | 222 | import ( 223 | "fmt" 224 | "net/http" 225 | 226 | "github.com/gorilla/mux" 227 | "github.com/newrelic/go-agent/v3/integrations/nrgorilla" 228 | "github.com/newrelic/go-agent/v3/newrelic" 229 | ) 230 | 231 | func NewRouter( 232 | nrapp *newrelic.Application, 233 | ) http.Handler { 234 | router := mux.NewRouter() 235 | router.Use(nrgorilla.Middleware(nrapp)) 236 | 237 | return router 238 | } 239 | 240 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 241 | defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() 242 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 243 | } 244 | `, 245 | }, 246 | { 247 | name: "AutoGenerated", 248 | src: `// Code generated by MockGen. DO NOT EDIT. 249 | 250 | package input 251 | 252 | import ( 253 | "context" 254 | "fmt" 255 | ) 256 | 257 | func MockFunc(ctx context.Context) { 258 | fmt.Println("Hello, playground") 259 | fmt.Println("end function") 260 | } 261 | `, 262 | want: `// Code generated by MockGen. DO NOT EDIT. 263 | 264 | package input 265 | 266 | import ( 267 | "context" 268 | "fmt" 269 | ) 270 | 271 | func MockFunc(ctx context.Context) { 272 | fmt.Println("Hello, playground") 273 | fmt.Println("end function") 274 | } 275 | `, 276 | }, 277 | } 278 | for _, tt := range tests { 279 | tt := tt 280 | t.Run(tt.name, func(t *testing.T) { 281 | t.Parallel() 282 | got, err := Process("", []byte(tt.src)) 283 | if err != nil { 284 | t.Fatal(err) 285 | } 286 | 287 | if diff := cmp.Diff(got, []byte(tt.want)); len(diff) != 0 { 288 | t.Errorf("-got +want %v", diff) 289 | } 290 | }) 291 | } 292 | } 293 | 294 | func Test_genSegName(t *testing.T) { 295 | t.Parallel() 296 | tests := [...]struct { 297 | name string 298 | n, want string 299 | }{ 300 | {name: "Simple", n: "Simple", want: "simple"}, 301 | {name: "Camel", n: "camelCase", want: "camel_case"}, 302 | {name: "Pascal", n: "PascalCase", want: "pascal_case"}, 303 | {name: "HTML", n: "SaveHTML", want: "save_html"}, 304 | } 305 | for _, tt := range tests { 306 | tt := tt 307 | t.Run(tt.name, func(t *testing.T) { 308 | t.Parallel() 309 | if got := toSnake(tt.n); got != tt.want { 310 | t.Errorf("toSnake() = %q, want %q", got, tt.want) 311 | } 312 | }) 313 | } 314 | } 315 | 316 | func Test_parseParams(t *testing.T) { 317 | t.Parallel() 318 | tests := [...]struct { 319 | name string 320 | src string 321 | wantName string 322 | wantType string 323 | }{ 324 | { 325 | name: "Context", 326 | src: ` 327 | package main 328 | 329 | import ( 330 | "context" 331 | ) 332 | 333 | func Hoge(ctx context.Context) {} 334 | `, 335 | wantName: "ctx", wantType: TypeContext, 336 | }, 337 | { 338 | name: "NamedContext", 339 | src: ` 340 | package main 341 | 342 | import ( 343 | c "context" 344 | ) 345 | 346 | func Hoge(ctx c.Context) {} 347 | `, 348 | wantName: "ctx", wantType: TypeContext, 349 | }, 350 | { 351 | name: "Http", 352 | src: ` 353 | package main 354 | 355 | import ( 356 | "net/http" 357 | ) 358 | 359 | func SampleHandler(w http.ResponseWriter, req *http.Request) {} 360 | `, 361 | wantName: "req", wantType: TypeHttpRequest, 362 | }, 363 | 364 | { 365 | name: "NamedHttp", 366 | src: ` 367 | package main 368 | 369 | import ( 370 | h "net/http" 371 | ) 372 | 373 | func SampleHandler(w h.ResponseWriter, req *h.Request) {} 374 | `, 375 | wantName: "req", wantType: TypeHttpRequest, 376 | }, 377 | } 378 | for _, tt := range tests { 379 | tt := tt 380 | t.Run(tt.name, func(t *testing.T) { 381 | t.Parallel() 382 | fs := token.NewFileSet() 383 | f, err := parser.ParseFile(fs, "sample.go", tt.src, parser.Mode(0)) 384 | if err != nil { 385 | t.Fatal(err) 386 | } 387 | var decl *ast.FuncDecl 388 | for _, d := range f.Decls { 389 | if fd, ok := d.(*ast.FuncDecl); ok { 390 | decl = fd 391 | break 392 | } 393 | } 394 | name, gtype := parseParams(f.Imports, decl.Type) 395 | if name != tt.wantName { 396 | t.Errorf("parseParams() name = %q, want %q", name, tt.wantName) 397 | } 398 | if gtype != tt.wantType { 399 | t.Errorf("parseParams() type = %q, want %q", gtype, tt.wantType) 400 | } 401 | }) 402 | } 403 | } 404 | 405 | func Test_getImportName(t *testing.T) { 406 | tests := []struct { 407 | name string 408 | src string 409 | typ string 410 | want string 411 | }{ 412 | { 413 | name: "Context", 414 | src: ` 415 | package main 416 | 417 | import ( 418 | "context" 419 | ) 420 | 421 | func Hoge() {}`, 422 | typ: TypeContext, want: "context", 423 | }, 424 | { 425 | name: "NoContext", 426 | src: ` 427 | package main 428 | 429 | import ( 430 | "net/http" 431 | ) 432 | 433 | func Hoge() {}`, 434 | typ: TypeContext, want: "context", 435 | }, 436 | { 437 | name: "NamedContext", 438 | src: ` 439 | package main 440 | 441 | import ( 442 | c "context" 443 | ) 444 | 445 | func Hoge() {}`, 446 | typ: TypeContext, want: "c", 447 | }, 448 | { 449 | name: "Http", 450 | src: ` 451 | package main 452 | 453 | import ( 454 | "net/http" 455 | ) 456 | 457 | func Hoge() {}`, 458 | typ: TypeHttpRequest, want: "http", 459 | }, 460 | { 461 | name: "NoHttp", 462 | src: ` 463 | package main 464 | 465 | import ( 466 | "context" 467 | ) 468 | 469 | func Hoge() {}`, 470 | typ: TypeHttpRequest, want: "http", 471 | }, 472 | { 473 | name: "NamedHttp", 474 | src: ` 475 | package main 476 | 477 | import ( 478 | myHttp "net/http" 479 | ) 480 | 481 | func Hoge() {}`, 482 | typ: TypeHttpRequest, want: "myHttp", 483 | }, 484 | } 485 | 486 | for _, tt := range tests { 487 | tt := tt 488 | t.Run(tt.name, func(t *testing.T) { 489 | t.Parallel() 490 | fs := token.NewFileSet() 491 | f, err := parser.ParseFile(fs, "sample.go", tt.src, parser.Mode(0)) 492 | if err != nil { 493 | t.Fatal(err) 494 | } 495 | if got := getImportName(f.Imports, tt.typ); got != tt.want { 496 | t.Errorf("getImportName() = %v, want %v", got, tt.want) 497 | } 498 | }) 499 | } 500 | } 501 | -------------------------------------------------------------------------------- /testdata/input/advance.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/newrelic/go-agent/v3/newrelic" 8 | ) 9 | 10 | // no insert because ignore comment. 11 | // nrseg:ignore this is test. 12 | func IgnoreHandler(w http.ResponseWriter, req *http.Request) { 13 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 14 | } 15 | 16 | // no insert because called already. 17 | func AlreadyHandler(w http.ResponseWriter, req *http.Request) { 18 | defer newrelic.FromContext(req.Context()).StartSegment("already_handler").End() 19 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 20 | } 21 | -------------------------------------------------------------------------------- /testdata/input/auto_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | 3 | package input 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | ) 9 | 10 | func MockFunc(ctx context.Context) { 11 | fmt.Println("Hello, playground") 12 | fmt.Println("end function") 13 | } 14 | -------------------------------------------------------------------------------- /testdata/input/basic.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | type S struct{} 10 | 11 | func (s *S) SampleMethod(ctx context.Context) { 12 | fmt.Println("Hello, playground") 13 | fmt.Println("end function") 14 | } 15 | 16 | func SampleFunc(ctx context.Context) { 17 | fmt.Println("Hello, playground") 18 | fmt.Println("end function") 19 | } 20 | 21 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 22 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 23 | } 24 | 25 | // ref: https://github.com/budougumi0617/nrseg/issues/20 26 | func ArgWithouteNameHandler(http.ResponseWriter, *http.Request) { 27 | fmt.Println("issue #20") 28 | } 29 | 30 | // ref: https://github.com/budougumi0617/nrseg/issues/20 31 | func ArgWithouteName(context.Context, string) { 32 | fmt.Println("issue #20") 33 | } 34 | -------------------------------------------------------------------------------- /testdata/input/basic_test.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "testing" 5 | "context" 6 | ) 7 | 8 | func helper(t *testing.T, ctx context.Context) { 9 | } 10 | 11 | func TestS_SampleMethod(t *testing.T) { 12 | } 13 | -------------------------------------------------------------------------------- /testdata/input/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/budougumi0617/nrseg/testdata/input 2 | 3 | go 1.15 4 | 5 | require github.com/newrelic/go-agent/v3 v3.9.0 6 | -------------------------------------------------------------------------------- /testdata/input/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 6 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 8 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 9 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 10 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 12 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 13 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 14 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 15 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 16 | github.com/newrelic/go-agent/v3 v3.9.0 h1:5bcTbdk/Up5cIYIkQjCG92Y+uNoett9wmhuz4kPiFlM= 17 | github.com/newrelic/go-agent/v3 v3.9.0/go.mod h1:1A1dssWBwzB7UemzRU6ZVaGDsI+cEn5/bNxI0wiYlIc= 18 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 19 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 20 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 21 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 22 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 23 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 24 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 25 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 26 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 27 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 28 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 29 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 30 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 31 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 35 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 38 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 39 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 40 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 41 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 42 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 43 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 44 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 45 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 46 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 47 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 48 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 49 | google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= 50 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 51 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 52 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 53 | -------------------------------------------------------------------------------- /testdata/input/ignore/must_not_change.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | type MustNotChange struct{} 10 | 11 | func (m *MustNotChange) SampleMethod(ctx context.Context) { 12 | fmt.Println("Hello, playground") 13 | fmt.Println("end function") 14 | } 15 | 16 | func SampleFunc(ctx context.Context) { 17 | fmt.Println("Hello, playground") 18 | fmt.Println("end function") 19 | } 20 | 21 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 22 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 23 | } 24 | -------------------------------------------------------------------------------- /testdata/input/testdata/must_not_change.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | type MustNotChange struct{} 10 | 11 | func (m *MustNotChange) SampleMethod(ctx context.Context) { 12 | fmt.Println("Hello, playground") 13 | fmt.Println("end function") 14 | } 15 | 16 | func SampleFunc(ctx context.Context) { 17 | fmt.Println("Hello, playground") 18 | fmt.Println("end function") 19 | } 20 | 21 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 22 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 23 | } 24 | -------------------------------------------------------------------------------- /testdata/want/advance.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/newrelic/go-agent/v3/newrelic" 8 | ) 9 | 10 | // no insert because ignore comment. 11 | // nrseg:ignore this is test. 12 | func IgnoreHandler(w http.ResponseWriter, req *http.Request) { 13 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 14 | } 15 | 16 | // no insert because called already. 17 | func AlreadyHandler(w http.ResponseWriter, req *http.Request) { 18 | defer newrelic.FromContext(req.Context()).StartSegment("already_handler").End() 19 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 20 | } 21 | -------------------------------------------------------------------------------- /testdata/want/auto_generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | 3 | package input 4 | 5 | import ( 6 | "context" 7 | "fmt" 8 | ) 9 | 10 | func MockFunc(ctx context.Context) { 11 | fmt.Println("Hello, playground") 12 | fmt.Println("end function") 13 | } 14 | -------------------------------------------------------------------------------- /testdata/want/basic.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/newrelic/go-agent/v3/newrelic" 9 | ) 10 | 11 | type S struct{} 12 | 13 | func (s *S) SampleMethod(ctx context.Context) { 14 | defer newrelic.FromContext(ctx).StartSegment("s_sample_method").End() 15 | fmt.Println("Hello, playground") 16 | fmt.Println("end function") 17 | } 18 | 19 | func SampleFunc(ctx context.Context) { 20 | defer newrelic.FromContext(ctx).StartSegment("sample_func").End() 21 | fmt.Println("Hello, playground") 22 | fmt.Println("end function") 23 | } 24 | 25 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 26 | defer newrelic.FromContext(req.Context()).StartSegment("sample_handler").End() 27 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 28 | } 29 | 30 | // ref: https://github.com/budougumi0617/nrseg/issues/20 31 | func ArgWithouteNameHandler(http.ResponseWriter, *http.Request) { 32 | fmt.Println("issue #20") 33 | } 34 | 35 | // ref: https://github.com/budougumi0617/nrseg/issues/20 36 | func ArgWithouteName(context.Context, string) { 37 | fmt.Println("issue #20") 38 | } 39 | -------------------------------------------------------------------------------- /testdata/want/basic_test.go: -------------------------------------------------------------------------------- 1 | package input 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func helper(t *testing.T, ctx context.Context) { 9 | } 10 | 11 | func TestS_SampleMethod(t *testing.T) { 12 | } 13 | -------------------------------------------------------------------------------- /testdata/want/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/budougumi0617/nrseg/testdata/want 2 | 3 | go 1.15 4 | 5 | require github.com/newrelic/go-agent/v3 v3.9.0 // indirect 6 | -------------------------------------------------------------------------------- /testdata/want/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 6 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 8 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 9 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 10 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 12 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 13 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 14 | github.com/newrelic/go-agent v1.11.0 h1:jnd8+H6dB+93UTJHFT1wJoij5spKNN/xZ0nkw0kvt7o= 15 | github.com/newrelic/go-agent v3.9.0+incompatible h1:W2Zummx9jNATZVX6QVjYksX1TwxJGf4E6x1Wf3CG/jY= 16 | github.com/newrelic/go-agent/v3 v3.9.0 h1:5bcTbdk/Up5cIYIkQjCG92Y+uNoett9wmhuz4kPiFlM= 17 | github.com/newrelic/go-agent/v3 v3.9.0/go.mod h1:1A1dssWBwzB7UemzRU6ZVaGDsI+cEn5/bNxI0wiYlIc= 18 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 19 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 20 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 21 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 22 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 23 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 24 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 25 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 26 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 27 | golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 28 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 29 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 30 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 31 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 32 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 35 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 38 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 39 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 40 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 41 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 42 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 43 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 44 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 45 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= 46 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 47 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 48 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 49 | google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= 50 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 51 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 52 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 53 | -------------------------------------------------------------------------------- /testdata/want/ignore/must_not_change.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | type MustNotChange struct{} 10 | 11 | func (m *MustNotChange) SampleMethod(ctx context.Context) { 12 | fmt.Println("Hello, playground") 13 | fmt.Println("end function") 14 | } 15 | 16 | func SampleFunc(ctx context.Context) { 17 | fmt.Println("Hello, playground") 18 | fmt.Println("end function") 19 | } 20 | 21 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 22 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 23 | } 24 | -------------------------------------------------------------------------------- /testdata/want/testdata/must_not_change.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | ) 8 | 9 | type MustNotChange struct{} 10 | 11 | func (m *MustNotChange) SampleMethod(ctx context.Context) { 12 | fmt.Println("Hello, playground") 13 | fmt.Println("end function") 14 | } 15 | 16 | func SampleFunc(ctx context.Context) { 17 | fmt.Println("Hello, playground") 18 | fmt.Println("end function") 19 | } 20 | 21 | func SampleHandler(w http.ResponseWriter, req *http.Request) { 22 | fmt.Fprintf(w, "Hello, %q", req.URL.Path) 23 | } 24 | --------------------------------------------------------------------------------