├── test ├── spf13 │ ├── .gitignore │ ├── .spf13.yml │ ├── LICENSE │ ├── main.go │ ├── go.mod │ └── root.go └── restart │ ├── restart_windows.go │ ├── restart.go │ └── main.go ├── sigpipe_windows.go ├── testdata ├── log.toml ├── log.json ├── cert.pem └── key.pem ├── stopsig_windows.go ├── AUTHORS ├── stopsig.go ├── logfile_windows.go ├── logfile.go ├── graceful_test.go ├── .gitignore ├── .github ├── ISSUE_TEMPLATE │ ├── issue.md │ └── bug_report.md └── workflows │ ├── release.yaml │ └── ci.yaml ├── graceful_unix_test.go ├── graceful_windows.go ├── reqid_test.go ├── idgen_test.go ├── systemd_test.go ├── graceful.go ├── reqid.go ├── systemd.go ├── doc.go ├── go.mod ├── LICENSE ├── signal.go ├── RELEASE.md ├── default.go ├── sigpipe.go ├── env_test.go ├── jsonlog.go ├── server_test.go ├── exec_test.go ├── server.go ├── example_test.go ├── log_test.go ├── exec.go ├── env.go ├── log.go ├── idgen.go ├── README.md ├── CHANGELOG.md ├── graceful_unix.go ├── DESIGN.md ├── http_test.go ├── http_17.go ├── http.go └── go.sum /test/spf13/.gitignore: -------------------------------------------------------------------------------- 1 | /spf13 2 | -------------------------------------------------------------------------------- /sigpipe_windows.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | func handleSigPipe() {} 4 | -------------------------------------------------------------------------------- /test/spf13/.spf13.yml: -------------------------------------------------------------------------------- 1 | other_key: 1 2 | 3 | log: 4 | level: error 5 | format: json 6 | -------------------------------------------------------------------------------- /testdata/log.toml: -------------------------------------------------------------------------------- 1 | [log] 2 | filename = "/abc/def" 3 | level = "debug" 4 | format = "json" 5 | -------------------------------------------------------------------------------- /stopsig_windows.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import "os" 4 | 5 | var stopSignals = []os.Signal{os.Interrupt} 6 | -------------------------------------------------------------------------------- /testdata/log.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "filename": "/abc/def", 4 | "level": "debug", 5 | "format": "json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | This project is originally created by employees of Cybozu. 2 | 3 | Here is a list of contributors to this project: 4 | 5 | * Yasuhiro Matsumoto (@mattn) 6 | * Jernej Jakob (@jjakob) 7 | -------------------------------------------------------------------------------- /test/restart/restart_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package main 5 | 6 | import "time" 7 | 8 | func restart() { 9 | time.Sleep(10 * time.Millisecond) 10 | } 11 | -------------------------------------------------------------------------------- /stopsig.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | ) 10 | 11 | var stopSignals = []os.Signal{syscall.SIGINT, syscall.SIGTERM} 12 | -------------------------------------------------------------------------------- /logfile_windows.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | func openLogFile(filename string) (io.Writer, error) { 9 | return os.OpenFile(filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 10 | } 11 | -------------------------------------------------------------------------------- /test/restart/restart.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func restart() { 13 | syscall.Kill(os.Getpid(), syscall.SIGHUP) 14 | time.Sleep(100 * time.Millisecond) 15 | } 16 | -------------------------------------------------------------------------------- /logfile.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import ( 7 | "io" 8 | "syscall" 9 | 10 | "github.com/cybozu-go/log" 11 | ) 12 | 13 | func openLogFile(filename string) (io.Writer, error) { 14 | return log.NewFileReopener(filename, syscall.SIGUSR1) 15 | } 16 | -------------------------------------------------------------------------------- /graceful_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import "testing" 7 | 8 | func TestGraceful(t *testing.T) { 9 | t.Skip(`Graceful cannot be tested by go test as it executes 10 | itself in another process of go test. 11 | Instead, we test Graceful in a test program under "test/graceful".`) 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Editors 7 | *~ 8 | .*.swp 9 | .#* 10 | \#*# 11 | 12 | # Folders 13 | _obj 14 | _test 15 | 16 | # Architecture specific extensions/prefixes 17 | *.[568vq] 18 | [568vq].out 19 | 20 | *.cgo1.go 21 | *.cgo2.c 22 | _cgo_defun.c 23 | _cgo_gotypes.go 24 | _cgo_export.* 25 | 26 | _testmain.go 27 | 28 | *.exe 29 | *.test 30 | *.prof 31 | 32 | /restart 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task 3 | about: Describe this issue 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## What 11 | 12 | Describe what this issue should address. 13 | 14 | ## How 15 | 16 | Describe how to address the issue. 17 | 18 | ## Checklist 19 | 20 | - [ ] Finish implementation of the issue 21 | - [ ] Test all functions 22 | - [ ] Have enough logs to trace activities 23 | - [ ] Notify developers of necessary actions 24 | -------------------------------------------------------------------------------- /graceful_unix_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import ( 7 | "net" 8 | "runtime" 9 | "testing" 10 | ) 11 | 12 | func TestListenerFiles(t *testing.T) { 13 | if runtime.GOOS == "windows" { 14 | t.Skip("windows doesn't support FileListener") 15 | } 16 | t.Parallel() 17 | 18 | ln, err := net.Listen("tcp", "localhost:18555") 19 | if err != nil { 20 | t.Skip(err) 21 | } 22 | defer ln.Close() 23 | 24 | fl, err := listenerFiles([]net.Listener{ln}) 25 | if err != nil { 26 | t.Error(err) 27 | } 28 | if len(fl) != 1 { 29 | t.Error(`len(fl) != 1`) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /graceful_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package well 5 | 6 | import "net" 7 | 8 | func isMaster() bool { 9 | return true 10 | } 11 | 12 | // SystemdListeners returns (nil, nil) on Windows. 13 | func SystemdListeners() ([]net.Listener, error) { 14 | return nil, nil 15 | } 16 | 17 | // Run simply calls g.Listen then g.Serve on Windows. 18 | func (g *Graceful) Run() { 19 | env := g.Env 20 | if env == nil { 21 | env = defaultEnv 22 | } 23 | 24 | // prepare listener files 25 | listeners, err := g.Listen() 26 | if err != nil { 27 | env.Cancel(err) 28 | return 29 | } 30 | g.Serve(listeners) 31 | } 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Environments** 14 | - Version: 15 | - OS: 16 | 17 | **To Reproduce** 18 | Steps to reproduce the behavior: 19 | 1. Go to '...' 20 | 2. Click on '....' 21 | 3. Scroll down to '....' 22 | 4. See error 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen. 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | release: 8 | name: Release on GitHub 9 | runs-on: ubuntu-22.04 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Create release 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | run: | 16 | version=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g") 17 | if echo $version | grep -q -e '-'; then prerelease=-p; fi 18 | gh release create $version $prerelease \ 19 | -t "Release $version" \ 20 | -n "See [CHANGELOG.md](./CHANGELOG.md) for details." 21 | -------------------------------------------------------------------------------- /reqid_test.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | func TestWithRequestID(t *testing.T) { 9 | t.Parallel() 10 | 11 | ctx := WithRequestID(context.Background(), "abc") 12 | if v := ctx.Value(RequestIDContextKey); v == nil { 13 | t.Error(`v == nil`) 14 | } else { 15 | if v != "abc" { 16 | t.Error(`v != "abc"`) 17 | } 18 | } 19 | } 20 | 21 | func TestBackgroundWithID(t *testing.T) { 22 | t.Parallel() 23 | 24 | ctx := WithRequestID(context.Background(), "abc") 25 | ctx = BackgroundWithID(ctx) 26 | if v := ctx.Value(RequestIDContextKey); v == nil { 27 | t.Error(`v == nil`) 28 | } else { 29 | if v != "abc" { 30 | t.Error(`v != "abc"`) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /idgen_test.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import "testing" 4 | 5 | func TestIDGenerator(t *testing.T) { 6 | t.Parallel() 7 | 8 | g := NewIDGenerator() 9 | if len(g.Generate()) != 36 { 10 | t.Error(len(g.Generate()) != 36) 11 | } 12 | 13 | g2 := &IDGenerator{ 14 | [16]byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 15 | 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xff}, 16 | 0xf0e0d0c001020304, 17 | } 18 | if g2.Generate() != "05020002-c4d5-e6f7-8090-a0b0c0d0e0ff" { 19 | t.Error(`g2.Generate() != "05020002-c4d5-e6f7-8090-a0b0c0d0e0ff"`) 20 | } 21 | } 22 | 23 | func BenchmarkIDGenerator(b *testing.B) { 24 | g := NewIDGenerator() 25 | b.ResetTimer() 26 | for i := 0; i < b.N; i++ { 27 | g.Generate() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /systemd_test.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | ) 8 | 9 | func TestIsSystemdService(t *testing.T) { 10 | t.Parallel() 11 | 12 | if runtime.GOOS != "linux" { 13 | if IsSystemdService() { 14 | t.Error(`IsSystemdService()`) 15 | } 16 | return 17 | } 18 | 19 | if os.Getenv("GITHUB_ACTIONS") == "true" { 20 | if !IsSystemdService() { 21 | t.Error(`GitHub Actions is run as a systemd service`) 22 | } 23 | return 24 | } 25 | 26 | if IsSystemdService() { 27 | t.Error(`IsSystemdService()`) 28 | } 29 | 30 | err := os.Setenv("JOURNAL_STREAM", "10:20") 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | if !IsSystemdService() { 36 | t.Error(`!IsSystemdService()`) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /graceful.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | // Graceful is a struct to implement graceful restart servers. 9 | // 10 | // On Windows, this is just a dummy to make porting easy. 11 | type Graceful struct { 12 | // Listen is a function to create listening sockets. 13 | // This function is called in the master process. 14 | Listen func() ([]net.Listener, error) 15 | 16 | // Serve is a function to accept connections from listeners. 17 | // This function is called in child processes. 18 | // In case of errors, use os.Exit to exit. 19 | Serve func(listeners []net.Listener) 20 | 21 | // ExitTimeout is duration before Run gives up waiting for 22 | // a child to exit. Zero disables timeout. 23 | ExitTimeout time.Duration 24 | 25 | // Env is the environment for the master process. 26 | // If nil, the global environment is used. 27 | Env *Environment 28 | } 29 | -------------------------------------------------------------------------------- /reqid.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import "context" 4 | 5 | // This file provides utilities for request ID. 6 | // Request ID is passed as context values. 7 | 8 | type contextKey string 9 | 10 | const ( 11 | // RequestIDContextKey is a context key for request ID. 12 | RequestIDContextKey contextKey = "request_id" 13 | ) 14 | 15 | func (k contextKey) String() string { 16 | return "well: context key: " + string(k) 17 | } 18 | 19 | // WithRequestID returns a new context with a request ID as a value. 20 | func WithRequestID(ctx context.Context, reqid string) context.Context { 21 | return context.WithValue(ctx, RequestIDContextKey, reqid) 22 | } 23 | 24 | // BackgroundWithID returns a new background context with an existing 25 | // request ID in ctx, if any. 26 | func BackgroundWithID(ctx context.Context) context.Context { 27 | id := ctx.Value(RequestIDContextKey) 28 | ctx = context.Background() 29 | if id == nil { 30 | return ctx 31 | } 32 | return WithRequestID(ctx, id.(string)) 33 | } 34 | -------------------------------------------------------------------------------- /systemd.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "runtime" 7 | "strings" 8 | ) 9 | 10 | // IsSystemdService returns true if the program runs as a systemd service. 11 | func IsSystemdService() bool { 12 | if runtime.GOOS != "linux" { 13 | return false 14 | } 15 | 16 | // https://www.freedesktop.org/software/systemd/man/systemd.exec.html#%24JOURNAL_STREAM 17 | if len(os.Getenv("JOURNAL_STREAM")) > 0 { 18 | return true 19 | } 20 | 21 | f, err := os.Open("/proc/self/cgroup") 22 | if err != nil { 23 | return false 24 | } 25 | defer f.Close() 26 | 27 | sc := bufio.NewScanner(f) 28 | isService := false 29 | for sc.Scan() { 30 | fields := strings.Split(sc.Text(), ":") 31 | if len(fields) < 3 { 32 | continue 33 | } 34 | if fields[1] != "name=systemd" { 35 | continue 36 | } 37 | isService = strings.HasSuffix(fields[2], ".service") 38 | break 39 | } 40 | if err := sc.Err(); err != nil { 41 | return false 42 | } 43 | 44 | return isService 45 | } 46 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package well provides a framework that helps implementation of commands. 3 | 4 | Deprecated: This project has come to an end and will not receive any update. 5 | 6 | Features: 7 | 8 | Better logging: 9 | 10 | By using github.com/cybozu-go/log package, logs can be structured 11 | in JSON or logfmt format. HTTP servers log accesses automatically. 12 | 13 | Graceful exit: 14 | 15 | The framework provides functions to manage goroutines and 16 | network server implementation that can be shutdown gracefully. 17 | 18 | Signal handlers: 19 | 20 | The framework installs SIGINT/SIGTERM signal handlers for 21 | graceful exit, and SIGUSR1 signal handler to reopen log files. 22 | 23 | Environment: 24 | 25 | Environment is the heart of the framework. It provides a base 26 | context.Context that will be canceled before program stops, and 27 | methods to manage goroutines. 28 | 29 | To use the framework easily, the framework provides an instance of 30 | Environment as the default, and functions to work with it. 31 | */ 32 | package well 33 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cybozu-go/well 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/BurntSushi/toml v1.2.1 7 | github.com/cybozu-go/log v1.7.0 8 | github.com/cybozu-go/netutil v1.4.4 9 | github.com/spf13/pflag v1.0.5 10 | github.com/spf13/viper v1.15.0 11 | golang.org/x/net v0.7.0 12 | ) 13 | 14 | require ( 15 | github.com/fsnotify/fsnotify v1.6.0 // indirect 16 | github.com/hashicorp/hcl v1.0.0 // indirect 17 | github.com/magiconair/properties v1.8.7 // indirect 18 | github.com/mitchellh/mapstructure v1.5.0 // indirect 19 | github.com/onsi/gomega v1.25.0 // indirect 20 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 21 | github.com/spf13/afero v1.9.3 // indirect 22 | github.com/spf13/cast v1.5.0 // indirect 23 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 24 | github.com/subosito/gotenv v1.4.2 // indirect 25 | github.com/vishvananda/netlink v1.2.1-beta.2 // indirect 26 | github.com/vishvananda/netns v0.0.3 // indirect 27 | golang.org/x/sys v0.5.0 // indirect 28 | golang.org/x/text v0.7.0 // indirect 29 | gopkg.in/ini.v1 v1.67.0 // indirect 30 | gopkg.in/yaml.v3 v3.0.1 // indirect 31 | ) 32 | -------------------------------------------------------------------------------- /test/spf13/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2018 Cybozu 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - 'main' 7 | env: 8 | go-version: 1.19 9 | jobs: 10 | test: 11 | name: Small tests 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-go@v3 16 | with: 17 | go-version: ${{ env.go-version }} 18 | - run: test -z "$(gofmt -s -l . | tee /dev/stderr)" 19 | - run: GOOS=windows go build . 20 | - run: go build . 21 | - run: go test -race -v . 22 | - run: go vet . 23 | - name: staticcheck 24 | run: | 25 | mkdir -p bin 26 | GOBIN=$(pwd)/bin go install honnef.co/go/tools/cmd/staticcheck@latest 27 | ./bin/staticcheck . 28 | - name: Restart test 29 | run: | 30 | go vet ./test/restart 31 | ./bin/staticcheck ./test/restart 32 | go build ./test/restart 33 | CANCELLATION_DELAY_SECONDS=0 ./restart 34 | - name: Config test 35 | run: | 36 | cd ./test/spf13 37 | go vet . 38 | ../../bin/staticcheck . 39 | go build . 40 | ./spf13 --config=.spf13.yml --loglevel=critical 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Cybozu and contributors. 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. 22 | -------------------------------------------------------------------------------- /test/spf13/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Cybozu 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package main 22 | 23 | func main() { 24 | Execute() 25 | } 26 | -------------------------------------------------------------------------------- /test/spf13/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cybozu-go/well/test/spf13 2 | 3 | go 1.19 4 | 5 | replace github.com/cybozu-go/well => ../.. 6 | 7 | require ( 8 | github.com/cybozu-go/log v1.7.0 9 | github.com/cybozu-go/well v1.11.1 10 | github.com/spf13/cobra v1.6.1 11 | github.com/spf13/viper v1.15.0 12 | ) 13 | 14 | require ( 15 | github.com/cybozu-go/netutil v1.4.4 // indirect 16 | github.com/fsnotify/fsnotify v1.6.0 // indirect 17 | github.com/hashicorp/hcl v1.0.0 // indirect 18 | github.com/inconshreveable/mousetrap v1.0.1 // indirect 19 | github.com/magiconair/properties v1.8.7 // indirect 20 | github.com/mitchellh/mapstructure v1.5.0 // indirect 21 | github.com/onsi/gomega v1.25.0 // indirect 22 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 23 | github.com/spf13/afero v1.9.3 // indirect 24 | github.com/spf13/cast v1.5.0 // indirect 25 | github.com/spf13/jwalterweatherman v1.1.0 // indirect 26 | github.com/spf13/pflag v1.0.5 // indirect 27 | github.com/subosito/gotenv v1.4.2 // indirect 28 | github.com/vishvananda/netlink v1.2.1-beta.2 // indirect 29 | github.com/vishvananda/netns v0.0.3 // indirect 30 | golang.org/x/net v0.7.0 // indirect 31 | golang.org/x/sys v0.5.0 // indirect 32 | golang.org/x/text v0.7.0 // indirect 33 | gopkg.in/ini.v1 v1.67.0 // indirect 34 | gopkg.in/yaml.v3 v3.0.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /signal.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "os/signal" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/cybozu-go/log" 11 | ) 12 | 13 | var ( 14 | errSignaled = errors.New("signaled") 15 | 16 | cancellationDelaySecondsEnv = "CANCELLATION_DELAY_SECONDS" 17 | 18 | defaultCancellationDelaySeconds = 5 19 | ) 20 | 21 | // IsSignaled returns true if err returned by Wait indicates that 22 | // the program has received SIGINT or SIGTERM. 23 | func IsSignaled(err error) bool { 24 | return err == errSignaled 25 | } 26 | 27 | // handleSignal runs independent goroutine to cancel an environment. 28 | func handleSignal(env *Environment) { 29 | ch := make(chan os.Signal, 2) 30 | signal.Notify(ch, stopSignals...) 31 | 32 | go func() { 33 | s := <-ch 34 | delay := getDelaySecondsFromEnv() 35 | log.Warn("well: got signal", map[string]interface{}{ 36 | "signal": s.String(), 37 | "delay": delay, 38 | }) 39 | time.Sleep(time.Duration(delay) * time.Second) 40 | env.Cancel(errSignaled) 41 | }() 42 | } 43 | 44 | func getDelaySecondsFromEnv() int { 45 | delayStr := os.Getenv(cancellationDelaySecondsEnv) 46 | if len(delayStr) == 0 { 47 | return defaultCancellationDelaySeconds 48 | } 49 | 50 | delay, err := strconv.Atoi(delayStr) 51 | if err != nil { 52 | log.Warn("well: set default cancellation delay seconds", map[string]interface{}{ 53 | "env": delayStr, 54 | "delay": defaultCancellationDelaySeconds, 55 | log.FnError: err, 56 | }) 57 | return defaultCancellationDelaySeconds 58 | } 59 | if delay < 0 { 60 | log.Warn("well: round up negative cancellation delay seconds to 0s", map[string]interface{}{ 61 | "env": delayStr, 62 | "delay": 0, 63 | }) 64 | return 0 65 | } 66 | return delay 67 | } 68 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | Release procedure 2 | ================= 3 | 4 | This document describes how to release a new version of well. 5 | 6 | Versioning 7 | ---------- 8 | 9 | Follow [semantic versioning 2.0.0][semver] to choose the new version number. 10 | 11 | Prepare change log entries 12 | -------------------------- 13 | 14 | Add notable changes since the last release to [CHANGELOG.md](CHANGELOG.md). 15 | It should look like: 16 | 17 | ```markdown 18 | (snip) 19 | ## [Unreleased] 20 | 21 | ### Added 22 | - Implement ... (#35) 23 | 24 | ### Changed 25 | - Fix a bug in ... (#33) 26 | 27 | ### Removed 28 | - Deprecated `-option` is removed ... (#39) 29 | 30 | (snip) 31 | ``` 32 | 33 | Bump version 34 | ------------ 35 | 36 | 1. Determine a new version number. Then set `VERSION` variable. 37 | 38 | ```console 39 | # Set VERSION and confirm it. It should not have "v" prefix. 40 | $ VERSION=x.y.z 41 | $ echo $VERSION 42 | ``` 43 | 44 | 2. Make a branch to release 45 | 46 | ```console 47 | $ git neco dev "bump-$VERSION" 48 | ``` 49 | 50 | 3. Edit `CHANGELOG.md` for the new version ([example][]). 51 | 4. Commit the change and push it. 52 | 53 | ```console 54 | $ git commit -a -m "Bump version to $VERSION" 55 | $ git neco review 56 | ``` 57 | 58 | 5. Merge this branch. 59 | 6. Add a git tag to the main HEAD, then push it. 60 | 61 | ```console 62 | # Set VERSION again. 63 | $ VERSION=x.y.z 64 | $ echo $VERSION 65 | 66 | $ git checkout main 67 | $ git pull 68 | $ git tag -a -m "Release v$VERSION" "v$VERSION" 69 | 70 | # Make sure the release tag exists. 71 | $ git tag -ln | grep $VERSION 72 | 73 | $ git push origin "v$VERSION" 74 | ``` 75 | 76 | GitHub actions will build and push artifacts such as container images and 77 | create a new GitHub release. 78 | 79 | [semver]: https://semver.org/spec/v2.0.0.html 80 | [example]: https://github.com/cybozu-go/etcdpasswd/commit/77d95384ac6c97e7f48281eaf23cb94f68867f79 81 | -------------------------------------------------------------------------------- /testdata/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFszCCA5ugAwIBAgIUGeNdB/5nvN0YfoZRiB6X3aPlkhAwDQYJKoZIhvcNAQEL 3 | BQAwaDELMAkGA1UEBhMCSlAxDjAMBgNVBAgMBVRva3lvMQ8wDQYDVQQKDAZDeWJv 4 | enUxGDAWBgNVBAMMD2Zvby5leGFtcGxlLmNvbTEeMBwGCSqGSIb3DQEJARYPYmFy 5 | QGV4YW1wbGUuY29tMCAXDTE5MDkyMzEyMzczNVoYDzIxMTkwODMwMTIzNzM1WjBo 6 | MQswCQYDVQQGEwJKUDEOMAwGA1UECAwFVG9reW8xDzANBgNVBAoMBkN5Ym96dTEY 7 | MBYGA1UEAwwPZm9vLmV4YW1wbGUuY29tMR4wHAYJKoZIhvcNAQkBFg9iYXJAZXhh 8 | bXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCoVTn8zdqE 9 | Oozk62gfeNa2bFkhqIuunaTwlNEhT9eFXyM0mR5D+KB2F2IYGYm5eTJkvPvVbIKW 10 | 9RhgqxgaVem+2tmN9NxJfR/99pW012W/Nc5wqmYEfDgRc470rUn7204DXtPo7Ppu 11 | 1cIXBBx/Qb12BjqQ1iOzoZfxlezMmDgkL0XMzvy2Qlzs4m/CNcy8wTCsMQ7+gmQ1 12 | olGVVOKgnIH6MgmYsyt6oxNwFbPVtJzyxdtiGfDY9xiruYDRQ7w17IE96CTOSMq/ 13 | KxGAKGhat+0MFh6gnF465n4juoiY4XddgXcTZQfaDlDpycI+x1jdk4vWLW2n6EMo 14 | Qad/btJRLiQXaNuZbWgiuKX/dNfkwqEhZnPx3yVHWQ6LA2xu6Vi2EiBALIQIa4Ic 15 | jVA1Ai8Sh0MCgy8Aw+l3Z+ArSsQ7ptouPw5TUiWrowGfA08AAJVmKlqwVMb0Da1r 16 | /MfBJxK05fG5mBQzo793NxCGoZntgZmHJSmM4ZauD4DI/s4U4qhCAkIit2ZjTiGo 17 | seO/q+PXg04C6/9EaFb2shDhPzcEqvcXSrogHqPOIAkFiUbRrTia3faoINfOE3lv 18 | hFIpYYY7wW+hvfIF4yr4UOqNB1hsuDSb9sReEnQzNMyU3BLC8VMYc1c1LBDptOCh 19 | bR0wc8fFAe80qILT7d6wRuvLSZhp9vY2+QIDAQABo1MwUTAdBgNVHQ4EFgQUuci/ 20 | 1/8TPznsIUQwY1HNLpalBOYwHwYDVR0jBBgwFoAUuci/1/8TPznsIUQwY1HNLpal 21 | BOYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEADwjEsBbe/3Jo 22 | aWUY24cLmpVfOPK6CyHG8TfBfEXucuFZqT8+ZrmoUhaQaOu5WFgMRSUVUPlUStKi 23 | mV/bB4D/zrQf3qkOQVR2JPxyimzhO66gx46IesHVhEdW1peIlwl8ItHZPnJyXu7x 24 | OwMtJ9yxQrm/1/zsv0ZMFMTDo41Ob1ZLvuvj/+jXQFDSIwqSgu8SuTuu81BCFl8w 25 | e5YEYT35eUXqzPTDUC9pqa8NxtIZ4YFdY3vHOXYCiNige39buoez4cTJU5cdlE/C 26 | fTMklpSQfvmylazhUfAYImwuu513Yjx+PJ+9I8oh2XhHiuZUl/h5olWc3cD5jK4E 27 | XR1FIEMrLq0MTxS0p+SpSXMQHIQJBXNCBLkq1JPzdYPmaGcEIsgeGSzbE4siMF2y 28 | oF0pVy1BiX2tiHOLx7ypA40GGaGNl9T25wB/5UuKlPHj80+KkYZExlx28xzove3h 29 | /5jvr/Zmz5NA4lxJNZlXA3X2oTht1TYpsyU5mfGw3r4S8AuOvGmThsohlNAVRLWX 30 | qVF4oYiQjsFsvEbmIeRF7ncBQQ8dxfPA17knvA9T5beEztQyKDQO9iLhxpUabZfk 31 | pkQZjmQtuVag/NN7ON1cRGvr6Sx+zebNyxh4H+bTgi6n7j+nzD6gBdd22ZxFVKzm 32 | 8E88FXHrPC5n/yne2ik0pGz7ZOCwjTE= 33 | -----END CERTIFICATE----- 34 | -------------------------------------------------------------------------------- /default.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import "context" 4 | 5 | var ( 6 | defaultEnv *Environment 7 | ) 8 | 9 | func init() { 10 | defaultEnv = NewEnvironment(context.Background()) 11 | handleSignal(defaultEnv) 12 | handleSigPipe() 13 | } 14 | 15 | // Stop just declares no further Go will be called. 16 | // 17 | // Calling Stop is optional if and only if Cancel is guaranteed 18 | // to be called at some point. For instance, if the program runs 19 | // until SIGINT or SIGTERM, Stop is optional. 20 | func Stop() { 21 | defaultEnv.Stop() 22 | } 23 | 24 | // Cancel cancels the base context of the global environment. 25 | // 26 | // Passed err will be returned by Wait(). 27 | // Once canceled, Go() will not start new goroutines. 28 | // 29 | // Note that calling Cancel(nil) is perfectly valid. 30 | // Unlike Stop(), Cancel(nil) cancels the base context and can 31 | // gracefully stop goroutines started by Server.Serve or 32 | // HTTPServer.ListenAndServe. 33 | // 34 | // This returns true if the caller is the first that calls Cancel. 35 | // For second and later calls, Cancel does nothing and returns false. 36 | func Cancel(err error) bool { 37 | return defaultEnv.Cancel(err) 38 | } 39 | 40 | // Wait waits for Stop or Cancel, and for all goroutines started by 41 | // Go to finish. 42 | // 43 | // The returned err is the one passed to Cancel, or nil. 44 | // err can be tested by IsSignaled to determine whether the 45 | // program got SIGINT or SIGTERM. 46 | func Wait() error { 47 | return defaultEnv.Wait() 48 | } 49 | 50 | // Go starts a goroutine that executes f in the global environment. 51 | // 52 | // f takes a drived context from the base context. The context 53 | // will be canceled when f returns. 54 | // 55 | // Goroutines started by this function will be waited for by 56 | // Wait until all such goroutines return. 57 | // 58 | // If f returns non-nil error, Cancel is called immediately 59 | // with that error. 60 | // 61 | // f should watch ctx.Done() channel and return quickly when the 62 | // channel is closed. 63 | func Go(f func(ctx context.Context) error) { 64 | defaultEnv.Go(f) 65 | } 66 | 67 | // GoWithID calls Go with a context having a new request tracking ID. 68 | func GoWithID(f func(ctx context.Context) error) { 69 | defaultEnv.GoWithID(f) 70 | } 71 | -------------------------------------------------------------------------------- /sigpipe.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import ( 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | ) 11 | 12 | // handleSigPipe discards SIGPIPE if the program is running 13 | // as a systemd service. 14 | // 15 | // Background: 16 | // 17 | // systemd interprets programs exited with SIGPIPE as 18 | // "successfully exited" because its default SuccessExitStatus= 19 | // includes SIGPIPE. 20 | // https://www.freedesktop.org/software/systemd/man/systemd.service.html 21 | // 22 | // Normal Go programs ignore SIGPIPE for file descriptors other than 23 | // stdout(1) and stderr(2). If SIGPIPE is raised from stdout or stderr, 24 | // Go programs exit with a SIGPIPE signal. 25 | // https://golang.org/pkg/os/signal/#hdr-SIGPIPE 26 | // 27 | // journald is a service tightly integrated in systemd. Go programs 28 | // running as a systemd service will normally connect their stdout and 29 | // stderr pipes to journald. Unfortunately though, journald often 30 | // dies and restarts due to bugs, and once it restarts, programs 31 | // whose stdout and stderr were connected to journald will receive 32 | // SIGPIPE at their next writes to stdout or stderr. 33 | // 34 | // Combined these specifications and problems all together, Go programs 35 | // running as systemd services often die with SIGPIPE, but systemd will 36 | // not restart them as they "successfully exited" except when the service 37 | // is configured with SuccessExitStatus= without SIGPIPE or Restart=always. 38 | // 39 | // If we catch SIGPIPE and exits abnormally, systemd would restart the 40 | // program. However, if we call signal.Notify(c, syscall.SIGPIPE), 41 | // SIGPIPE would be raised not only for stdout and stderr but also for 42 | // other file descriptors. This means that programs that make network 43 | // connections will get a lot of SIGPIPEs and die. Of course, this is 44 | // not acceptable. 45 | // 46 | // Therefore, we just catch SIGPIPEs and drop them if the program 47 | // runs as a systemd service. This way, we can detect journald restarts 48 | // by checking the errors from os.Stdout.Write or os.Stderr.Write. 49 | // This check is mainly done in our logger, cybozu-go/log. 50 | func handleSigPipe() { 51 | if !IsSystemdService() { 52 | return 53 | } 54 | 55 | // signal.Ignore does NOT ignore signals; instead, it just stop 56 | // relaying signals to the channel. Instead, we set a nop handler. 57 | c := make(chan os.Signal, 1) 58 | signal.Notify(c, syscall.SIGPIPE) 59 | } 60 | -------------------------------------------------------------------------------- /env_test.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | ) 8 | 9 | func TestEnvironmentStop(t *testing.T) { 10 | t.Parallel() 11 | 12 | env := NewEnvironment(context.Background()) 13 | waitCh := make(chan struct{}) 14 | 15 | env.Go(func(ctx context.Context) error { 16 | return nil 17 | }) 18 | env.Go(func(ctx context.Context) error { 19 | <-waitCh 20 | return nil 21 | }) 22 | 23 | env.Stop() 24 | close(waitCh) 25 | err := env.Wait() 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | } 30 | 31 | func TestEnvironmentError(t *testing.T) { 32 | t.Parallel() 33 | 34 | env := NewEnvironment(context.Background()) 35 | 36 | testError := errors.New("test") 37 | 38 | stopCh := make(chan struct{}) 39 | 40 | go func() { 41 | err := env.Wait() 42 | if err != testError { 43 | t.Error(`err != testError`) 44 | } 45 | close(stopCh) 46 | }() 47 | 48 | waitCh := make(chan struct{}) 49 | 50 | env.Go(func(ctx context.Context) error { 51 | close(waitCh) 52 | return nil 53 | }) 54 | 55 | <-waitCh 56 | env.Cancel(testError) 57 | 58 | <-stopCh 59 | } 60 | 61 | func TestEnvironmentGo(t *testing.T) { 62 | t.Parallel() 63 | 64 | env := NewEnvironment(context.Background()) 65 | 66 | testError := errors.New("test") 67 | 68 | waitCh := make(chan struct{}) 69 | 70 | env.Go(func(ctx context.Context) error { 71 | <-ctx.Done() 72 | return nil 73 | }) 74 | 75 | env.Go(func(ctx context.Context) error { 76 | <-ctx.Done() 77 | // uncomment the next line delays test just 2 seconds. 78 | //time.Sleep(2 * time.Second) 79 | return nil 80 | }) 81 | 82 | env.Go(func(ctx context.Context) error { 83 | <-waitCh 84 | return testError 85 | }) 86 | 87 | close(waitCh) 88 | err := env.Wait() 89 | if err != testError { 90 | t.Error(`err != testError`) 91 | } 92 | } 93 | 94 | func TestEnvironmentID(t *testing.T) { 95 | t.Parallel() 96 | 97 | env := NewEnvironment(context.Background()) 98 | 99 | idch := make(chan interface{}, 1) 100 | env.GoWithID(func(ctx context.Context) error { 101 | idch <- ctx.Value(RequestIDContextKey) 102 | return nil 103 | }) 104 | 105 | env.Stop() 106 | err := env.Wait() 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | 111 | id := <-idch 112 | if id == nil { 113 | t.Fatal(`id == nil`) 114 | } 115 | 116 | sid, ok := id.(string) 117 | if !ok { 118 | t.Error(`id is not a string`) 119 | } 120 | if len(sid) != 36 { 121 | t.Error(`len(sid) != 36`) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /jsonlog.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import "time" 4 | 5 | // AccessLog is to decode access log records from HTTPServer. 6 | // The struct is tagged for JSON format. 7 | type AccessLog struct { 8 | Topic string `json:"topic"` 9 | LoggedAt time.Time `json:"logged_at"` 10 | Severity string `json:"severity"` 11 | Utsname string `json:"utsname"` 12 | Message string `json:"message"` 13 | 14 | Type string `json:"type"` // "access" 15 | Elapsed float64 `json:"response_time"` // floating point number of seconds. 16 | Protocol string `json:"protocol"` // "HTTP/1.1" or alike 17 | StatusCode int `json:"http_status_code"` // 200, 404, ... 18 | Method string `json:"http_method"` 19 | RequestURI string `json:"url"` 20 | Host string `json:"http_host"` 21 | RequestLength int64 `json:"request_size"` 22 | ResponseLength int64 `json:"response_size"` 23 | RemoteAddr string `json:"remote_ipaddr"` 24 | UserAgent string `json:"http_user_agent"` 25 | RequestID string `json:"request_id"` 26 | } 27 | 28 | // RequestLog is to decode request log from HTTPClient. 29 | // The struct is tagged for JSON format. 30 | type RequestLog struct { 31 | Topic string `json:"topic"` 32 | LoggedAt time.Time `json:"logged_at"` 33 | Severity string `json:"severity"` // "error" if request failed. 34 | Utsname string `json:"utsname"` 35 | Message string `json:"message"` 36 | 37 | Type string `json:"type"` // "http" 38 | ResponseTime float64 `json:"response_time"` // floating point number of seconds. 39 | StatusCode int `json:"http_status_code"` // 200, 404, 500, ... 40 | Method string `json:"http_method"` 41 | URLString string `json:"url"` 42 | StartAt time.Time `json:"start_at"` 43 | RequestID string `json:"request_id"` 44 | Error string `json:"error"` 45 | } 46 | 47 | // ExecLog is a struct to decode command execution log from LogCmd. 48 | // The struct is tagged for JSON format. 49 | type ExecLog struct { 50 | Topic string `json:"topic"` 51 | LoggedAt time.Time `json:"logged_at"` 52 | Severity string `json:"severity"` // "error" if exec failed. 53 | Utsname string `json:"utsname"` 54 | Message string `json:"message"` 55 | 56 | Type string `json:"type"` // "exec" 57 | Elapsed float64 `json:"response_time"` // floating point number of seconds. 58 | Command string `json:"command"` 59 | Args []string `json:"args"` 60 | RequestID string `json:"request_id"` 61 | Error string `json:"error"` 62 | Stderr string `json:"stderr"` 63 | } 64 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "io" 7 | "net" 8 | "runtime" 9 | "strconv" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func listen(port int, t *testing.T) net.Listener { 15 | l, err := net.Listen("tcp", "localhost:"+strconv.Itoa(port)) 16 | if err != nil { 17 | t.Skip(err) 18 | } 19 | return l 20 | } 21 | 22 | func connect(port int, t *testing.T) net.Conn { 23 | conn, err := net.Dial("tcp", "localhost:"+strconv.Itoa(port)) 24 | if err != nil { 25 | t.Fatal(err) 26 | } 27 | return conn 28 | } 29 | 30 | func TestServer(t *testing.T) { 31 | if runtime.GOOS == "windows" { 32 | t.Skip("windows doesn't support FileListener") 33 | } 34 | t.Parallel() 35 | 36 | l := listen(15555, t) 37 | handler := func(ctx context.Context, conn net.Conn) { 38 | if ctx.Value(RequestIDContextKey) == nil { 39 | // handler must receive a new request ID. 40 | return 41 | } 42 | 43 | conn.Write([]byte{'h', 'e', 'l', 'l', 'o'}) 44 | <-ctx.Done() 45 | 46 | // uncomment the next line delays the test 47 | //time.Sleep(2 * time.Second) 48 | } 49 | 50 | env := NewEnvironment(context.Background()) 51 | s := &Server{ 52 | Handler: handler, 53 | Env: env, 54 | } 55 | s.Serve(l) 56 | 57 | conn := connect(15555, t) 58 | buf := make([]byte, 5) 59 | _, err := io.ReadFull(conn, buf) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | if !bytes.Equal(buf, []byte{'h', 'e', 'l', 'l', 'o'}) { 64 | t.Error(`!bytes.Equal(buf, []byte{'h', 'e', 'l', 'l', 'o'})`) 65 | } 66 | 67 | env.Cancel(nil) 68 | err = env.Wait() 69 | if err != nil { 70 | t.Error(err) 71 | } 72 | 73 | if s.TimedOut() { 74 | t.Error(`s.TimedOut()`) 75 | } 76 | } 77 | 78 | func TestServerTimeout(t *testing.T) { 79 | if runtime.GOOS == "windows" { 80 | t.Skip("windows doesn't support FileListener") 81 | } 82 | t.Parallel() 83 | 84 | l := listen(15556, t) 85 | handler := func(ctx context.Context, conn net.Conn) { 86 | conn.Write([]byte{'h', 'e', 'l', 'l', 'o'}) 87 | <-ctx.Done() 88 | time.Sleep(1 * time.Second) 89 | } 90 | 91 | env := NewEnvironment(context.Background()) 92 | s := &Server{ 93 | Handler: handler, 94 | ShutdownTimeout: 100 * time.Millisecond, 95 | Env: env, 96 | } 97 | s.Serve(l) 98 | 99 | conn := connect(15556, t) 100 | buf := make([]byte, 5) 101 | _, err := io.ReadFull(conn, buf) 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | if !bytes.Equal(buf, []byte{'h', 'e', 'l', 'l', 'o'}) { 106 | t.Error(`!bytes.Equal(buf, []byte{'h', 'e', 'l', 'l', 'o'})`) 107 | } 108 | 109 | env.Cancel(nil) 110 | err = env.Wait() 111 | if err != nil { 112 | t.Error(err) 113 | } 114 | 115 | if !s.TimedOut() { 116 | t.Error(`!s.TimedOut()`) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /exec_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import ( 7 | "bytes" 8 | "context" 9 | "encoding/json" 10 | "testing" 11 | "time" 12 | "unicode/utf8" 13 | 14 | "github.com/cybozu-go/log" 15 | ) 16 | 17 | func TestUTF8StringFromBytes(t *testing.T) { 18 | t.Parallel() 19 | 20 | str := "世\xc5\x33\x34" 21 | if utf8.ValidString(str) { 22 | t.Error(str + " should be invalid") 23 | } 24 | 25 | vstr := UTF8StringFromBytes([]byte(str)) 26 | if !utf8.ValidString(vstr) { 27 | t.Error(vstr + ` should be valid`) 28 | } 29 | } 30 | 31 | func TestLogCmd(t *testing.T) { 32 | ctx := context.Background() 33 | ctx = WithRequestID(ctx, testUUID) 34 | 35 | logger := log.NewLogger() 36 | logger.SetFormatter(log.JSONFormat{}) 37 | buf := new(bytes.Buffer) 38 | logger.SetOutput(buf) 39 | 40 | cmd := CommandContext(ctx, "true", "1", "2") 41 | cmd.Severity = log.LvDebug 42 | cmd.Logger = logger 43 | err := cmd.Run() 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if len(buf.Bytes()) > 0 { 48 | t.Error(`log should not be recorded`) 49 | } 50 | 51 | cmd = CommandContext(ctx, "true", "1", "2") 52 | cmd.Severity = log.LvInfo 53 | cmd.Logger = logger 54 | err = cmd.Run() 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | var execlog ExecLog 59 | err = json.Unmarshal(buf.Bytes(), &execlog) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | 64 | if time.Since(execlog.LoggedAt) > time.Minute { 65 | t.Error(time.Since(execlog.LoggedAt) > time.Minute) 66 | } 67 | if execlog.Severity != "info" { 68 | t.Error(`execlog.Severity != "info"`) 69 | } 70 | if execlog.Type != "exec" { 71 | t.Error(`execlog.Type != "exec"`) 72 | } 73 | if execlog.Args[1] != "1" { 74 | t.Error(`execlog.Args[1] != "1"`) 75 | } 76 | if execlog.RequestID != testUUID { 77 | t.Error(`execlog.RequestID != testUUID`) 78 | } 79 | } 80 | 81 | func TestLogCmdError(t *testing.T) { 82 | ctx := context.Background() 83 | logger := log.NewLogger() 84 | logger.SetFormatter(log.JSONFormat{}) 85 | buf := new(bytes.Buffer) 86 | logger.SetOutput(buf) 87 | 88 | cmd := CommandContext(ctx, "/bin/sh", "-c", "echo hoge fuga 1>&2; exit 3") 89 | cmd.Severity = log.LvDebug 90 | cmd.Logger = logger 91 | err := cmd.Run() 92 | if err == nil { 93 | t.Fatal("the command should fail") 94 | } 95 | 96 | var execlog ExecLog 97 | err = json.Unmarshal(buf.Bytes(), &execlog) 98 | if err != nil { 99 | t.Fatal(err) 100 | } 101 | 102 | if execlog.Severity != "error" { 103 | t.Error(`execlog.Severity != "error"`) 104 | } 105 | if execlog.Type != "exec" { 106 | t.Error(`execlog.Type != "exec"`) 107 | } 108 | t.Log(execlog.Error) 109 | if execlog.Stderr != "hoge fuga\n" { 110 | t.Error(`execlog.Stderr != "hoge fuga\n"`) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | "github.com/cybozu-go/log" 11 | "github.com/cybozu-go/netutil" 12 | ) 13 | 14 | // Server is a generic network server that accepts connections 15 | // and invokes Handler in a goroutine for each connection. 16 | // 17 | // In addition, Serve method gracefully waits all its goroutines to 18 | // complete before returning. 19 | type Server struct { 20 | 21 | // Handler handles a connection. This must not be nil. 22 | // 23 | // ctx is a derived context from the base context that will be 24 | // canceled when Handler returns. 25 | // 26 | // conn will be closed when Handler returns. 27 | Handler func(ctx context.Context, conn net.Conn) 28 | 29 | // ShutdownTimeout is the maximum duration the server waits for 30 | // all connections to be closed before shutdown. 31 | // 32 | // Zero duration disables timeout. 33 | ShutdownTimeout time.Duration 34 | 35 | // Env is the environment where this server runs. 36 | // 37 | // The global environment is used if Env is nil. 38 | Env *Environment 39 | 40 | wg sync.WaitGroup 41 | timedout int32 42 | } 43 | 44 | // Serve starts a managed goroutine to accept connections. 45 | // 46 | // Serve itself returns immediately. The goroutine continues 47 | // to accept and handle connections until the base context is 48 | // canceled. 49 | // 50 | // If the listener is *net.TCPListener, TCP keep-alive is automatically 51 | // enabled. 52 | // 53 | // The listener l will be closed automatically when the environment's 54 | // Cancel is called. 55 | func (s *Server) Serve(l net.Listener) { 56 | env := s.Env 57 | if env == nil { 58 | env = defaultEnv 59 | } 60 | 61 | l = netutil.KeepAliveListener(l) 62 | 63 | go func() { 64 | <-env.ctx.Done() 65 | l.Close() 66 | }() 67 | 68 | env.Go(func(ctx context.Context) error { 69 | generator := NewIDGenerator() 70 | for { 71 | conn, err := l.Accept() 72 | if err != nil { 73 | log.Debug("well: Listener.Accept error", map[string]interface{}{ 74 | "addr": l.Addr().String(), 75 | "error": err.Error(), 76 | }) 77 | goto OUT 78 | } 79 | 80 | s.wg.Add(1) 81 | go func() { 82 | ctx, cancel := context.WithCancel(ctx) 83 | defer func() { 84 | cancel() 85 | conn.Close() 86 | }() 87 | ctx = WithRequestID(ctx, generator.Generate()) 88 | s.Handler(ctx, conn) 89 | s.wg.Done() 90 | }() 91 | } 92 | OUT: 93 | s.wait() 94 | return nil 95 | }) 96 | } 97 | 98 | func (s *Server) wait() { 99 | if s.ShutdownTimeout == 0 { 100 | s.wg.Wait() 101 | return 102 | } 103 | 104 | ch := make(chan struct{}) 105 | go func() { 106 | s.wg.Wait() 107 | close(ch) 108 | }() 109 | 110 | select { 111 | case <-ch: 112 | case <-time.After(s.ShutdownTimeout): 113 | log.Warn("well: timeout waiting for shutdown", nil) 114 | atomic.StoreInt32(&s.timedout, 1) 115 | } 116 | } 117 | 118 | // TimedOut returns true if the server shut down before all connections 119 | // got closed. 120 | func (s *Server) TimedOut() bool { 121 | return atomic.LoadInt32(&s.timedout) != 0 122 | } 123 | -------------------------------------------------------------------------------- /testdata/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCoVTn8zdqEOozk 3 | 62gfeNa2bFkhqIuunaTwlNEhT9eFXyM0mR5D+KB2F2IYGYm5eTJkvPvVbIKW9Rhg 4 | qxgaVem+2tmN9NxJfR/99pW012W/Nc5wqmYEfDgRc470rUn7204DXtPo7Ppu1cIX 5 | BBx/Qb12BjqQ1iOzoZfxlezMmDgkL0XMzvy2Qlzs4m/CNcy8wTCsMQ7+gmQ1olGV 6 | VOKgnIH6MgmYsyt6oxNwFbPVtJzyxdtiGfDY9xiruYDRQ7w17IE96CTOSMq/KxGA 7 | KGhat+0MFh6gnF465n4juoiY4XddgXcTZQfaDlDpycI+x1jdk4vWLW2n6EMoQad/ 8 | btJRLiQXaNuZbWgiuKX/dNfkwqEhZnPx3yVHWQ6LA2xu6Vi2EiBALIQIa4IcjVA1 9 | Ai8Sh0MCgy8Aw+l3Z+ArSsQ7ptouPw5TUiWrowGfA08AAJVmKlqwVMb0Da1r/MfB 10 | JxK05fG5mBQzo793NxCGoZntgZmHJSmM4ZauD4DI/s4U4qhCAkIit2ZjTiGoseO/ 11 | q+PXg04C6/9EaFb2shDhPzcEqvcXSrogHqPOIAkFiUbRrTia3faoINfOE3lvhFIp 12 | YYY7wW+hvfIF4yr4UOqNB1hsuDSb9sReEnQzNMyU3BLC8VMYc1c1LBDptOChbR0w 13 | c8fFAe80qILT7d6wRuvLSZhp9vY2+QIDAQABAoICABXe9OjOyjxx6wp3N5EXPzZ2 14 | f2IrJvLqEpsbisx8NjFi+g0Ec/U64YZWZW1iLA3IHG8QStwg/Bqm/6LXuAr77KHa 15 | h3e1P8Xglv6FQlA7yEF+uTLUQOsGRLanwAQ0W694f/taH8hcufEantwksZsrLHWg 16 | O43D9OGK9lXyN9EhWfIBnl0M67K2jqJMjV67ZL2FkFBfE2aoBGMfkuAHJmbdztl2 17 | FN8dMGH/vC/M8g+rJtU4NdfaLPvTpwXw1TbbSSHsd6JWfim7NXR/IQf4uA5JZWgz 18 | m4kcp/WZKvxzaPoMGVkx0AnaD5oaUBMAHtfPSNGJMRsDzgKJ3+fkF+IrwPHBP/kA 19 | uV4l5X2xiRW5Zfi4s5B104eSLU9gnQ6Eed1xdBCZkJNoJnEgoZpPKnGEmMcJF73T 20 | yZ3dy1IWxx3xZDXes5w+j1qj86Btu9ncQyhVt9hns6MwEGaX2MkMFk4gEO7Cog2w 21 | ukZh40+Dtg3IZmgGV1krbNHVbmiBASu6PytCq3s71uLIJkIERFqOQyNuoh5U8wpm 22 | k2rfI+sjqzrljIcAtkzjoXfHqNmFWTbQdCWTZjoVg7yXEUqyrVJTki+5UC320pwi 23 | 9CtPd37sELcXxA2/r6HvxcaRNCZlYN2zX2q3V3z2fP1wD4bpiCqI5EGp+bYPS2ij 24 | VK169KcfmgOvQuX0G9vxAoIBAQDR5JkVV264lSGx7Sn8ggo3IjWJJakQa2XIxYpe 25 | aFMBV+nUA5LmhJjQWVTmJWD6o71PKqbT5/uwEz78xnEWhl4krLgOHzqkrli8j/ZW 26 | anHeYCszaAiKlQBFoNvoMV3DuASmF3OqDVQ1d9M/HT/J2H/9PucGtTvHZd1GioYa 27 | CFbYPxuxibA4BBD7QyUPtLcifMMdb6Fc9d8MBZMd0Zxg1EnH1/khh1vz1RTz2HmC 28 | 5pZx5GtXZimMMBs9y3o6UvYM3w3OiZT65nU1fnhHy+EwyeFaBVLnXV6ly6U995yl 29 | oSBuVwGProZK8b4H2hdX38ulT2Y20SD20HRGEs1QJSja1YW9AoIBAQDNT3w7dGEr 30 | 6B70+8174iUQHM9gKfZvd7psdRuzoZYJQtnyWMunAF9d8Ae7wxzp8whd2oMGAehe 31 | UnLEroh8BPSBkzUBwTikPk03xgZcgWqVorRmnzVX4ZYM2wGOKKim4EFDZzxB65P9 32 | AnP+smvalnDuEpiPFJ5Ip/Y61mh0yESoIRLG74uJIa/OggHjina4vr++RYOj5ztw 33 | GOCcnj4xJKYRjylj2lvMdIN9QqEtAIcXNEwn4V6oxx6NpL1rNZQ0M6ZB9MZNOvhP 34 | 5VOosgzYOBTaoLpyDNSxa3PVob8AQcnmsnTwkPWOVsocB/G/WyaExsWsT4OIvp73 35 | 4l0XKqRSo/PtAoIBAQCJ50sZNYfeqMUptOwYJox1qvlHscHBsv5+CiQeYSbgRBWy 36 | oeJ7qHUKtxbL25U7OCeigyB3K/02s3538B3PMdta90w6oxQRrSdKRMASCP/lnoPj 37 | 05+BBeLW3Jh/lyfG9vm+8rug5/8C3kaiOiJ2CNQW3KRHyP2MxgW7Wjo/kmkUzWnx 38 | nMh2FV3e6D/KcO6+LwC3L2fyvuycZnFBbz3ydKhBMEpUWC33J3ILwJgdhEkmbXCF 39 | TO9RVy4ezLjy5x0YWdklcUm8C3zI994pNfAv8QQp9EODFdqgzgs0iqKWRCYduPai 40 | o4VCalBzsjwSFU0kwOUkcQTahR1QCPxyguez2AR1AoIBAAyYxOGM+W1NN6EGVgw0 41 | yttbOSoISRN7wBdpdAKodmEpFeA1rIEZl8BWutK9XOfJvoiroIBc25n16w7AOqWJ 42 | VEsi8gyig0woempinv9L/XMi4XiuagJzV9aAFW9YdUiP19E/40WUI6dRYDaPUei+ 43 | dD1XLL8Sn8eLbqMbhpsrsnKyXMmR5vjJ9VRxS5ZfZ3tcUL1m3HXQ+SeejyFs4Bn5 44 | yqqnIv36Zsl7mJm13de6AY/d6JJF1J38YZaFNg3+4pH1XU0MrKIObS/RvTlLe1hE 45 | 0Jq9Y9MdkGs2UYwY+XQVlUKvTld4PWi3UH05y45XFFzvKS+yTTq1n2FMJTQHrL9Z 46 | Sd0CggEBAKKRJFDBBnUIcYZqcWsm40HmYoNpozb+SNJsfIvTKQhpOs5TSu8rzN9N 47 | fyl9kkqVlIWzTvcUDF3tXBtEB/DYVvNRUTq1omRtnkjrYRQKHpjMGe7ICKgaREwF 48 | CHQrTZh+kIqtbz6ybjNgAYVB/e3LF8t3bVJkbatUk8hPnw3tEJnKac3LqDIre7eY 49 | 6ABrPkpP/OWR9w0/YpjXEtzzGdG0ezxZHJm7+G4jwrpIL15Z08hPVUTLzLBLh+Wp 50 | IlCbCN7pcfNYHRUV1ZRRd0LvlXWbvjW+uS4tFWgd9tHmEm32RgN7LmfDIipZ9d0D 51 | RV7U7VCPOYmw0cQ3EVODHKcnmsRosMU= 52 | -----END PRIVATE KEY----- 53 | -------------------------------------------------------------------------------- /test/restart/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "io" 8 | "net" 9 | "os" 10 | "runtime" 11 | "strconv" 12 | "sync/atomic" 13 | "time" 14 | 15 | "github.com/cybozu-go/log" 16 | "github.com/cybozu-go/well" 17 | ) 18 | 19 | var ( 20 | tcpAddr = "localhost:18556" 21 | unixAddr string 22 | ) 23 | 24 | func getTemporaryFilename() string { 25 | f, err := os.CreateTemp("", "gotest") 26 | if err != nil { 27 | log.ErrorExit(err) 28 | } 29 | f.Close() 30 | os.Remove(f.Name()) 31 | return f.Name() 32 | } 33 | 34 | func main() { 35 | flag.Parse() 36 | well.LogConfig{}.Apply() 37 | 38 | if well.IsSystemdService() { 39 | log.Info("run as a systemd service", nil) 40 | } else { 41 | log.Info("not a systemd service", nil) 42 | } 43 | 44 | unixAddr = getTemporaryFilename() 45 | defer os.Remove(unixAddr) 46 | 47 | listen := func() ([]net.Listener, error) { 48 | ln1, err := net.Listen("tcp", tcpAddr) 49 | if err != nil { 50 | log.ErrorExit(err) 51 | } 52 | if runtime.GOOS == "windows" { 53 | well.Go(testClient) 54 | return []net.Listener{ln1}, nil 55 | } 56 | ln2, err := net.Listen("unix", unixAddr) 57 | if err != nil { 58 | log.ErrorExit(err) 59 | } 60 | well.Go(testClient) 61 | return []net.Listener{ln1, ln2}, nil 62 | } 63 | 64 | g := &well.Graceful{ 65 | Listen: listen, 66 | Serve: serve, 67 | } 68 | g.Run() 69 | 70 | // rest are executed only in the master process. 71 | err := well.Wait() 72 | if err != nil && !well.IsSignaled(err) { 73 | log.ErrorExit(err) 74 | } 75 | } 76 | 77 | // serve implements a network server that can be stopped gracefully 78 | // using well.Server. 79 | func serve(listeners []net.Listener) { 80 | var counter int64 81 | handler := func(ctx context.Context, conn net.Conn) { 82 | if runtime.GOOS == "windows" { 83 | conn.Write([]byte("hello 1")) 84 | return 85 | } 86 | n := atomic.AddInt64(&counter, 1) 87 | if n > 1 { 88 | time.Sleep(time.Duration(n) * time.Second) 89 | } 90 | conn.Write([]byte("hello " + strconv.FormatInt(n, 10))) 91 | } 92 | 93 | s := &well.Server{ 94 | Handler: handler, 95 | } 96 | for _, ln := range listeners { 97 | s.Serve(ln) 98 | } 99 | err := well.Wait() 100 | if err != nil && !well.IsSignaled(err) { 101 | log.ErrorExit(err) 102 | } 103 | } 104 | 105 | func testClient(ctx context.Context) error { 106 | for i := 0; i < 5; i++ { 107 | err := ping("tcp4", tcpAddr) 108 | if err != nil { 109 | return err 110 | } 111 | restart() 112 | } 113 | 114 | if runtime.GOOS != "windows" { 115 | err := ping("unix", unixAddr) 116 | if err != nil { 117 | return err 118 | } 119 | } 120 | 121 | well.Cancel(nil) 122 | return nil 123 | } 124 | 125 | func ping(network, addr string) error { 126 | conn, err := net.Dial(network, addr) 127 | if err != nil { 128 | return err 129 | } 130 | defer conn.Close() 131 | 132 | st := time.Now() 133 | data, err := io.ReadAll(conn) 134 | if err != nil { 135 | return err 136 | } 137 | if string(data) != "hello 1" { 138 | log.Error("wrong response", map[string]interface{}{ 139 | "data": string(data), 140 | }) 141 | return errors.New("invalid response") 142 | } 143 | log.Info("got data", map[string]interface{}{ 144 | "data": string(data), 145 | }) 146 | 147 | if time.Since(st) > time.Second { 148 | return errors.New("too long") 149 | } 150 | return nil 151 | } 152 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package well_test 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "net/http" 7 | 8 | "github.com/BurntSushi/toml" 9 | "github.com/cybozu-go/log" 10 | "github.com/cybozu-go/well" 11 | ) 12 | 13 | func doSomething() error { 14 | return nil 15 | } 16 | 17 | // The most basic usage of the framework. 18 | func Example_basic() { 19 | flag.Parse() 20 | err := well.LogConfig{}.Apply() 21 | if err != nil { 22 | log.ErrorExit(err) 23 | } 24 | 25 | well.Go(func(ctx context.Context) error { 26 | err := doSomething() 27 | 28 | if err != nil { 29 | // non-nil error will be passed to Cancel 30 | // by the framework. 31 | return err 32 | } 33 | 34 | // on success, nil should be returned. 35 | return nil 36 | }) 37 | 38 | // some more Go 39 | //well.Go(func(ctx context.Context) error {}) 40 | 41 | // Stop declares no Go calls will be made from this point. 42 | // Calling Stop is optional if Cancel is guaranteed to be called 43 | // at some point. 44 | well.Stop() 45 | 46 | // Wait waits for all goroutines started by Go to complete, 47 | // or one of such goroutine returns non-nil error. 48 | err = well.Wait() 49 | if err != nil { 50 | log.ErrorExit(err) 51 | } 52 | } 53 | 54 | // HTTP server that stops gracefully. 55 | func Example_http() { 56 | flag.Parse() // must precedes LogConfig.Apply 57 | err := well.LogConfig{}.Apply() 58 | if err != nil { 59 | log.ErrorExit(err) 60 | } 61 | 62 | // log accesses in JSON Lines format. 63 | accessLog := log.NewLogger() 64 | accessLog.SetFormatter(log.JSONFormat{}) 65 | 66 | // HTTP server. 67 | serv := &well.HTTPServer{ 68 | Server: &http.Server{ 69 | Handler: http.FileServer(http.Dir("/path/to/directory")), 70 | }, 71 | AccessLog: accessLog, 72 | } 73 | 74 | // ListenAndServe is overridden to start a goroutine by well.Go. 75 | err = serv.ListenAndServe() 76 | if err != nil { 77 | log.ErrorExit(err) 78 | } 79 | 80 | // Wait waits for SIGINT or SIGTERM. 81 | // In this case, well.Stop can be omitted. 82 | err = well.Wait() 83 | 84 | // Use IsSignaled to determine err is the result of a signal. 85 | if err != nil && !well.IsSignaled(err) { 86 | log.ErrorExit(err) 87 | } 88 | } 89 | 90 | // Load logging configurations from TOML file. 91 | func ExampleLogConfig() { 92 | // compile-time defaults 93 | config := &well.LogConfig{ 94 | Level: "error", 95 | Format: "json", 96 | } 97 | 98 | // load from TOML 99 | _, err := toml.DecodeFile("/path/to/config.toml", config) 100 | if err != nil { 101 | log.ErrorExit(err) 102 | } 103 | 104 | // Apply gives priority to command-line flags, if any. 105 | flag.Parse() 106 | err = config.Apply() 107 | if err != nil { 108 | log.ErrorExit(err) 109 | } 110 | } 111 | 112 | // Barrier wait for gorutines. 113 | func ExampleNewEnvironment() { 114 | // An independent environment. 115 | env := well.NewEnvironment(context.Background()) 116 | 117 | env.Go(func(ctx context.Context) error { 118 | // do something 119 | return nil 120 | }) 121 | // some more env.Go() 122 | 123 | // wait all goroutines started by env.Go(). 124 | // These goroutines may also be canceled when the global 125 | // environment is canceled. 126 | env.Stop() 127 | err := env.Wait() 128 | if err != nil { 129 | log.ErrorExit(err) 130 | } 131 | 132 | // another environment for another barrier. 133 | env = well.NewEnvironment(context.Background()) 134 | 135 | // env.Go, env.Stop, and env.Wait again. 136 | } 137 | -------------------------------------------------------------------------------- /log_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import ( 7 | "bytes" 8 | "encoding/json" 9 | "flag" 10 | "io" 11 | "os" 12 | "syscall" 13 | "testing" 14 | "time" 15 | 16 | "github.com/BurntSushi/toml" 17 | "github.com/cybozu-go/log" 18 | ) 19 | 20 | func TestLogConfig(t *testing.T) { 21 | t.Parallel() 22 | 23 | var c1, c2 struct { 24 | Log LogConfig `toml:"log" json:"log"` 25 | } 26 | 27 | _, err := toml.DecodeFile("testdata/log.toml", &c1) 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | if c1.Log.Filename != "/abc/def" { 33 | t.Error(`c1.Log.Filename != "/abc/def"`) 34 | } 35 | if c1.Log.Level != "debug" { 36 | t.Error(`c1.Log.Level != "debug"`) 37 | } 38 | if c1.Log.Format != "json" { 39 | t.Error(`c1.Log.Format != "json"`) 40 | } 41 | 42 | f, err := os.Open("testdata/log.json") 43 | if err != nil { 44 | t.Fatal(err) 45 | } 46 | defer f.Close() 47 | 48 | err = json.NewDecoder(f).Decode(&c2) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | if c2.Log.Filename != "/abc/def" { 54 | t.Error(`c2.Log.Filename != "/abc/def"`) 55 | } 56 | if c2.Log.Level != "debug" { 57 | t.Error(`c2.Log.Level != "debug"`) 58 | } 59 | if c2.Log.Format != "json" { 60 | t.Error(`c2.Log.Format != "json"`) 61 | } 62 | } 63 | 64 | func TestLogConfigApply(t *testing.T) { 65 | t.Parallel() 66 | 67 | c := &LogConfig{ 68 | Filename: "", 69 | Level: "info", 70 | Format: "json", 71 | } 72 | 73 | err := c.Apply() 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | 78 | logger := log.DefaultLogger() 79 | if logger.Threshold() != log.LvInfo { 80 | t.Error(`logger.Threshold() != log.LvInfo`) 81 | } 82 | if logger.Formatter().String() != "json" { 83 | t.Error(`logger.Formatter().String() != "json"`) 84 | } 85 | 86 | c.Format = "bad_format" 87 | err = c.Apply() 88 | if err == nil { 89 | t.Error(c.Format + " should cause an error") 90 | } 91 | 92 | c.Level = "bad_level" 93 | c.Format = "json" 94 | err = c.Apply() 95 | if err == nil { 96 | t.Error(c.Level + " should cause an error") 97 | } 98 | } 99 | 100 | func TestLogFlags(t *testing.T) { 101 | t.Parallel() 102 | t.Skip("this test redirects log outputs to a temp file.") 103 | 104 | c := &LogConfig{ 105 | Filename: "", 106 | Level: "info", 107 | Format: "json", 108 | } 109 | 110 | f, err := os.CreateTemp("", "gotest") 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | f.Close() 115 | defer os.Remove(f.Name()) 116 | 117 | flag.Set("logfile", f.Name()) 118 | flag.Set("loglevel", "debug") 119 | flag.Set("logformat", "plain") 120 | 121 | err = c.Apply() 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | 126 | logger := log.DefaultLogger() 127 | 128 | if logger.Threshold() != log.LvDebug { 129 | t.Error(`logger.Threshold() != log.LvDebug`) 130 | } 131 | if logger.Formatter().String() != "plain" { 132 | t.Error(`logger.Formatter().String() != "plain"`) 133 | } 134 | 135 | err = log.Critical("hoge fuga", nil) 136 | if err != nil { 137 | t.Fatal(err) 138 | } 139 | syscall.Kill(os.Getpid(), syscall.SIGUSR1) 140 | time.Sleep(10 * time.Millisecond) 141 | 142 | g, err := os.Open(f.Name()) 143 | if err != nil { 144 | t.Fatal(err) 145 | } 146 | data, err := io.ReadAll(g) 147 | if err != nil { 148 | t.Fatal(err) 149 | } 150 | 151 | if !bytes.Contains(data, []byte("hoge fuga")) { 152 | t.Error(`!bytes.Contains(data, []byte("hoge fuga"))`) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /exec.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "os/exec" 7 | "time" 8 | "unicode/utf8" 9 | 10 | "github.com/cybozu-go/log" 11 | ) 12 | 13 | // UTF8StringFromBytes returns a valid UTF-8 string from 14 | // maybe invalid slice of bytes. 15 | func UTF8StringFromBytes(b []byte) string { 16 | if utf8.Valid(b) { 17 | return string(b) 18 | } 19 | 20 | // This effectively replaces invalid bytes to \uFFFD (replacement char). 21 | return string(bytes.Runes(b)) 22 | } 23 | 24 | // LogCmd is a wrapper for *exec.Cmd to record command execution results. 25 | // If command fails, log level will be log.LvError. 26 | // If command succeeds, log level will be log.LvInfo. 27 | // 28 | // In most cases, use CommandContext function to prepare LogCmd. 29 | type LogCmd struct { 30 | *exec.Cmd 31 | 32 | // Severity is used to log successful requests. 33 | // 34 | // Zero suppresses logging. Valid values are one of 35 | // log.LvDebug, log.LvInfo, and so on. 36 | // 37 | // Errors are always logged with log.LvError. 38 | Severity int 39 | 40 | // Fields is passed to Logger as log fields. 41 | Fields map[string]interface{} 42 | 43 | // Logger for execution results. If nil, the default logger is used. 44 | Logger *log.Logger 45 | } 46 | 47 | func (c *LogCmd) log(st time.Time, err error, output []byte) { 48 | logger := c.Logger 49 | if logger == nil { 50 | logger = log.DefaultLogger() 51 | } 52 | 53 | if err == nil && (c.Severity == 0 || !logger.Enabled(c.Severity)) { 54 | // successful logs are suppressed if c.Severity is 0 or 55 | // logger threshold is under c.Severity. 56 | return 57 | } 58 | 59 | fields := c.Fields 60 | fields[log.FnType] = "exec" 61 | fields[log.FnResponseTime] = time.Since(st).Seconds() 62 | fields["command"] = c.Cmd.Path 63 | fields["args"] = c.Cmd.Args 64 | 65 | if err == nil { 66 | logger.Log(c.Severity, "well: exec", fields) 67 | return 68 | } 69 | 70 | fields["error"] = err.Error() 71 | if len(output) > 0 { 72 | fields["stderr"] = UTF8StringFromBytes(output) 73 | } 74 | logger.Error("well: exec", fields) 75 | } 76 | 77 | // CombinedOutput overrides exec.Cmd.CombinedOutput to record the result. 78 | func (c *LogCmd) CombinedOutput() ([]byte, error) { 79 | st := time.Now() 80 | data, err := c.Cmd.CombinedOutput() 81 | c.log(st, err, nil) 82 | return data, err 83 | } 84 | 85 | // Output overrides exec.Cmd.Output to record the result. 86 | // If Cmd.Stderr is nil, Output logs outputs to stderr as well. 87 | func (c *LogCmd) Output() ([]byte, error) { 88 | st := time.Now() 89 | data, err := c.Cmd.Output() 90 | if err != nil { 91 | ee, ok := err.(*exec.ExitError) 92 | if ok { 93 | c.log(st, err, ee.Stderr) 94 | return data, err 95 | } 96 | } 97 | c.log(st, err, nil) 98 | return data, err 99 | } 100 | 101 | // Run overrides exec.Cmd.Run to record the result. 102 | // If both Cmd.Stdout and Cmd.Stderr are nil, this calls Output 103 | // instead to log stderr. 104 | func (c *LogCmd) Run() error { 105 | if c.Cmd.Stdout == nil && c.Cmd.Stderr == nil { 106 | _, err := c.Output() 107 | return err 108 | } 109 | 110 | st := time.Now() 111 | err := c.Cmd.Run() 112 | c.log(st, err, nil) 113 | return err 114 | } 115 | 116 | // Wait overrides exec.Cmd.Wait to record the result. 117 | func (c *LogCmd) Wait() error { 118 | st := time.Now() 119 | err := c.Cmd.Wait() 120 | c.log(st, err, nil) 121 | return err 122 | } 123 | 124 | // CommandContext is similar to exec.CommandContext, 125 | // but returns *LogCmd with its Context set to ctx. 126 | // 127 | // LogCmd.Severity is set to log.LvInfo. 128 | // 129 | // LogCmd.Logger is left nil. If you want to use another logger, 130 | // set it manually. 131 | func CommandContext(ctx context.Context, name string, args ...string) *LogCmd { 132 | return &LogCmd{ 133 | Cmd: exec.CommandContext(ctx, name, args...), 134 | Severity: log.LvInfo, 135 | Fields: FieldsFromContext(ctx), 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /env.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/cybozu-go/log" 8 | ) 9 | 10 | // Environment implements context-based goroutine management. 11 | type Environment struct { 12 | ctx context.Context 13 | cancel context.CancelFunc 14 | wg sync.WaitGroup 15 | generator *IDGenerator 16 | 17 | mu sync.RWMutex 18 | stopped bool 19 | stopCh chan struct{} 20 | canceled bool 21 | err error 22 | } 23 | 24 | // NewEnvironment creates a new Environment. 25 | // 26 | // This does *not* install signal handlers for SIGINT/SIGTERM 27 | // for new environments. Only the global environment will be 28 | // canceled on these signals. 29 | func NewEnvironment(ctx context.Context) *Environment { 30 | ctx, cancel := context.WithCancel(ctx) 31 | e := &Environment{ 32 | ctx: ctx, 33 | cancel: cancel, 34 | generator: NewIDGenerator(), 35 | stopCh: make(chan struct{}), 36 | } 37 | return e 38 | } 39 | 40 | // Stop just declares no further Go will be called. 41 | // 42 | // Calling Stop is optional if and only if Cancel is guaranteed 43 | // to be called at some point. For instance, if the program runs 44 | // until SIGINT or SIGTERM, Stop is optional. 45 | func (e *Environment) Stop() { 46 | e.mu.Lock() 47 | 48 | if !e.stopped { 49 | e.stopped = true 50 | close(e.stopCh) 51 | } 52 | 53 | e.mu.Unlock() 54 | } 55 | 56 | // Cancel cancels the base context. 57 | // 58 | // Passed err will be returned by Wait(). 59 | // Once canceled, Go() will not start new goroutines. 60 | // 61 | // Note that calling Cancel(nil) is perfectly valid. 62 | // Unlike Stop(), Cancel(nil) cancels the base context and can 63 | // gracefully stop goroutines started by Server.Serve or 64 | // HTTPServer.ListenAndServe. 65 | // 66 | // This returns true if the caller is the first that calls Cancel. 67 | // For second and later calls, Cancel does nothing and returns false. 68 | func (e *Environment) Cancel(err error) bool { 69 | e.mu.Lock() 70 | defer e.mu.Unlock() 71 | 72 | if e.canceled { 73 | return false 74 | } 75 | e.canceled = true 76 | e.err = err 77 | e.cancel() 78 | 79 | if e.stopped { 80 | return true 81 | } 82 | 83 | e.stopped = true 84 | close(e.stopCh) 85 | return true 86 | } 87 | 88 | // Wait waits for Stop or Cancel, and for all goroutines started by 89 | // Go to finish. 90 | // 91 | // The returned err is the one passed to Cancel, or nil. 92 | // err can be tested by IsSignaled to determine whether the 93 | // program got SIGINT or SIGTERM. 94 | func (e *Environment) Wait() error { 95 | <-e.stopCh 96 | if log.Enabled(log.LvDebug) { 97 | log.Debug("well: waiting for all goroutines to complete", nil) 98 | } 99 | e.wg.Wait() 100 | e.cancel() // in case no one calls Cancel 101 | 102 | e.mu.Lock() 103 | defer e.mu.Unlock() 104 | 105 | return e.err 106 | } 107 | 108 | // Go starts a goroutine that executes f. 109 | // 110 | // f takes a drived context from the base context. The context 111 | // will be canceled when f returns. 112 | // 113 | // Goroutines started by this function will be waited for by 114 | // Wait until all such goroutines return. 115 | // 116 | // If f returns non-nil error, Cancel is called immediately 117 | // with that error. 118 | // 119 | // f should watch ctx.Done() channel and return quickly when the 120 | // channel is closed. 121 | func (e *Environment) Go(f func(ctx context.Context) error) { 122 | e.mu.RLock() 123 | if e.stopped { 124 | e.mu.RUnlock() 125 | return 126 | } 127 | e.wg.Add(1) 128 | e.mu.RUnlock() 129 | 130 | go func() { 131 | ctx, cancel := context.WithCancel(e.ctx) 132 | defer cancel() 133 | err := f(ctx) 134 | if err != nil { 135 | e.Cancel(err) 136 | } 137 | e.wg.Done() 138 | }() 139 | } 140 | 141 | // GoWithID calls Go with a context having a new request tracking ID. 142 | func (e *Environment) GoWithID(f func(ctx context.Context) error) { 143 | e.Go(func(ctx context.Context) error { 144 | return f(WithRequestID(ctx, e.generator.Generate())) 145 | }) 146 | } 147 | -------------------------------------------------------------------------------- /test/spf13/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Cybozu 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package main 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | 27 | "github.com/cybozu-go/log" 28 | "github.com/cybozu-go/well" 29 | "github.com/spf13/cobra" 30 | "github.com/spf13/viper" 31 | ) 32 | 33 | var cfgFile string 34 | 35 | // rootCmd represents the base command when called without any subcommands 36 | var rootCmd = &cobra.Command{ 37 | Use: "spf13", 38 | Short: "test combination with spf13 tools", 39 | Long: `This is a test program that combines cybozu-go/well with 40 | spf13/{cobra,pflag,viper} tools.`, 41 | 42 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 43 | err := well.LogConfig{}.Apply() 44 | if err != nil { 45 | log.ErrorExit(err) 46 | } 47 | }, 48 | Run: func(cmd *cobra.Command, args []string) { 49 | if os.Getenv("CI") != "true" { 50 | log.Info("log: info", nil) 51 | log.Error("log: error", nil) 52 | log.Critical("log: critical", nil) 53 | return 54 | } 55 | 56 | logger := log.DefaultLogger() 57 | if logger.Enabled(log.LvError) { 58 | fmt.Println("error logs should be disabled") 59 | os.Exit(2) 60 | } 61 | if !logger.Enabled(log.LvCritical) { 62 | fmt.Println("critical logs should be eabled") 63 | os.Exit(2) 64 | } 65 | if _, ok := logger.Formatter().(log.JSONFormat); !ok { 66 | fmt.Println("log format should be JSON") 67 | os.Exit(2) 68 | } 69 | 70 | fmt.Println("test passed") 71 | }, 72 | } 73 | 74 | // Execute adds all child commands to the root command and sets flags appropriately. 75 | // This is called by main.main(). It only needs to happen once to the rootCmd. 76 | func Execute() { 77 | if err := rootCmd.Execute(); err != nil { 78 | fmt.Println(err) 79 | os.Exit(1) 80 | } 81 | } 82 | 83 | func init() { 84 | cobra.OnInitialize(initConfig) 85 | 86 | // Here you will define your flags and configuration settings. 87 | // Cobra supports persistent flags, which, if defined here, 88 | // will be global for your application. 89 | rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.spf13.yaml)") 90 | 91 | // Cobra also supports local flags, which will only run 92 | // when this action is called directly. 93 | rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 94 | } 95 | 96 | // initConfig reads in config file and ENV variables if set. 97 | func initConfig() { 98 | if cfgFile != "" { 99 | // Use config file from the flag. 100 | viper.SetConfigFile(cfgFile) 101 | } else { 102 | // Find home directory. 103 | home, err := os.UserHomeDir() 104 | if err != nil { 105 | fmt.Println(err) 106 | os.Exit(1) 107 | } 108 | 109 | // Search config in home directory with name ".spf13" (without extension). 110 | viper.AddConfigPath(home) 111 | viper.SetConfigName(".spf13") 112 | } 113 | 114 | // If a config file is found, read it in. 115 | viper.ReadInConfig() 116 | } 117 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "flag" 7 | "path/filepath" 8 | 9 | "github.com/cybozu-go/log" 10 | "github.com/spf13/pflag" 11 | "github.com/spf13/viper" 12 | ) 13 | 14 | var ( 15 | // empty default values indicates unspecified condition. 16 | logFilename = flag.String("logfile", "", "Log filename") 17 | logLevel = flag.String("loglevel", "", "Log level [critical,error,warning,info,debug]") 18 | logFormat = flag.String("logformat", "", "Log format [plain,logfmt,json]") 19 | 20 | ignoreLogFilename bool 21 | ) 22 | 23 | func init() { 24 | // This is for child processes of graceful restarting server. 25 | // See graceful.go 26 | ignoreLogFilename = !isMaster() 27 | 28 | // Support for spf13/{cobra,pflag,viper} toolkit. 29 | pflag.String("logfile", "", "Log filename") 30 | pflag.String("loglevel", "", "Log level [critical,error,warning,info,debug]") 31 | pflag.String("logformat", "", "Log format [plain,logfmt,json]") 32 | viper.BindPFlag("log.file", pflag.Lookup("logfile")) 33 | viper.BindPFlag("log.level", pflag.Lookup("loglevel")) 34 | viper.BindPFlag("log.format", pflag.Lookup("logformat")) 35 | } 36 | 37 | // LogConfig configures cybozu-go/log's default logger. 38 | // 39 | // Filename, if not an empty string, specifies the output filename. 40 | // 41 | // Level is the log threshold level name. 42 | // Valid levels are "critical", "error", "warning", "info", and "debug". 43 | // Empty string is treated as "info". 44 | // 45 | // Format specifies log formatter to be used. 46 | // Available formatters are "plain", "logfmt", and "json". 47 | // Empty string is treated as "plain". 48 | // 49 | // For details, see https://godoc.org/github.com/cybozu-go/log . 50 | type LogConfig struct { 51 | Filename string `toml:"filename" json:"filename" yaml:"filename"` 52 | Level string `toml:"level" json:"level" yaml:"level"` 53 | Format string `toml:"format" json:"format" yaml:"format"` 54 | } 55 | 56 | // Apply applies configurations to the default logger. 57 | // 58 | // Command-line flags take precedence over the struct member values. 59 | // 60 | // When used with github.com/spf13/{pflag,viper}, pflag values are 61 | // bound to viper database, and Apply look for following keys 62 | // in the viper database: 63 | // - log.file 64 | // - log.level 65 | // - log.format 66 | // 67 | // If they are not empty, they take precedence over the struct member values. 68 | func (c LogConfig) Apply() error { 69 | logger := log.DefaultLogger() 70 | 71 | filename := c.Filename 72 | if len(*logFilename) > 0 { 73 | filename = *logFilename 74 | } 75 | if v := viper.GetString("log.file"); len(v) > 0 { 76 | filename = v 77 | } 78 | if len(filename) > 0 && !ignoreLogFilename { 79 | abspath, err := filepath.Abs(filename) 80 | if err != nil { 81 | return err 82 | } 83 | w, err := openLogFile(abspath) 84 | if err != nil { 85 | return err 86 | } 87 | logger.SetOutput(w) 88 | } 89 | 90 | level := c.Level 91 | if len(*logLevel) > 0 { 92 | level = *logLevel 93 | } 94 | if v := viper.GetString("log.level"); len(v) > 0 { 95 | level = v 96 | } 97 | if len(level) == 0 { 98 | level = "info" 99 | } 100 | err := logger.SetThresholdByName(level) 101 | if err != nil { 102 | return err 103 | } 104 | 105 | format := c.Format 106 | if len(*logFormat) > 0 { 107 | format = *logFormat 108 | } 109 | if v := viper.GetString("log.format"); len(v) > 0 { 110 | format = v 111 | } 112 | switch format { 113 | case "", "plain": 114 | logger.SetFormatter(log.PlainFormat{}) 115 | case "logfmt": 116 | logger.SetFormatter(log.Logfmt{}) 117 | case "json": 118 | logger.SetFormatter(log.JSONFormat{}) 119 | default: 120 | return errors.New("invalid format: " + format) 121 | } 122 | 123 | return nil 124 | } 125 | 126 | // FieldsFromContext returns a map of fields containing 127 | // context information. Currently, request ID field is 128 | // included, if any. 129 | func FieldsFromContext(ctx context.Context) map[string]interface{} { 130 | m := make(map[string]interface{}) 131 | v := ctx.Value(RequestIDContextKey) 132 | if v != nil { 133 | m[log.FnRequestID] = v.(string) 134 | } 135 | return m 136 | } 137 | -------------------------------------------------------------------------------- /idgen.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "sync/atomic" 7 | ) 8 | 9 | var hexData = [...]byte{ 10 | '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', '0', 'a', '0', 'b', '0', 'c', '0', 'd', '0', 'e', '0', 'f', 11 | '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', '1', 'a', '1', 'b', '1', 'c', '1', 'd', '1', 'e', '1', 'f', 12 | '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', '2', 'a', '2', 'b', '2', 'c', '2', 'd', '2', 'e', '2', 'f', 13 | '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', '3', 'a', '3', 'b', '3', 'c', '3', 'd', '3', 'e', '3', 'f', 14 | '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', '4', 'a', '4', 'b', '4', 'c', '4', 'd', '4', 'e', '4', 'f', 15 | '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', '5', 'a', '5', 'b', '5', 'c', '5', 'd', '5', 'e', '5', 'f', 16 | '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', '6', 'a', '6', 'b', '6', 'c', '6', 'd', '6', 'e', '6', 'f', 17 | '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', '7', 'a', '7', 'b', '7', 'c', '7', 'd', '7', 'e', '7', 'f', 18 | '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', '8', 'a', '8', 'b', '8', 'c', '8', 'd', '8', 'e', '8', 'f', 19 | '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9', '9', 'a', '9', 'b', '9', 'c', '9', 'd', '9', 'e', '9', 'f', 20 | 'a', '0', 'a', '1', 'a', '2', 'a', '3', 'a', '4', 'a', '5', 'a', '6', 'a', '7', 'a', '8', 'a', '9', 'a', 'a', 'a', 'b', 'a', 'c', 'a', 'd', 'a', 'e', 'a', 'f', 21 | 'b', '0', 'b', '1', 'b', '2', 'b', '3', 'b', '4', 'b', '5', 'b', '6', 'b', '7', 'b', '8', 'b', '9', 'b', 'a', 'b', 'b', 'b', 'c', 'b', 'd', 'b', 'e', 'b', 'f', 22 | 'c', '0', 'c', '1', 'c', '2', 'c', '3', 'c', '4', 'c', '5', 'c', '6', 'c', '7', 'c', '8', 'c', '9', 'c', 'a', 'c', 'b', 'c', 'c', 'c', 'd', 'c', 'e', 'c', 'f', 23 | 'd', '0', 'd', '1', 'd', '2', 'd', '3', 'd', '4', 'd', '5', 'd', '6', 'd', '7', 'd', '8', 'd', '9', 'd', 'a', 'd', 'b', 'd', 'c', 'd', 'd', 'd', 'e', 'd', 'f', 24 | 'e', '0', 'e', '1', 'e', '2', 'e', '3', 'e', '4', 'e', '5', 'e', '6', 'e', '7', 'e', '8', 'e', '9', 'e', 'a', 'e', 'b', 'e', 'c', 'e', 'd', 'e', 'e', 'e', 'f', 25 | 'f', '0', 'f', '1', 'f', '2', 'f', '3', 'f', '4', 'f', '5', 'f', '6', 'f', '7', 'f', '8', 'f', '9', 'f', 'a', 'f', 'b', 'f', 'c', 'f', 'd', 'f', 'e', 'f', 'f', 26 | } 27 | 28 | var ( 29 | defaultGenerator = NewIDGenerator() 30 | ) 31 | 32 | // IDGenerator generates ID suitable for request tracking. 33 | type IDGenerator struct { 34 | seed [16]byte 35 | n uint64 36 | } 37 | 38 | // NewIDGenerator creates a new IDGenerator. 39 | func NewIDGenerator() *IDGenerator { 40 | g := new(IDGenerator) 41 | _, err := rand.Read(g.seed[:]) 42 | if err != nil { 43 | panic(err) 44 | } 45 | return g 46 | } 47 | 48 | // Generate generates an ID. 49 | // Multiple goroutines can safely call this. 50 | func (g *IDGenerator) Generate() string { 51 | var nb [8]byte 52 | n := atomic.AddUint64(&g.n, 1) 53 | binary.LittleEndian.PutUint64(nb[:], n) 54 | 55 | id := g.seed 56 | for i, b := range nb { 57 | id[i] ^= b 58 | } 59 | 60 | var strbuf [36]byte 61 | for i := 0; i < 4; i++ { 62 | strbuf[i*2] = hexData[int(id[i])*2] 63 | strbuf[i*2+1] = hexData[int(id[i])*2+1] 64 | } 65 | strbuf[8] = '-' 66 | for i := 4; i < 6; i++ { 67 | strbuf[i*2+1] = hexData[int(id[i])*2] 68 | strbuf[i*2+2] = hexData[int(id[i])*2+1] 69 | } 70 | strbuf[13] = '-' 71 | for i := 6; i < 8; i++ { 72 | strbuf[i*2+2] = hexData[int(id[i])*2] 73 | strbuf[i*2+3] = hexData[int(id[i])*2+1] 74 | } 75 | strbuf[18] = '-' 76 | for i := 8; i < 10; i++ { 77 | strbuf[i*2+3] = hexData[int(id[i])*2] 78 | strbuf[i*2+4] = hexData[int(id[i])*2+1] 79 | } 80 | strbuf[23] = '-' 81 | for i := 10; i < 16; i++ { 82 | strbuf[i*2+4] = hexData[int(id[i])*2] 83 | strbuf[i*2+5] = hexData[int(id[i])*2+1] 84 | } 85 | return string(strbuf[:]) 86 | } 87 | 88 | // GenerateID genereates an ID using the default generator. 89 | // Multiple goroutines can safely call this. 90 | func GenerateID() string { 91 | return defaultGenerator.Generate() 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GitHub release](https://img.shields.io/github/release/cybozu-go/well.svg?maxAge=60)][releases] 2 | [![CI](https://github.com/cybozu-go/well/actions/workflows/ci.yaml/badge.svg)](https://github.com/cybozu-go/well/actions/workflows/ci.yaml) 3 | [![PkgGoDev](https://pkg.go.dev/badge/github.com/cybozu-go/well)](https://pkg.go.dev/github.com/cybozu-go/well) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/cybozu-go/well)](https://goreportcard.com/report/github.com/cybozu-go/well) 5 | 6 | Go Command Framework 7 | ==================== 8 | 9 | **Deprecated: This project has come to an end and will not receive any update.** 10 | 11 | This is a framework to create well-behaving commands. 12 | 13 | Features 14 | -------- 15 | 16 | * [Context](https://golang.org/pkg/context/)-based goroutine management. 17 | * Signal handlers. 18 | * Graceful stop/restart for any kind of network servers. 19 | * Logging options. 20 | * Enhanced [http.Server](https://golang.org/pkg/net/http/#Server). 21 | * Ultra fast UUID-like ID generator. 22 | * Activity tracking. 23 | * Support for [systemd socket activation](http://0pointer.de/blog/projects/socket-activation.html). 24 | * Support for [github.com/spf13/cobra][cobra]. 25 | 26 | Requirements 27 | ------------ 28 | 29 | Go 1.11 or later, though the code is expected to work with Go 1.7. 30 | 31 | Specifications 32 | -------------- 33 | 34 | Commands using this framework implement these external specifications: 35 | 36 | ### Command-line options 37 | 38 | * `-logfile FILE` 39 | 40 | Output logs to FILE instead of standard error. 41 | 42 | * `-loglevel LEVEL` 43 | 44 | Change logging threshold to LEVEL. Default is `info`. 45 | LEVEL is one of `critical`, `error`, `warning`, `info`, or `debug`. 46 | 47 | * `-logformat FORMAT` 48 | 49 | Change log formatter. Default is `plain`. 50 | FORMAT is one of `plain`, `logfmt`, or `json`. 51 | 52 | ### Signal Handlers 53 | 54 | * `SIGUSR1` 55 | 56 | If `-logfile` is specified, this signal make the program reopen 57 | the log file to cooperate with an external log rotation program. 58 | 59 | On Windows, this is not implemented. 60 | 61 | * `SIGINT` and `SIGTERM` 62 | 63 | These signals cancel the context of the global environment, 64 | and hence goroutines registered with the environment. Usually 65 | this will result in graceful stop of network servers, if any. 66 | 67 | On Windows, only `SIGINT` is handled. 68 | 69 | * `SIGHUP` 70 | 71 | This signal is used to restart network servers gracefully. 72 | Internally, the main (master) process restarts its child process. 73 | The PID of the master process thus will not change. 74 | 75 | There is one limitation: the location of log file cannot be changed 76 | by graceful restart. To change log file location, the server need 77 | to be (gracefully) stopped and started. 78 | 79 | On Windows, this is not implemented. 80 | 81 | * `SIGPIPE` 82 | 83 | The framework changes [the way Go handles SIGPIPE slightly](https://golang.org/pkg/os/signal/#hdr-SIGPIPE). 84 | If a program using this framework receives SIGPIPE when writing to stdout or stderr, the program exits with status code 2. 85 | See [#15](https://github.com/cybozu-go/well/issues/15) for details. 86 | 87 | ### Environment variables 88 | 89 | * `REQUEST_ID_HEADER` 90 | 91 | The value of this variable is used as HTTP header name. 92 | The HTTP header is used to track activities across services. 93 | The default header name is "X-Cybozu-Request-ID". 94 | 95 | * `CYBOZU_LISTEN_FDS` 96 | 97 | This is used internally for graceful restart. 98 | 99 | * `CANCELLATION_DELAY_SECONDS` 100 | 101 | After `SIGINT` or `SIGTERM` received, the signal handler waits for the seconds before cancelling the context. 102 | The default value is 5 sec. 103 | 104 | Usage 105 | ----- 106 | 107 | Read [Tutorial][wiki], [the design notes](DESIGN.md) and [API documents](https://pkg.go.dev/github.com/cybozu-go/well). 108 | 109 | A [wiki page for cobra users](https://github.com/cybozu-go/well/wiki/Use-with-spf13-cobra) is also available. 110 | 111 | Real world examples 112 | ------------------- 113 | 114 | * [`github.com/cybozu-go/coil`](https://github.com/cybozu-go/coil) uses well with [cobra][]. 115 | * [`github.com/cybozu-go/aptutil`](https://github.com/cybozu-go/aptutil) 116 | * [`github.com/cybozu-go/goma`](https://github.com/cybozu-go/goma) 117 | * [`github.com/cybozu-go/transocks`](https://github.com/cybozu-go/transocks) 118 | * [`github.com/cybozu-go/usocksd`](https://github.com/cybozu-go/usocksd) 119 | 120 | Pull requests are welcome to add your project to this list! 121 | 122 | [releases]: https://github.com/cybozu-go/well/releases 123 | [wiki]: https://github.com/cybozu-go/well/wiki/Tutorial 124 | [cobra]: https://github.com/spf13/cobra 125 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [Unreleased] 7 | 8 | ## [1.11.2] - 2023-02-01 9 | 10 | ### Changed 11 | - Update dependencies in [#43](https://github.com/cybozu-go/well/pull/43) 12 | - Upgrade direct dependencies in go.mod 13 | - Update Golang used for testing from 1.18 to 1.19 14 | - Update GitHub Actions 15 | - Fix for deprecated "io/ioutil" in [#43](https://github.com/cybozu-go/well/pull/43) 16 | 17 | ## [1.11.1] - 2022-08-30 18 | 19 | ### Changed 20 | - Update dependencies ([#40](https://github.com/cybozu-go/well/pull/40)). 21 | 22 | ## [1.11.0] - 2021-12-15 23 | 24 | ### Changed 25 | - Update dependencies ([#39](https://github.com/cybozu-go/well/pull/39)). 26 | 27 | ### Removed 28 | - The wrapped `http.ResponseWriter` no longer implements `http.CloseNotifier` ([#39](https://github.com/cybozu-go/well/pull/39)). 29 | 30 | ## [1.10.0] - 2020-01-24 31 | ### Added 32 | - Getter for requestIDHeader ([#35](https://github.com/cybozu-go/well/pull/35)). 33 | 34 | ### Changed 35 | - Sleep for some seconds after signal handler receives a signal ([#37](https://github.com/cybozu-go/well/pull/37)). 36 | 37 | ## [1.9.0] - 2019-09-24 38 | ### Added 39 | - HTTPServer: add support for HTTP/2 request handling ([#27](https://github.com/cybozu-go/well/pull/27), [#32](https://github.com/cybozu-go/well/pull/32)). 40 | 41 | ### Changed 42 | - HTTPServer: fix remote IP access logging ([#31](https://github.com/cybozu-go/well/pull/31)). 43 | 44 | ## [1.8.1] - 2018-10-21 45 | ### Changed 46 | - Remove dependency on `github.com/spf13/cobra`. 47 | 48 | ## [1.8.0] - 2018-10-21 49 | ### Added 50 | - Support for users of [github.com/spf13/cobra](https://github.com/spf13/cobra) and its friends ([#24](https://github.com/cybozu-go/well/pull/24)). 51 | 52 | ## [1.7.0] - 2018-10-20 53 | ### Changed 54 | - Use http.Server.Shutdown for Go 1.8+ ([#23](https://github.com/cybozu-go/well/pull/23)). 55 | - Rename the project from `cmd` to `well`. 56 | - Remove dependency on `github.com/pkg/errors`. 57 | 58 | ## [1.6.0] - 2018-09-14 59 | ### Added 60 | - Opt in to [Go modules](https://github.com/golang/go/wiki/Modules). 61 | 62 | ## [1.5.0] - 2017-04-28 63 | ### Added 64 | - `IsSystemdService` to detect if running as a systemd service. 65 | 66 | ### Changed 67 | - Ignore SIGPIPE for systemd, reverts [#15](https://github.com/cybozu-go/well/pull/15) ([#17](https://github.com/cybozu-go/well/pull/17)). 68 | 69 | ## [1.4.2] - 2017-04-26 70 | ### Changed 71 | - Exit abnormally upon SIGPIPE ([#15](https://github.com/cybozu-go/well/pull/15)). 72 | 73 | ## [1.4.1] - 2017-03-01 74 | ### Changed 75 | - Fix `NewEnvironment` documentation. 76 | - Ignore SIGPIPE for systemd ([#13](https://github.com/cybozu-go/well/pull/13)). 77 | 78 | ## [1.4.0] - 2016-09-10 79 | ### Added 80 | - `BackgroundWithID` creates a new context inheriting the request ID. 81 | - `Graceful` for Windows to make porting easy, though it does not restart. 82 | 83 | ### Changed 84 | - Fix Windows support by [@mattn](https://github.com/mattn). 85 | - Fix a subtle data race in `Graceful`. 86 | 87 | ## [1.3.0] - 2016-09-02 88 | ### Added 89 | - `GoWithID` starts a goroutine with a new request tracking ID. 90 | 91 | ### Changed 92 | - `Go` no longer issues new ID automatically. Use `GoWithID` instead. 93 | 94 | ## [1.2.0] - 2016-08-31 95 | ### Added 96 | - `Graceful` for network servers to implement graceful restart. 97 | - `SystemdListeners` returns `[]net.Listener` for [systemd socket activation][activation]. 98 | 99 | ### Changed 100 | - Optimize `IDGenerator` performance. 101 | - `Server.Handler` closes connection. 102 | - Lower `Environment.Wait` log to debug level. 103 | 104 | ## [1.1.0] - 2016-08-24 105 | ### Added 106 | - `IDGenerator` generates UUID-like ID string for request tracking. 107 | - `Go` issues new request tracking ID and store it in the derived context. 108 | - `HTTPClient`, a wrapper for `http.Client` that exports request tracking ID and logs results. 109 | - `LogCmd`, a wrapper for `exec.Cmd` that records command execution results together with request tracking ID. 110 | 111 | ### Changed 112 | - `HTTPServer` adds or imports request tracking ID for every request. 113 | - `Server` adds request tracking ID for each new connection. 114 | - Install signal handler only for the global environment. 115 | 116 | ### Removed 117 | - `Context` method of `Environment` is removed. It was a design flaw. 118 | 119 | ## [1.0.1] - 2016-08-22 120 | ### Changed 121 | - Update docs. 122 | - Use [cybozu-go/netutil](https://github.com/cybozu-go/netutil). 123 | - Conform to cybozu-go/log v1.1.0 spec. 124 | 125 | [activation]: http://0pointer.de/blog/projects/socket-activation.html 126 | [Unreleased]: https://github.com/cybozu-go/cmd/compare/v1.11.2...HEAD 127 | [1.11.2]: https://github.com/cybozu-go/cmd/compare/v1.11.1...v1.11.2 128 | [1.11.1]: https://github.com/cybozu-go/cmd/compare/v1.11.0...v1.11.1 129 | [1.11.0]: https://github.com/cybozu-go/cmd/compare/v1.10.0...v1.11.0 130 | [1.10.0]: https://github.com/cybozu-go/cmd/compare/v1.9.0...v1.10.0 131 | [1.9.0]: https://github.com/cybozu-go/cmd/compare/v1.8.1...v1.9.0 132 | [1.8.1]: https://github.com/cybozu-go/cmd/compare/v1.8.0...v1.8.1 133 | [1.8.0]: https://github.com/cybozu-go/cmd/compare/v1.7.0...v1.8.0 134 | [1.7.0]: https://github.com/cybozu-go/cmd/compare/v1.6.0...v1.7.0 135 | [1.6.0]: https://github.com/cybozu-go/cmd/compare/v1.5.0...v1.6.0 136 | [1.5.0]: https://github.com/cybozu-go/cmd/compare/v1.4.2...v1.5.0 137 | [1.4.2]: https://github.com/cybozu-go/cmd/compare/v1.4.1...v1.4.2 138 | [1.4.1]: https://github.com/cybozu-go/cmd/compare/v1.4.0...v1.4.1 139 | [1.4.0]: https://github.com/cybozu-go/cmd/compare/v1.3.0...v1.4.0 140 | [1.3.0]: https://github.com/cybozu-go/cmd/compare/v1.2.0...v1.3.0 141 | [1.2.0]: https://github.com/cybozu-go/cmd/compare/v1.1.0...v1.2.0 142 | [1.1.0]: https://github.com/cybozu-go/cmd/compare/v1.0.1...v1.1.0 143 | [1.0.1]: https://github.com/cybozu-go/cmd/compare/v1.0.0...v1.0.1 144 | -------------------------------------------------------------------------------- /graceful_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package well 5 | 6 | import ( 7 | "context" 8 | "errors" 9 | "io" 10 | "net" 11 | "os" 12 | "os/exec" 13 | "os/signal" 14 | "strconv" 15 | "syscall" 16 | "time" 17 | 18 | "github.com/cybozu-go/log" 19 | ) 20 | 21 | const ( 22 | listenEnv = "CYBOZU_LISTEN_FDS" 23 | 24 | restartWait = 10 * time.Millisecond 25 | ) 26 | 27 | func isMaster() bool { 28 | return len(os.Getenv(listenEnv)) == 0 29 | } 30 | 31 | type fileFunc interface { 32 | File() (f *os.File, err error) 33 | } 34 | 35 | func listenerFiles(listeners []net.Listener) ([]*os.File, error) { 36 | files := make([]*os.File, 0, len(listeners)) 37 | for _, l := range listeners { 38 | fd, ok := l.(fileFunc) 39 | if !ok { 40 | return nil, errors.New("no File() method for " + l.Addr().String()) 41 | } 42 | f, err := fd.File() 43 | if err != nil { 44 | return nil, err 45 | } 46 | files = append(files, f) 47 | } 48 | return files, nil 49 | } 50 | 51 | func restoreListeners(envvar string) ([]net.Listener, error) { 52 | nfds, err := strconv.Atoi(os.Getenv(envvar)) 53 | defer os.Unsetenv(envvar) 54 | 55 | if err != nil { 56 | return nil, err 57 | } 58 | if nfds == 0 { 59 | return nil, nil 60 | } 61 | 62 | log.Debug("well: restored listeners", map[string]interface{}{ 63 | "nfds": nfds, 64 | }) 65 | 66 | ls := make([]net.Listener, 0, nfds) 67 | for i := 0; i < nfds; i++ { 68 | fd := 3 + i 69 | f := os.NewFile(uintptr(fd), "FD"+strconv.Itoa(fd)) 70 | l, err := net.FileListener(f) 71 | f.Close() 72 | if err != nil { 73 | return nil, err 74 | } 75 | ls = append(ls, l) 76 | } 77 | return ls, nil 78 | } 79 | 80 | // SystemdListeners returns listeners from systemd socket activation. 81 | func SystemdListeners() ([]net.Listener, error) { 82 | pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) 83 | if err != nil { 84 | return nil, err 85 | } 86 | if pid != os.Getpid() { 87 | return nil, nil 88 | } 89 | return restoreListeners("LISTEN_FDS") 90 | } 91 | 92 | // Run runs the graceful restarting server. 93 | // 94 | // If this is the master process, Run starts a child process, 95 | // and installs SIGHUP handler to restarts the child process. 96 | // 97 | // If this is a child process, Run simply calls g.Serve. 98 | // 99 | // Run returns immediately in the master process, and never 100 | // returns in the child process. 101 | func (g *Graceful) Run() { 102 | if isMaster() { 103 | env := g.Env 104 | if env == nil { 105 | env = defaultEnv 106 | } 107 | env.Go(g.runMaster) 108 | return 109 | } 110 | 111 | lns, err := restoreListeners(listenEnv) 112 | if err != nil { 113 | log.ErrorExit(err) 114 | } 115 | log.DefaultLogger().SetDefaults(map[string]interface{}{ 116 | "pid": os.Getpid(), 117 | }) 118 | log.Info("well: new child", nil) 119 | g.Serve(lns) 120 | 121 | // child process should not return. 122 | os.Exit(0) 123 | } 124 | 125 | // runMaster is the main function of the master process. 126 | func (g *Graceful) runMaster(ctx context.Context) error { 127 | logger := log.DefaultLogger() 128 | 129 | // prepare listener files 130 | listeners, err := g.Listen() 131 | if err != nil { 132 | return err 133 | } 134 | files, err := listenerFiles(listeners) 135 | if err != nil { 136 | return err 137 | } 138 | if len(files) == 0 { 139 | return errors.New("no listener") 140 | } 141 | defer func() { 142 | for _, f := range files { 143 | f.Close() 144 | } 145 | // we cannot close listeners no sooner than this point 146 | // because net.UnixListener removes the socket file on Close. 147 | for _, l := range listeners { 148 | l.Close() 149 | } 150 | }() 151 | 152 | sighup := make(chan os.Signal, 2) 153 | signal.Notify(sighup, syscall.SIGHUP) 154 | 155 | RESTART: 156 | child := g.makeChild(files) 157 | clog, err := child.StderrPipe() 158 | if err != nil { 159 | return err 160 | } 161 | copyDone := make(chan struct{}) 162 | // clog will be closed on child.Wait(). 163 | go copyLog(logger, clog, copyDone) 164 | 165 | done := make(chan error, 1) 166 | err = child.Start() 167 | if err != nil { 168 | return err 169 | } 170 | go func() { 171 | <-copyDone 172 | done <- child.Wait() 173 | }() 174 | 175 | select { 176 | case err := <-done: 177 | return err 178 | case <-sighup: 179 | child.Process.Signal(syscall.SIGTERM) 180 | log.Warn("well: got sighup", nil) 181 | time.Sleep(restartWait) 182 | goto RESTART 183 | case <-ctx.Done(): 184 | child.Process.Signal(syscall.SIGTERM) 185 | if g.ExitTimeout == 0 { 186 | <-done 187 | return nil 188 | } 189 | select { 190 | case <-done: 191 | return nil 192 | case <-time.After(g.ExitTimeout): 193 | logger.Warn("well: timeout child exit", nil) 194 | return nil 195 | } 196 | } 197 | } 198 | 199 | func (g *Graceful) makeChild(files []*os.File) *exec.Cmd { 200 | child := exec.Command(os.Args[0], os.Args[1:]...) 201 | child.Env = os.Environ() 202 | child.Env = append(child.Env, listenEnv+"="+strconv.Itoa(len(files))) 203 | child.ExtraFiles = files 204 | return child 205 | } 206 | 207 | func copyLog(logger *log.Logger, r io.Reader, done chan<- struct{}) { 208 | defer func() { 209 | close(done) 210 | }() 211 | 212 | var unwritten []byte 213 | buf := make([]byte, 1<<20) 214 | 215 | for { 216 | n, err := r.Read(buf) 217 | if err != nil { 218 | if len(unwritten) == 0 { 219 | if n > 0 { 220 | logger.WriteThrough(buf[0:n]) 221 | } 222 | return 223 | } 224 | unwritten = append(unwritten, buf[0:n]...) 225 | logger.WriteThrough(unwritten) 226 | return 227 | } 228 | if n == 0 { 229 | continue 230 | } 231 | if buf[n-1] != '\n' { 232 | unwritten = append(unwritten, buf[0:n]...) 233 | continue 234 | } 235 | if len(unwritten) == 0 { 236 | err = logger.WriteThrough(buf[0:n]) 237 | if err != nil { 238 | return 239 | } 240 | continue 241 | } 242 | unwritten = append(unwritten, buf[0:n]...) 243 | err = logger.WriteThrough(unwritten) 244 | if err != nil { 245 | return 246 | } 247 | unwritten = unwritten[:0] 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /DESIGN.md: -------------------------------------------------------------------------------- 1 | Design notes 2 | ============ 3 | 4 | **Be warned that statements here may not be correct nor up-to-date.** 5 | 6 | Logging 7 | ------- 8 | 9 | The framework uses [cybozu-go/log][log] for structured logging, and 10 | provides command-line flags to configure logging. 11 | 12 | The framework also provides `LogConfig` struct that can be load from 13 | JSON or [TOML][] file, and configures the default logger according to 14 | the struct member values. The command-line flags take precedence 15 | over the member values, if specified. 16 | 17 | Context and `Environment` 18 | ------------------------- 19 | 20 | [Context](https://blog.golang.org/context) itself is quite useful. 21 | 22 | `Environment` adds a bit more usefulness to context with these methods: 23 | 24 | * `Go(f func(ctx context.Context) error)` 25 | 26 | This function starts a goroutine that executes `f`. If `f` returns 27 | non-nil error, the framework calls `Cancel()` with that error. 28 | 29 | `ctx` is a derived context from the base context that is to be 30 | canceled when f returns. 31 | 32 | * `Stop()` 33 | 34 | This function just declares no further `Go()` will be called. 35 | 36 | Calling `Stop()` is optional if and only if `Cancel()` is 37 | guaranteed to be called at some point. For instance, if the 38 | program runs until SIGINT or SIGTERM, `Stop()` is optional. 39 | 40 | * `Cancel(err error)` 41 | 42 | This function cancels the base context and closes all managed 43 | listeners. After `Cancel()`, `Go()` will not start new goroutines 44 | any longer. 45 | 46 | * `Wait() error` 47 | 48 | This function waits for `Stop()` or `Cancel()` being called and then 49 | waits for all managed goroutines to finish. The return value will be 50 | the error that was passed to `Cancel()`, or nil. 51 | 52 | Basically, an environment can be considered as a barrier synchronizer. 53 | 54 | There is no way to obtain the context inside `Environment` other than `Go()`. 55 | If `Environment` had `Context() context.Context` method, users would 56 | almost fail to stop goroutines gracefully as such goroutines will not 57 | be waited for by `Wait()`. 58 | 59 | ### The global environment 60 | 61 | The framework creates and provides a global environment. 62 | 63 | It also installs a signal handler as described in the next section. 64 | 65 | Signal handlers 66 | --------------- 67 | 68 | The framework implicitly starts a goroutine to handle SIGINT and SIGTERM. 69 | The goroutine, when such a signal is sent, will call the global 70 | environment's `Cancel()` with a special error value. 71 | 72 | The error value can be identified by `IsSignaled` function. 73 | 74 | If a command-line flag is used to write logs to an external file, the 75 | framework installs SIGUSR1 signal handler to reopen the file to work 76 | with external log rotation programs. 77 | 78 | Generic server 79 | -------------- 80 | 81 | Suppose that we create a simple TCP server on this framework. 82 | 83 | A naive idea is to use `Go()` to start goroutines for every accepted 84 | connections. However, since `Go()` acquires mutex, such an 85 | implementation would limit concurrency of the server. 86 | 87 | In order to implement high performance servers, the server should 88 | manage all goroutines started by the server by itself. The framework 89 | provides `Server` as a generic implementation of such servers. 90 | 91 | HTTP Server 92 | ----------- 93 | 94 | As to [http.Server](https://golang.org/pkg/net/http/#Server), we extend it for: 95 | 96 | 1. Graceful server termination 97 | 98 | `http.Server` can gracefully shutdown by the following steps: 99 | 100 | 1. Close all listeners. 101 | 2. Call `SetKeepAlivesEnabled(false)`. 102 | 3. Call `net.Conn.SetReadDeadline(time.Now())` for all idle connections. 103 | 4. Wait all connections to be closed. 104 | 105 | For 3 and 4, `http.Server.ConnState` callback can be used to track 106 | connection status. 107 | 108 | Note that `ReadTimeout` can work as keep-alive timeout according 109 | to the go source code (at least as of Go 1.7). Since keep-alived 110 | connections may block on `conn.Read` to wait for the next request, 111 | we need to cancel `conn.Read` for quicker shutdown. 112 | 113 | c.f. https://groups.google.com/forum/#!topic/golang-nuts/5E4gM7EzdLw 114 | 115 | 2. Better logging 116 | 117 | Use [cybozu-go/log][log] for structured logging of error messages, 118 | and output access logs by wrapping `http.Server.Handler`. 119 | 120 | 3. Cancel running handlers 121 | 122 | Since Go 1.7, `http.Request` has `Context()` that returns a context 123 | that will be canceled when `Handler.ServeHTTP()` returns. The 124 | framework replaces the context so that the context is also canceled 125 | when the server is about to stop in addition to the original behavior. 126 | 127 | To implement these, the framework provides a wrapping struct: 128 | 129 | * `HTTPServer` 130 | 131 | This struct embeds http.Server and overrides `Serve`, `ListenAndServe`, 132 | and `ListenAndServeTLS` methods. 133 | 134 | Tracking activities 135 | ------------------- 136 | 137 | Good programs record logs that help users to track problems. 138 | 139 | Among others, requests to other servers and execution of other programs 140 | are significant. The framework provides helpers to log these events. 141 | 142 | Individual logs may help track problems but are not enough without 143 | information about relationship between logs. For example, activities 144 | to complete a request for a REST API may involve events like: 145 | 146 | - Command executions 147 | - Requests for other services 148 | 149 | What we need is to include an identifier in log fields for each 150 | distinguished incoming request. We call it *request ID*. 151 | 152 | Note that, unfortunately, Go does not provide ID for goroutines, hence 153 | we need to have an ID in contexts. 154 | 155 | Inside the framework, request ID is conveyed as a context value. 156 | The context key is `RequestIDContextKey`. 157 | 158 | Request ID is imported/exported via HTTP header "X-Cybozu-Request-ID". 159 | The header can be changed through "REQUEST_ID_HEADER" environment variable. 160 | `HTTPServer` and `HTTPClient` do this automatically. 161 | 162 | Related structs and functions: 163 | 164 | * `HTTPClient` 165 | 166 | This is a thin wrapper for `http.Client`. It overrides `Do()` to 167 | add "X-Cybozu-Request-ID" header and to record request logs. 168 | Since only `Do()` can take `http.Request` explicitly and request 169 | context need to added by `http.Request.WithContext()`, other methods 170 | cannot be used. They (`Get()`, `Head()`, `Post()`, and `PostForm()`) 171 | would cause panic if called. 172 | 173 | * `HTTPServer` 174 | 175 | If an incoming request has "X-Cybozu-Request-ID" header, it 176 | populates the header value into the request context. 177 | 178 | * `FieldsFromContext` 179 | 180 | This is a function to construct fields of logs from a context. 181 | Specifically, if the context have a request ID value, it is 182 | added to the fields as "request_id". 183 | 184 | * `LogCmd` 185 | 186 | This is a wrapper for `exec.Cmd`. It overrides methods to 187 | record execution logs. If LogCmd.Context is not nil and 188 | have a request ID value, the ID is logged as "request_id". 189 | 190 | * `CommandContext` 191 | 192 | This function is similar to `exec.CommandContext` but creates 193 | and returns `*LogCmd`. 194 | 195 | Graceful restart 196 | ---------------- 197 | 198 | Graceful restart of network servers need to keep listening sockets 199 | while restarting server programs. To keep listening sockets, a 200 | master process should exist. 201 | 202 | The master process executes a child process with file descriptors 203 | of listening sockets. The child process converts them into 204 | `net.Listner` objects and uses them to accept connections. 205 | 206 | To restart, the master process handles SIGHUP. When got a SIGHUP, 207 | the master process send SIGTERM to the child. The child process 208 | will immediately close the listeners as long as they are built on 209 | this framework. Therefore, the master process can create a new 210 | child process soon after SIGTERM sent. 211 | 212 | Another thing we need to care is how to serialize writes to log files. 213 | Our solution is that the master process gathers logs from children 214 | via stderr and writes them to logs. For this to work, we need to: 215 | 216 | 1. communicate between the master and children via pipe on stderr. 217 | 2. make `LogConfig.Apply()` ignore filename in child processes. 218 | 219 | Related structs: 220 | 221 | * `Graceful` 222 | 223 | This struct has only one public method `Run`. 224 | Users must call `Run` in their `main`. 225 | 226 | 227 | [log]: https://github.com/cybozu-go/log/ 228 | [TOML]: https://github.com/toml-lang/toml 229 | -------------------------------------------------------------------------------- /http_test.go: -------------------------------------------------------------------------------- 1 | package well 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto/tls" 7 | "encoding/json" 8 | "fmt" 9 | "io" 10 | "net/http" 11 | "runtime" 12 | "testing" 13 | "time" 14 | 15 | "github.com/cybozu-go/log" 16 | "golang.org/x/net/http2" 17 | ) 18 | 19 | const ( 20 | testUUID = "cad48be9-285c-4b70-8177-33e41550a3c8" 21 | ) 22 | 23 | func newMux(env *Environment, sleepCh chan struct{}) http.Handler { 24 | mux := http.NewServeMux() 25 | mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { 26 | v := r.Context().Value(RequestIDContextKey) 27 | if v == nil { 28 | http.Error(w, "No request ID in context", http.StatusInternalServerError) 29 | return 30 | } 31 | 32 | fmt.Printf("req=%+v\n", r) 33 | if r.ProtoMajor == 2 { 34 | if _, ok := w.(StdResponseWriter2); !ok { 35 | http.Error(w, "not implement StdResponseWriter2", http.StatusInternalServerError) 36 | return 37 | } 38 | w.Write([]byte("hello2")) 39 | return 40 | } 41 | 42 | if _, ok := w.(StdResponseWriter); !ok { 43 | http.Error(w, "not implement StdResponseWriter", http.StatusInternalServerError) 44 | return 45 | } 46 | w.Write([]byte("hello")) 47 | }) 48 | mux.HandleFunc("/sleep", func(w http.ResponseWriter, r *http.Request) { 49 | close(sleepCh) 50 | time.Sleep(1 * time.Second) 51 | }) 52 | mux.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) { 53 | env.Cancel(nil) 54 | }) 55 | return mux 56 | } 57 | 58 | func newHTTPClient() *http.Client { 59 | tr := &http.Transport{ 60 | DisableCompression: true, 61 | MaxIdleConnsPerHost: 10, 62 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 63 | } 64 | err := http2.ConfigureTransport(tr) 65 | if err != nil { 66 | panic(err) 67 | } 68 | return &http.Client{ 69 | Transport: tr, 70 | } 71 | } 72 | 73 | func TestHTTPServer(t *testing.T) { 74 | t.Parallel() 75 | 76 | env := NewEnvironment(context.Background()) 77 | logger := log.NewLogger() 78 | out := new(bytes.Buffer) 79 | logger.SetOutput(out) 80 | logger.SetFormatter(log.JSONFormat{}) 81 | 82 | s := &HTTPServer{ 83 | Server: &http.Server{ 84 | Addr: "localhost:16555", 85 | Handler: newMux(env, nil), 86 | ReadTimeout: 3 * time.Second, 87 | }, 88 | AccessLog: logger, 89 | Env: env, 90 | } 91 | err := s.ListenAndServe() 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | 96 | cl := newHTTPClient() 97 | 98 | req, err := http.NewRequest("GET", "http://localhost:16555/hello", nil) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | req.Header.Set(requestIDHeader, testUUID) 103 | resp, err := cl.Do(req) 104 | if err != nil { 105 | t.Fatal(err) 106 | } 107 | data, _ := io.ReadAll(resp.Body) 108 | resp.Body.Close() 109 | if resp.StatusCode != http.StatusOK { 110 | t.Errorf("%d %s", resp.StatusCode, string(data)) 111 | } 112 | if !bytes.Equal(data, []byte("hello")) { 113 | t.Error(`!bytes.Equal(data, []byte("hello"))`) 114 | } 115 | 116 | resp, err = cl.Get("http://localhost:16555/notfound") 117 | if err != nil { 118 | t.Fatal(err) 119 | } 120 | if resp.StatusCode != http.StatusNotFound { 121 | t.Errorf(`resp.StatusCode != http.StatusNotFound`) 122 | } 123 | 124 | resp, err = cl.Get("http://localhost:16555/shutdown") 125 | if err != nil { 126 | t.Fatal(err) 127 | } 128 | data, _ = io.ReadAll(resp.Body) 129 | resp.Body.Close() 130 | if resp.StatusCode != http.StatusOK { 131 | t.Errorf("%d %s", resp.StatusCode, string(data)) 132 | } 133 | 134 | cl.CloseIdleConnections() 135 | 136 | waitStart := time.Now() 137 | err = env.Wait() 138 | if err != nil { 139 | t.Error(err) 140 | } 141 | if time.Since(waitStart) > time.Second { 142 | t.Error("too long to shutdown") 143 | } 144 | 145 | testAccessLog(bytes.NewReader(out.Bytes()), t, 5) 146 | } 147 | 148 | func TestHTTPServer2(t *testing.T) { 149 | t.Parallel() 150 | 151 | env := NewEnvironment(context.Background()) 152 | logger := log.NewLogger() 153 | out := new(bytes.Buffer) 154 | logger.SetOutput(out) 155 | logger.SetFormatter(log.JSONFormat{}) 156 | 157 | s := &HTTPServer{ 158 | Server: &http.Server{ 159 | Addr: "localhost:16556", 160 | Handler: newMux(env, nil), 161 | ReadTimeout: 3 * time.Second, 162 | }, 163 | AccessLog: logger, 164 | Env: env, 165 | } 166 | err := s.ListenAndServeTLS("testdata/cert.pem", "testdata/key.pem") 167 | if err != nil { 168 | t.Fatal(err) 169 | } 170 | 171 | cl := newHTTPClient() 172 | 173 | req, err := http.NewRequest("GET", "https://localhost:16556/hello", nil) 174 | if err != nil { 175 | t.Fatal(err) 176 | } 177 | req.Header.Set(requestIDHeader, testUUID) 178 | resp, err := cl.Do(req) 179 | if err != nil { 180 | t.Fatal(err) 181 | } 182 | data, _ := io.ReadAll(resp.Body) 183 | resp.Body.Close() 184 | if resp.StatusCode != http.StatusOK { 185 | t.Errorf("%d %s", resp.StatusCode, string(data)) 186 | } 187 | if !bytes.Equal(data, []byte("hello2")) { 188 | t.Error(`!bytes.Equal(data, []byte("hello2"))`) 189 | } 190 | 191 | resp, err = cl.Get("https://localhost:16556/notfound") 192 | if err != nil { 193 | t.Fatal(err) 194 | } 195 | if resp.StatusCode != http.StatusNotFound { 196 | t.Errorf(`resp.StatusCode != http.StatusNotFound`) 197 | } 198 | 199 | resp, err = cl.Get("https://localhost:16556/shutdown") 200 | if err != nil { 201 | t.Fatal(err) 202 | } 203 | data, _ = io.ReadAll(resp.Body) 204 | resp.Body.Close() 205 | if resp.StatusCode != http.StatusOK { 206 | t.Errorf("%d %s", resp.StatusCode, string(data)) 207 | } 208 | 209 | waitStart := time.Now() 210 | err = env.Wait() 211 | if err != nil { 212 | t.Error(err) 213 | } 214 | if time.Since(waitStart) > 3*time.Second { 215 | t.Error("too long to shutdown") 216 | } 217 | 218 | testAccessLog(bytes.NewReader(out.Bytes()), t, 6) 219 | } 220 | 221 | func testAccessLog(r io.Reader, t *testing.T, helloLength int64) { 222 | decoder := json.NewDecoder(r) 223 | 224 | accessLogs := make([]*AccessLog, 0, 3) 225 | for decoder.More() { 226 | al := new(AccessLog) 227 | err := decoder.Decode(al) 228 | if err != nil { 229 | t.Fatal(err) 230 | } 231 | accessLogs = append(accessLogs, al) 232 | } 233 | 234 | if len(accessLogs) != 3 { 235 | t.Fatal(`len(accessLogs) != 3`) 236 | } 237 | 238 | helloLog := accessLogs[0] 239 | notfoundLog := accessLogs[1] 240 | 241 | if time.Since(helloLog.LoggedAt) > time.Minute { 242 | t.Error(`time.Since(helloLog.LoggedAt) > time.Minute`) 243 | } 244 | if time.Since(notfoundLog.LoggedAt) > time.Minute { 245 | t.Error(`time.Since(notfoundLog.LoggedAt) > time.Minute`) 246 | } 247 | if helloLog.Severity != "info" { 248 | t.Error(`helloLog.Severity != "info"`) 249 | } 250 | if notfoundLog.Severity != "warning" { 251 | t.Error(`notfoundLog.Severity != "warning"`) 252 | } 253 | if helloLog.Type != "access" { 254 | t.Error(`helloLog.Type != "access"`) 255 | } 256 | if helloLog.StatusCode != http.StatusOK { 257 | t.Error(`helloLog.StatusCode != http.StatusOK`) 258 | } 259 | if notfoundLog.StatusCode != http.StatusNotFound { 260 | t.Error(`notfoundLog.StatusCode != http.StatusNotFound`) 261 | } 262 | if helloLog.Method != "GET" { 263 | t.Error(`helloLog.Method != "GET"`) 264 | } 265 | if helloLog.RequestURI != "/hello" { 266 | t.Error(`helloLog.RequestURI != "/hello"`) 267 | } 268 | if notfoundLog.RequestURI != "/notfound" { 269 | t.Error(`notfoundLog.RequestURI != "/notfound"`) 270 | } 271 | if helloLog.ResponseLength != helloLength { 272 | t.Error(`helloLog.ResponseLength != helloLength`) 273 | } 274 | if helloLog.RequestID != testUUID { 275 | t.Error(`helloLog.RequestID != testUUID`) 276 | } 277 | } 278 | 279 | func TestHTTPServerTimeout(t *testing.T) { 280 | if runtime.GOOS == "windows" { 281 | t.Skip("windows doesn't support FileListener") 282 | } 283 | t.Parallel() 284 | 285 | env := NewEnvironment(context.Background()) 286 | sleepCh := make(chan struct{}) 287 | s := &HTTPServer{ 288 | Server: &http.Server{ 289 | Addr: "localhost:16557", 290 | Handler: newMux(env, sleepCh), 291 | }, 292 | ShutdownTimeout: 50 * time.Millisecond, 293 | Env: env, 294 | } 295 | err := s.ListenAndServe() 296 | if err != nil { 297 | t.Fatal(err) 298 | } 299 | 300 | cl := newHTTPClient() 301 | go func() { 302 | resp, err := cl.Get("http://localhost:16557/sleep") 303 | if err != nil { 304 | return 305 | } 306 | resp.Body.Close() 307 | }() 308 | 309 | <-sleepCh 310 | resp, err := cl.Get("http://localhost:16557/shutdown") 311 | if err != nil { 312 | t.Fatal(err) 313 | } 314 | data, _ := io.ReadAll(resp.Body) 315 | resp.Body.Close() 316 | if resp.StatusCode != http.StatusOK { 317 | t.Fatalf("%d %s", resp.StatusCode, string(data)) 318 | } 319 | 320 | err = env.Wait() 321 | if err != nil { 322 | t.Error(err) 323 | } 324 | if !s.TimedOut() { 325 | t.Error(`!s.TimedOut()`) 326 | } 327 | } 328 | 329 | // Client tests 330 | 331 | type testClientHandler struct{} 332 | 333 | func (h testClientHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 334 | uuid := r.Header.Get(requestIDHeader) 335 | if uuid == testUUID { 336 | return 337 | } 338 | http.Error(w, "invalid UUID", http.StatusInternalServerError) 339 | } 340 | 341 | func TestHTTPClient(t *testing.T) { 342 | t.Parallel() 343 | 344 | ctx := context.Background() 345 | env := NewEnvironment(ctx) 346 | ctx = WithRequestID(ctx, testUUID) 347 | 348 | s := &HTTPServer{ 349 | Server: &http.Server{ 350 | Addr: "localhost:16558", 351 | Handler: testClientHandler{}, 352 | }, 353 | Env: env, 354 | } 355 | err := s.ListenAndServe() 356 | if err != nil { 357 | t.Fatal(err) 358 | } 359 | 360 | logger := log.NewLogger() 361 | logger.SetFormatter(log.JSONFormat{}) 362 | buf := new(bytes.Buffer) 363 | logger.SetOutput(buf) 364 | 365 | cl := HTTPClient{ 366 | Client: &http.Client{}, 367 | Severity: log.LvDebug, 368 | Logger: logger, 369 | } 370 | req, err := http.NewRequest("GET", "http://localhost:16558", nil) 371 | if err != nil { 372 | t.Fatal(err) 373 | } 374 | 375 | req = req.WithContext(ctx) 376 | resp, err := cl.Do(req) 377 | if err != nil { 378 | t.Fatal(err) 379 | } 380 | if resp.StatusCode != 200 { 381 | t.Error("bad response:", resp.StatusCode) 382 | } 383 | 384 | if len(buf.Bytes()) != 0 { 385 | t.Error("should not be logged") 386 | } 387 | 388 | req, err = http.NewRequest("GET", "http://localhost:16558", nil) 389 | if err != nil { 390 | t.Fatal(err) 391 | } 392 | 393 | // raise threshold 394 | logger.SetThreshold(log.LvDebug) 395 | 396 | req = req.WithContext(ctx) 397 | _, err = cl.Do(req) 398 | if err != nil { 399 | t.Fatal(err) 400 | } 401 | 402 | var reqlog RequestLog 403 | err = json.Unmarshal(buf.Bytes(), &reqlog) 404 | if err != nil { 405 | t.Fatal(err) 406 | } 407 | 408 | if time.Since(reqlog.LoggedAt) > time.Minute { 409 | t.Error(`time.Since(reqlog.LoggedAt) > time.Minute`) 410 | } 411 | if reqlog.Severity != "debug" { 412 | t.Error(`reqlog.Severity != "debug"`) 413 | } 414 | if reqlog.Type != "http" { 415 | t.Error(`reqlog.Type != "http"`) 416 | } 417 | if reqlog.ResponseTime > 60.0 { 418 | t.Error(`reqlog.ResponseTime > 60.0`) 419 | } 420 | if reqlog.StatusCode != 200 { 421 | t.Error(`reqlog.StatusCode != 200`) 422 | } 423 | if reqlog.URLString != "http://localhost:16558" { 424 | t.Error(`reqlog.URLString != "http://localhost:16558"`) 425 | } 426 | if time.Since(reqlog.StartAt) > time.Minute { 427 | t.Error(`time.Since(reqlog.StartAt) > time.Minute`) 428 | } 429 | if reqlog.RequestID != testUUID { 430 | t.Error(`reqlog.RequestID != testUUID`) 431 | } 432 | 433 | env.Cancel(nil) 434 | err = env.Wait() 435 | if err != nil { 436 | t.Error(err) 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /http_17.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.8 2 | // +build !go1.8 3 | 4 | package well 5 | 6 | import ( 7 | "context" 8 | "crypto/tls" 9 | "io" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "sync" 15 | "sync/atomic" 16 | "time" 17 | 18 | "github.com/cybozu-go/log" 19 | "github.com/cybozu-go/netutil" 20 | ) 21 | 22 | const ( 23 | defaultHTTPReadTimeout = 30 * time.Second 24 | 25 | // request tracking header. 26 | defaultRequestIDHeader = "X-Cybozu-Request-ID" 27 | 28 | requestIDHeaderEnv = "REQUEST_ID_HEADER" 29 | ) 30 | 31 | var ( 32 | requestIDHeader = defaultRequestIDHeader 33 | ) 34 | 35 | func init() { 36 | hn := os.Getenv(requestIDHeaderEnv) 37 | if len(hn) > 0 { 38 | requestIDHeader = hn 39 | } 40 | } 41 | 42 | // HTTPServer is a wrapper for http.Server. 43 | // 44 | // This struct overrides Serve and ListenAndServe* methods. 45 | // 46 | // http.Server members are replaced as following: 47 | // - Handler is replaced with a wrapper handler. 48 | // - ReadTimeout is set to 30 seconds if it is zero. 49 | // - ConnState is replaced with the one provided by the framework. 50 | type HTTPServer struct { 51 | *http.Server 52 | 53 | // AccessLog is a logger for access logs. 54 | // If this is nil, the default logger is used. 55 | AccessLog *log.Logger 56 | 57 | // ShutdownTimeout is the maximum duration the server waits for 58 | // all connections to be closed before shutdown. 59 | // 60 | // Zero duration disables timeout. 61 | ShutdownTimeout time.Duration 62 | 63 | // Env is the environment where this server runs. 64 | // 65 | // The global environment is used if Env is nil. 66 | Env *Environment 67 | 68 | handler http.Handler 69 | wg sync.WaitGroup 70 | timedout int32 71 | 72 | mu sync.Mutex 73 | idleConns map[net.Conn]struct{} 74 | generator *IDGenerator 75 | 76 | initOnce sync.Once 77 | } 78 | 79 | // StdResponseWriter is the interface implemented by 80 | // the ResponseWriter from http.Server. 81 | // 82 | // HTTPServer's ResponseWriter implements this as well. 83 | type StdResponseWriter interface { 84 | http.ResponseWriter 85 | io.ReaderFrom 86 | http.Flusher 87 | http.CloseNotifier 88 | http.Hijacker 89 | WriteString(data string) (int, error) 90 | } 91 | 92 | type logResponseWriter struct { 93 | StdResponseWriter 94 | status int 95 | size int64 96 | } 97 | 98 | func (w *logResponseWriter) WriteHeader(status int) { 99 | w.status = status 100 | w.StdResponseWriter.WriteHeader(status) 101 | } 102 | 103 | func (w *logResponseWriter) Write(data []byte) (int, error) { 104 | n, err := w.StdResponseWriter.Write(data) 105 | w.size += int64(n) 106 | return n, err 107 | } 108 | 109 | func (w *logResponseWriter) ReadFrom(r io.Reader) (int64, error) { 110 | n, err := w.StdResponseWriter.ReadFrom(r) 111 | w.size += n 112 | return n, err 113 | } 114 | 115 | func (w *logResponseWriter) WriteString(data string) (int, error) { 116 | n, err := w.StdResponseWriter.WriteString(data) 117 | w.size += int64(n) 118 | return n, err 119 | } 120 | 121 | // ServeHTTP implements http.Handler interface. 122 | func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 123 | startTime := time.Now() 124 | 125 | lw := &logResponseWriter{w.(StdResponseWriter), http.StatusOK, 0} 126 | ctx, cancel := context.WithCancel(s.Env.ctx) 127 | defer cancel() 128 | 129 | reqid := r.Header.Get(requestIDHeader) 130 | if len(reqid) == 0 { 131 | reqid = s.generator.Generate() 132 | } 133 | ctx = WithRequestID(ctx, reqid) 134 | 135 | s.handler.ServeHTTP(lw, r.WithContext(ctx)) 136 | 137 | fields := map[string]interface{}{ 138 | log.FnType: "access", 139 | log.FnResponseTime: time.Since(startTime).Seconds(), 140 | log.FnProtocol: r.Proto, 141 | log.FnHTTPStatusCode: lw.status, 142 | log.FnHTTPMethod: r.Method, 143 | log.FnURL: r.RequestURI, 144 | log.FnHTTPHost: r.Host, 145 | log.FnRequestSize: r.ContentLength, 146 | log.FnResponseSize: lw.size, 147 | } 148 | ip, _, err := net.SplitHostPort(r.RemoteAddr) 149 | if err != nil { 150 | fields[log.FnRemoteAddress] = ip 151 | } 152 | ua := r.Header.Get("User-Agent") 153 | if len(ua) > 0 { 154 | fields[log.FnHTTPUserAgent] = ua 155 | } 156 | if len(reqid) > 0 { 157 | fields[log.FnRequestID] = reqid 158 | } 159 | 160 | lv := log.LvInfo 161 | switch { 162 | case 500 <= lw.status: 163 | lv = log.LvError 164 | case 400 <= lw.status: 165 | lv = log.LvWarn 166 | } 167 | s.AccessLog.Log(lv, "well: access", fields) 168 | } 169 | 170 | func (s *HTTPServer) init() { 171 | if s.handler != nil { 172 | return 173 | } 174 | 175 | s.idleConns = make(map[net.Conn]struct{}, 100000) 176 | s.generator = NewIDGenerator() 177 | 178 | if s.Server.Handler == nil { 179 | panic("Handler must not be nil") 180 | } 181 | s.handler = s.Server.Handler 182 | s.Server.Handler = s 183 | if s.Server.ReadTimeout == 0 { 184 | s.Server.ReadTimeout = defaultHTTPReadTimeout 185 | } 186 | s.Server.ConnState = func(c net.Conn, state http.ConnState) { 187 | s.mu.Lock() 188 | if state == http.StateIdle { 189 | s.idleConns[c] = struct{}{} 190 | } else { 191 | delete(s.idleConns, c) 192 | } 193 | s.mu.Unlock() 194 | 195 | if state == http.StateNew { 196 | s.wg.Add(1) 197 | return 198 | } 199 | if state == http.StateHijacked || state == http.StateClosed { 200 | s.wg.Done() 201 | } 202 | } 203 | 204 | if s.AccessLog == nil { 205 | s.AccessLog = log.DefaultLogger() 206 | } 207 | 208 | if s.Env == nil { 209 | s.Env = defaultEnv 210 | } 211 | s.Env.Go(s.wait) 212 | } 213 | 214 | func (s *HTTPServer) wait(ctx context.Context) error { 215 | <-ctx.Done() 216 | 217 | s.Server.SetKeepAlivesEnabled(false) 218 | 219 | ch := make(chan struct{}) 220 | 221 | // Interrupt conn.Read for idle connections. 222 | // 223 | // This must be run inside for-loop to catch connections 224 | // going idle at critical timing to acquire s.mu 225 | go func() { 226 | AGAIN: 227 | s.mu.Lock() 228 | for conn := range s.idleConns { 229 | conn.SetReadDeadline(time.Now()) 230 | } 231 | s.mu.Unlock() 232 | select { 233 | case <-ch: 234 | return 235 | default: 236 | } 237 | time.Sleep(10 * time.Millisecond) 238 | goto AGAIN 239 | }() 240 | 241 | go func() { 242 | s.wg.Wait() 243 | close(ch) 244 | }() 245 | 246 | if s.ShutdownTimeout == 0 { 247 | <-ch 248 | return nil 249 | } 250 | 251 | select { 252 | case <-ch: 253 | case <-time.After(s.ShutdownTimeout): 254 | log.Warn("well: timeout waiting for shutdown", nil) 255 | atomic.StoreInt32(&s.timedout, 1) 256 | } 257 | return nil 258 | } 259 | 260 | // TimedOut returns true if the server shut down before all connections 261 | // got closed. 262 | func (s *HTTPServer) TimedOut() bool { 263 | return atomic.LoadInt32(&s.timedout) != 0 264 | } 265 | 266 | // Serve overrides http.Server's Serve method. 267 | // 268 | // Unlike the original, this method returns immediately just after 269 | // starting a goroutine to accept connections. 270 | // 271 | // The framework automatically closes l when the environment's Cancel 272 | // is called. 273 | // 274 | // Serve always returns nil. 275 | func (s *HTTPServer) Serve(l net.Listener) error { 276 | s.initOnce.Do(s.init) 277 | 278 | l = netutil.KeepAliveListener(l) 279 | 280 | go func() { 281 | <-s.Env.ctx.Done() 282 | l.Close() 283 | }() 284 | 285 | go func() { 286 | s.Server.Serve(l) 287 | }() 288 | 289 | return nil 290 | } 291 | 292 | // ListenAndServe overrides http.Server's method. 293 | // 294 | // Unlike the original, this method returns immediately just after 295 | // starting a goroutine to accept connections. To stop listening, 296 | // call the environment's Cancel. 297 | // 298 | // ListenAndServe returns non-nil error if and only if net.Listen failed. 299 | func (s *HTTPServer) ListenAndServe() error { 300 | addr := s.Server.Addr 301 | if addr == "" { 302 | addr = ":http" 303 | } 304 | ln, err := net.Listen("tcp", addr) 305 | if err != nil { 306 | return err 307 | } 308 | return s.Serve(ln) 309 | } 310 | 311 | // ListenAndServeTLS overrides http.Server's method. 312 | // 313 | // Unlike the original, this method returns immediately just after 314 | // starting a goroutine to accept connections. To stop listening, 315 | // call the environment's Cancel. 316 | // 317 | // Another difference from the original is that certFile and keyFile 318 | // must be specified. If not, configure http.Server.TLSConfig 319 | // manually and use Serve(). 320 | // 321 | // HTTP/2 is always enabled. 322 | // 323 | // ListenAndServeTLS returns non-nil error if net.Listen failed 324 | // or failed to load certificate files. 325 | func (s *HTTPServer) ListenAndServeTLS(certFile, keyFile string) error { 326 | addr := s.Server.Addr 327 | if addr == "" { 328 | addr = ":https" 329 | } 330 | 331 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 332 | if err != nil { 333 | return err 334 | } 335 | 336 | config := &tls.Config{ 337 | NextProtos: []string{"h2", "http/1.1"}, 338 | Certificates: []tls.Certificate{cert}, 339 | PreferServerCipherSuites: true, 340 | ClientSessionCache: tls.NewLRUClientSessionCache(0), 341 | } 342 | s.Server.TLSConfig = config 343 | 344 | ln, err := net.Listen("tcp", addr) 345 | if err != nil { 346 | return err 347 | } 348 | 349 | tlsListener := tls.NewListener(ln, config) 350 | return s.Serve(tlsListener) 351 | } 352 | 353 | // HTTPClient is a thin wrapper for *http.Client. 354 | // 355 | // This overrides Do method to add the request tracking header if 356 | // the passed request's context brings a request tracking ID. Do 357 | // also records the request log to Logger. 358 | // 359 | // Do not use Get/Head/Post/PostForm. They panics. 360 | type HTTPClient struct { 361 | *http.Client 362 | 363 | // Severity is used to log successful requests. 364 | // 365 | // Zero suppresses logging. Valid values are one of 366 | // log.LvDebug, log.LvInfo, and so on. 367 | // 368 | // Errors are always logged with log.LvError. 369 | Severity int 370 | 371 | // Logger for HTTP request. If nil, the default logger is used. 372 | Logger *log.Logger 373 | } 374 | 375 | // Do overrides http.Client.Do. 376 | // 377 | // req's context should have been set by http.Request.WithContext 378 | // for request tracking and context-based cancelation. 379 | func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) { 380 | ctx := req.Context() 381 | v := ctx.Value(RequestIDContextKey) 382 | if v != nil { 383 | req.Header.Set(requestIDHeader, v.(string)) 384 | } 385 | st := time.Now() 386 | resp, err := c.Client.Do(req) 387 | 388 | logger := c.Logger 389 | if logger == nil { 390 | logger = log.DefaultLogger() 391 | } 392 | 393 | if err == nil && (c.Severity == 0 || !logger.Enabled(c.Severity)) { 394 | // successful logs are suppressed if c.Severity is 0 or 395 | // logger threshold is under c.Severity. 396 | return resp, err 397 | } 398 | 399 | fields := FieldsFromContext(ctx) 400 | fields[log.FnType] = "http" 401 | fields[log.FnResponseTime] = time.Since(st).Seconds() 402 | fields[log.FnHTTPMethod] = req.Method 403 | fields[log.FnURL] = req.URL.String() 404 | fields[log.FnStartAt] = st 405 | 406 | if err != nil { 407 | fields["error"] = err.Error() 408 | logger.Error("well: http", fields) 409 | return resp, err 410 | } 411 | 412 | fields[log.FnHTTPStatusCode] = resp.StatusCode 413 | logger.Log(c.Severity, "well: http", fields) 414 | return resp, err 415 | } 416 | 417 | // Get panics. 418 | func (c *HTTPClient) Get(url string) (*http.Response, error) { 419 | panic("Use Do") 420 | } 421 | 422 | // Head panics. 423 | func (c *HTTPClient) Head(url string) (*http.Response, error) { 424 | panic("Use Do") 425 | } 426 | 427 | // Post panics. 428 | func (c *HTTPClient) Post(url, bodyType string, body io.Reader) (*http.Response, error) { 429 | panic("Use Do") 430 | } 431 | 432 | // PostForm panics. 433 | func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) { 434 | panic("Use Do") 435 | } 436 | -------------------------------------------------------------------------------- /http.go: -------------------------------------------------------------------------------- 1 | //go:build go1.8 2 | // +build go1.8 3 | 4 | package well 5 | 6 | import ( 7 | "context" 8 | "crypto/tls" 9 | "io" 10 | "net" 11 | "net/http" 12 | "net/url" 13 | "os" 14 | "sync" 15 | "time" 16 | 17 | "github.com/cybozu-go/log" 18 | "github.com/cybozu-go/netutil" 19 | ) 20 | 21 | const ( 22 | defaultHTTPReadTimeout = 30 * time.Second 23 | 24 | // request tracking header. 25 | defaultRequestIDHeader = "X-Cybozu-Request-ID" 26 | 27 | requestIDHeaderEnv = "REQUEST_ID_HEADER" 28 | ) 29 | 30 | var ( 31 | requestIDHeader = defaultRequestIDHeader 32 | ) 33 | 34 | func init() { 35 | hn := os.Getenv(requestIDHeaderEnv) 36 | if len(hn) > 0 { 37 | requestIDHeader = hn 38 | } 39 | } 40 | 41 | // RequestIDHeader provides the name of the request tracking header 42 | func RequestIDHeader() string { 43 | return requestIDHeader 44 | } 45 | 46 | // HTTPServer is a wrapper for http.Server. 47 | // 48 | // This struct overrides Serve and ListenAndServe* methods. 49 | // 50 | // http.Server members are replaced as following: 51 | // - Handler is replaced with a wrapper handler that logs requests. 52 | // - ReadTimeout is set to 30 seconds if it is zero. 53 | // - ConnState is replaced with the one provided by the framework. 54 | type HTTPServer struct { 55 | *http.Server 56 | 57 | // AccessLog is a logger for access logs. 58 | // If this is nil, the default logger is used. 59 | AccessLog *log.Logger 60 | 61 | // ShutdownTimeout is the maximum duration the server waits for 62 | // all connections to be closed before shutdown. 63 | // 64 | // Zero duration disables timeout. 65 | ShutdownTimeout time.Duration 66 | 67 | // Env is the environment where this server runs. 68 | // 69 | // The global environment is used if Env is nil. 70 | Env *Environment 71 | 72 | handler http.Handler 73 | shutdownErr error 74 | generator *IDGenerator 75 | 76 | initOnce sync.Once 77 | } 78 | 79 | // StdResponseWriter is the interface implemented by 80 | // the ResponseWriter from http.Server for non-HTTP/2 requests. 81 | // 82 | // HTTPServer's ResponseWriter implements this as well. 83 | type StdResponseWriter interface { 84 | http.ResponseWriter 85 | io.ReaderFrom 86 | http.Flusher 87 | http.Hijacker 88 | WriteString(data string) (int, error) 89 | } 90 | 91 | // StdResponseWriter2 is the interface implemented by 92 | // the ResponseWriter from http.Server for HTTP/2 requests. 93 | // 94 | // HTTPServer's ResponseWriter implements this as well. 95 | type StdResponseWriter2 interface { 96 | http.ResponseWriter 97 | http.Flusher 98 | http.Pusher 99 | WriteString(data string) (int, error) 100 | } 101 | 102 | type logWriter interface { 103 | Status() int 104 | Size() int64 105 | } 106 | 107 | type logResponseWriter struct { 108 | StdResponseWriter 109 | status int 110 | size int64 111 | } 112 | 113 | func (w *logResponseWriter) WriteHeader(status int) { 114 | w.status = status 115 | w.StdResponseWriter.WriteHeader(status) 116 | } 117 | 118 | func (w *logResponseWriter) Write(data []byte) (int, error) { 119 | n, err := w.StdResponseWriter.Write(data) 120 | w.size += int64(n) 121 | return n, err 122 | } 123 | 124 | func (w *logResponseWriter) ReadFrom(r io.Reader) (int64, error) { 125 | n, err := w.StdResponseWriter.ReadFrom(r) 126 | w.size += n 127 | return n, err 128 | } 129 | 130 | func (w *logResponseWriter) WriteString(data string) (int, error) { 131 | n, err := w.StdResponseWriter.WriteString(data) 132 | w.size += int64(n) 133 | return n, err 134 | } 135 | 136 | func (w *logResponseWriter) Status() int { 137 | return w.status 138 | } 139 | 140 | func (w *logResponseWriter) Size() int64 { 141 | return w.size 142 | } 143 | 144 | type logResponseWriter2 struct { 145 | StdResponseWriter2 146 | status int 147 | size int64 148 | } 149 | 150 | func (w *logResponseWriter2) WriteHeader(status int) { 151 | w.status = status 152 | w.StdResponseWriter2.WriteHeader(status) 153 | } 154 | 155 | func (w *logResponseWriter2) Write(data []byte) (int, error) { 156 | n, err := w.StdResponseWriter2.Write(data) 157 | w.size += int64(n) 158 | return n, err 159 | } 160 | 161 | func (w *logResponseWriter2) WriteString(data string) (int, error) { 162 | n, err := w.StdResponseWriter2.WriteString(data) 163 | w.size += int64(n) 164 | return n, err 165 | } 166 | 167 | func (w *logResponseWriter2) Status() int { 168 | return w.status 169 | } 170 | 171 | func (w *logResponseWriter2) Size() int64 { 172 | return w.size 173 | } 174 | 175 | func createLogWriter(w http.ResponseWriter) (http.ResponseWriter, logWriter) { 176 | if srw1, ok := w.(StdResponseWriter); ok { 177 | t := &logResponseWriter{srw1, http.StatusOK, 0} 178 | return t, t 179 | } 180 | 181 | if srw2, ok := w.(StdResponseWriter2); ok { 182 | t := &logResponseWriter2{srw2, http.StatusOK, 0} 183 | return t, t 184 | } 185 | 186 | panic("unexpected ResponseWriter implementation") 187 | } 188 | 189 | // ServeHTTP implements http.Handler interface. 190 | func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { 191 | startTime := time.Now() 192 | 193 | w, lw := createLogWriter(w) 194 | 195 | ctx, cancel := context.WithCancel(s.Env.ctx) 196 | defer cancel() 197 | 198 | reqid := r.Header.Get(requestIDHeader) 199 | if len(reqid) == 0 { 200 | reqid = s.generator.Generate() 201 | } 202 | ctx = WithRequestID(ctx, reqid) 203 | 204 | s.handler.ServeHTTP(w, r.WithContext(ctx)) 205 | status := lw.Status() 206 | 207 | fields := map[string]interface{}{ 208 | log.FnType: "access", 209 | log.FnResponseTime: time.Since(startTime).Seconds(), 210 | log.FnProtocol: r.Proto, 211 | log.FnHTTPStatusCode: status, 212 | log.FnHTTPMethod: r.Method, 213 | log.FnURL: r.RequestURI, 214 | log.FnHTTPHost: r.Host, 215 | log.FnRequestSize: r.ContentLength, 216 | log.FnResponseSize: lw.Size(), 217 | } 218 | ip, _, err := net.SplitHostPort(r.RemoteAddr) 219 | if err == nil { 220 | fields[log.FnRemoteAddress] = ip 221 | } 222 | ua := r.Header.Get("User-Agent") 223 | if len(ua) > 0 { 224 | fields[log.FnHTTPUserAgent] = ua 225 | } 226 | if len(reqid) > 0 { 227 | fields[log.FnRequestID] = reqid 228 | } 229 | 230 | lv := log.LvInfo 231 | switch { 232 | case 500 <= status: 233 | lv = log.LvError 234 | case 400 <= status: 235 | lv = log.LvWarn 236 | } 237 | s.AccessLog.Log(lv, "well: access", fields) 238 | } 239 | 240 | func (s *HTTPServer) init() { 241 | s.generator = NewIDGenerator() 242 | 243 | if s.Server.Handler == nil { 244 | panic("Handler must not be nil") 245 | } 246 | s.handler = s.Server.Handler 247 | s.Server.Handler = s 248 | if s.Server.ReadTimeout == 0 { 249 | s.Server.ReadTimeout = defaultHTTPReadTimeout 250 | } 251 | 252 | if s.AccessLog == nil { 253 | s.AccessLog = log.DefaultLogger() 254 | } 255 | 256 | if s.Env == nil { 257 | s.Env = defaultEnv 258 | } 259 | 260 | s.Env.Go(s.wait) 261 | } 262 | 263 | func (s *HTTPServer) wait(ctx context.Context) error { 264 | <-ctx.Done() 265 | 266 | s.Server.SetKeepAlivesEnabled(false) 267 | 268 | ctx = context.Background() 269 | if s.ShutdownTimeout != 0 { 270 | ctx2, cancel := context.WithTimeout(ctx, s.ShutdownTimeout) 271 | defer cancel() 272 | ctx = ctx2 273 | } 274 | 275 | err := s.Server.Shutdown(ctx) 276 | if err != nil { 277 | log.Warn("well: unclean shutdown", map[string]interface{}{ 278 | log.FnError: err, 279 | }) 280 | s.shutdownErr = err 281 | } 282 | return err 283 | } 284 | 285 | // TimedOut returns true if the server shut down before all connections 286 | // got closed. 287 | func (s *HTTPServer) TimedOut() bool { 288 | return s.shutdownErr == context.DeadlineExceeded 289 | } 290 | 291 | // Serve overrides http.Server's Serve method. 292 | // 293 | // Unlike the original, this method returns immediately just after 294 | // starting a goroutine to accept connections. 295 | // 296 | // The framework automatically closes l when the environment's Cancel 297 | // is called. 298 | // 299 | // Serve always returns nil. 300 | func (s *HTTPServer) Serve(l net.Listener) error { 301 | s.initOnce.Do(s.init) 302 | 303 | l = netutil.KeepAliveListener(l) 304 | 305 | go func() { 306 | s.Server.Serve(l) 307 | }() 308 | 309 | return nil 310 | } 311 | 312 | // ListenAndServe overrides http.Server's method. 313 | // 314 | // Unlike the original, this method returns immediately just after 315 | // starting a goroutine to accept connections. To stop listening, 316 | // call the environment's Cancel. 317 | // 318 | // ListenAndServe returns non-nil error if and only if net.Listen failed. 319 | func (s *HTTPServer) ListenAndServe() error { 320 | addr := s.Server.Addr 321 | if addr == "" { 322 | addr = ":http" 323 | } 324 | ln, err := net.Listen("tcp", addr) 325 | if err != nil { 326 | return err 327 | } 328 | return s.Serve(ln) 329 | } 330 | 331 | // ListenAndServeTLS overrides http.Server's method. 332 | // 333 | // Unlike the original, this method returns immediately just after 334 | // starting a goroutine to accept connections. To stop listening, 335 | // call the environment's Cancel. 336 | // 337 | // Another difference from the original is that certFile and keyFile 338 | // must be specified. If not, configure http.Server.TLSConfig 339 | // manually and use Serve(). 340 | // 341 | // HTTP/2 is always enabled. 342 | // 343 | // ListenAndServeTLS returns non-nil error if net.Listen failed 344 | // or failed to load certificate files. 345 | func (s *HTTPServer) ListenAndServeTLS(certFile, keyFile string) error { 346 | addr := s.Server.Addr 347 | if addr == "" { 348 | addr = ":https" 349 | } 350 | 351 | cert, err := tls.LoadX509KeyPair(certFile, keyFile) 352 | if err != nil { 353 | return err 354 | } 355 | 356 | config := &tls.Config{ 357 | NextProtos: []string{"h2", "http/1.1"}, 358 | Certificates: []tls.Certificate{cert}, 359 | PreferServerCipherSuites: true, 360 | ClientSessionCache: tls.NewLRUClientSessionCache(0), 361 | } 362 | s.Server.TLSConfig = config 363 | 364 | ln, err := net.Listen("tcp", addr) 365 | if err != nil { 366 | return err 367 | } 368 | 369 | tlsListener := tls.NewListener(ln, config) 370 | return s.Serve(tlsListener) 371 | } 372 | 373 | // HTTPClient is a thin wrapper for *http.Client. 374 | // 375 | // This overrides Do method to add the request tracking header if 376 | // the passed request's context brings a request tracking ID. Do 377 | // also records the request log to Logger. 378 | // 379 | // Do not use Get/Head/Post/PostForm. They panics. 380 | type HTTPClient struct { 381 | *http.Client 382 | 383 | // Severity is used to log successful requests. 384 | // 385 | // Zero suppresses logging. Valid values are one of 386 | // log.LvDebug, log.LvInfo, and so on. 387 | // 388 | // Errors are always logged with log.LvError. 389 | Severity int 390 | 391 | // Logger for HTTP request. If nil, the default logger is used. 392 | Logger *log.Logger 393 | } 394 | 395 | // Do overrides http.Client.Do. 396 | // 397 | // req's context should have been set by http.Request.WithContext 398 | // for request tracking and context-based cancelation. 399 | func (c *HTTPClient) Do(req *http.Request) (*http.Response, error) { 400 | ctx := req.Context() 401 | v := ctx.Value(RequestIDContextKey) 402 | if v != nil { 403 | req.Header.Set(requestIDHeader, v.(string)) 404 | } 405 | st := time.Now() 406 | resp, err := c.Client.Do(req) 407 | 408 | logger := c.Logger 409 | if logger == nil { 410 | logger = log.DefaultLogger() 411 | } 412 | 413 | if err == nil && (c.Severity == 0 || !logger.Enabled(c.Severity)) { 414 | // successful logs are suppressed if c.Severity is 0 or 415 | // logger threshold is under c.Severity. 416 | return resp, err 417 | } 418 | 419 | fields := FieldsFromContext(ctx) 420 | fields[log.FnType] = "http" 421 | fields[log.FnResponseTime] = time.Since(st).Seconds() 422 | fields[log.FnHTTPMethod] = req.Method 423 | fields[log.FnURL] = req.URL.String() 424 | fields[log.FnStartAt] = st 425 | 426 | if err != nil { 427 | fields["error"] = err.Error() 428 | logger.Error("well: http", fields) 429 | return resp, err 430 | } 431 | 432 | fields[log.FnHTTPStatusCode] = resp.StatusCode 433 | logger.Log(c.Severity, "well: http", fields) 434 | return resp, err 435 | } 436 | 437 | // Get panics. 438 | func (c *HTTPClient) Get(url string) (*http.Response, error) { 439 | panic("Use Do") 440 | } 441 | 442 | // Head panics. 443 | func (c *HTTPClient) Head(url string) (*http.Response, error) { 444 | panic("Use Do") 445 | } 446 | 447 | // Post panics. 448 | func (c *HTTPClient) Post(url, bodyType string, body io.Reader) (*http.Response, error) { 449 | panic("Use Do") 450 | } 451 | 452 | // PostForm panics. 453 | func (c *HTTPClient) PostForm(url string, data url.Values) (*http.Response, error) { 454 | panic("Use Do") 455 | } 456 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 7 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 8 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 9 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 10 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 11 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 12 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 13 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 14 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 15 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 16 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 17 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 18 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 19 | cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= 20 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 21 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 22 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 23 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 24 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 25 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 26 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 27 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 28 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 29 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 30 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 31 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 32 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 33 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 34 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 35 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 36 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 37 | cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 40 | github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= 41 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 42 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 43 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 44 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 45 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 46 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 47 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 48 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 49 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 50 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 51 | github.com/cybozu-go/log v1.7.0 h1:wPTkNDWcnSLLAv1ejFSn07qvYG8ng6U6Gygv04dYW1w= 52 | github.com/cybozu-go/log v1.7.0/go.mod h1:pwWH0DFLY85XgTEI6nqkDAvmGReEBDu2vmlkU7CpudQ= 53 | github.com/cybozu-go/netutil v1.4.4 h1:vGOAFdFx9ny0QCJ4IbSq6OIk+o5jEyErhFLz++VZ3zE= 54 | github.com/cybozu-go/netutil v1.4.4/go.mod h1:RsEP+fqLRSb5MHi6t2mzbqt9YDuzmgf3QOhZ5TqgJFE= 55 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 56 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 57 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 58 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 59 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 60 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 61 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 62 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 63 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 64 | github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= 65 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 66 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 67 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 68 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 69 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 70 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 71 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 72 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 73 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 74 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 75 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 76 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 77 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 78 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 79 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 80 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 81 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 82 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 83 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 84 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 85 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 86 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 87 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 88 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 89 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 90 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 91 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 92 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 93 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 94 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 95 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 96 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 97 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 98 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 99 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 100 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 101 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 102 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 103 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 104 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 105 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 106 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 107 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 108 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 109 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 110 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 111 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 112 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 113 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 114 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 115 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 116 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 117 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 118 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 119 | github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 120 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 121 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 122 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 123 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 124 | github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= 125 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 126 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 127 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 128 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 129 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 130 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 131 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 132 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 133 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 134 | github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 135 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 136 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 137 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 138 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 139 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 140 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 141 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 142 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 143 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 144 | github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= 145 | github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= 146 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 147 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 148 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 149 | github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= 150 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 151 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 152 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 153 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 154 | github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= 155 | github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= 156 | github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= 157 | github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= 158 | github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= 159 | github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= 160 | github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 161 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 162 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 163 | github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= 164 | github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= 165 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 166 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 167 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 168 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 169 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 170 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 171 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 172 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 173 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 174 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 175 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 176 | github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= 177 | github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= 178 | github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs= 179 | github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= 180 | github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= 181 | github.com/vishvananda/netns v0.0.3 h1:WxY6MpgIdDMQX50UJ7bPIRJdBCOeUV6XtW8dZZja988= 182 | github.com/vishvananda/netns v0.0.3/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= 183 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 184 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 185 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 186 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 187 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 188 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 189 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 190 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 191 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 192 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 193 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 194 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 195 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 196 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 197 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 198 | golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= 199 | golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 200 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 201 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 202 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 203 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 204 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 205 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 206 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 207 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 208 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 209 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 210 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 211 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 212 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 213 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 214 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 215 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 216 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 217 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 218 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 219 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 220 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 221 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 222 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 223 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 224 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 225 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 226 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 227 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 228 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 229 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 230 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 231 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 232 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 233 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 234 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 235 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 236 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 237 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 238 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 239 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 240 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 241 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 242 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 243 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 244 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 245 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 246 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 247 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 248 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 249 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 250 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 251 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 252 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 253 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 254 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 255 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 256 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 257 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 258 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 259 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 260 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 261 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 262 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 263 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 264 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 265 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 266 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 267 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 268 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 269 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 270 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 271 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 272 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 273 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 274 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 275 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 276 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 277 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 278 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 279 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 280 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 281 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 282 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 283 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 284 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 285 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 286 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 287 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 288 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 289 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 290 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 291 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 292 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 293 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 294 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 295 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 296 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 297 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 298 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 299 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 300 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 301 | golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 302 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 303 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 304 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 305 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 306 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 307 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 308 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 309 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 310 | golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 311 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 312 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 313 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 314 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 315 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 316 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 317 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 318 | golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 319 | golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 320 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 321 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 322 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= 323 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 324 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 325 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 326 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 327 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 328 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 329 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 330 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 331 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= 332 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 333 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 334 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 335 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 336 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 337 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 338 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 339 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 340 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 341 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 342 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 343 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 344 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 345 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 346 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 347 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 348 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 349 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 350 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 351 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 352 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 353 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 354 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 355 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 356 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 357 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 358 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 359 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 360 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 361 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 362 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 363 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 364 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 365 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 366 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 367 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 368 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 369 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 370 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 371 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 372 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 373 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 374 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 375 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 376 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 377 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 378 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 379 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 380 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 381 | golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 382 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 383 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 384 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 385 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 386 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 387 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 388 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 389 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 390 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 391 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 392 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 393 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 394 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 395 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 396 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 397 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 398 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 399 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 400 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 401 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 402 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 403 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 404 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 405 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 406 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 407 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 408 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 409 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 410 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 411 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 412 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 413 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 414 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 415 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 416 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 417 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 418 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 419 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 420 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 421 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 422 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 423 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 424 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 425 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 426 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 427 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 428 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 429 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 430 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 431 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 432 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 433 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 434 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 435 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 436 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 437 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 438 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 439 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 440 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 441 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 442 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 443 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 444 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 445 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 446 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 447 | google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 448 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 449 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 450 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 451 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 452 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 453 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 454 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 455 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 456 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 457 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 458 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 459 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 460 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 461 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 462 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 463 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 464 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 465 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 466 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 467 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 468 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 469 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 470 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 471 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 472 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 473 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 474 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 475 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 476 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 477 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 478 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 479 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 480 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 481 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 482 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 483 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 484 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 485 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 486 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 487 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 488 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 489 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 490 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 491 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 492 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 493 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 494 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 495 | --------------------------------------------------------------------------------