github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/instrument/build.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 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 instrument 22 23 import ( 24 "encoding/json" 25 "errors" 26 "log" 27 "os" 28 "runtime" 29 "strconv" 30 "sync" 31 "time" 32 ) 33 34 var ( 35 // Revision is the VCS revision associated with this build. Overridden using ldflags 36 // at compile time. Example: 37 // $ go build -ldflags "-X github.com/m3db/m3/src/x/instrument.Revision=abcdef" ... 38 // Adapted from: https://www.atatus.com/blog/golang-auto-build-versioning/ 39 Revision = "unknown" 40 41 // Branch is the VCS branch associated with this build. 42 Branch = "unknown" 43 44 // Version is the version associated with this build. 45 Version = "unknown" 46 47 // BuildDate is the date this build was created. 48 BuildDate = "unknown" 49 50 // BuildTimeUnix is the seconds since epoch representing the date this build was created. 51 BuildTimeUnix = "0" 52 53 // LogBuildInfoAtStartup controls whether we log build information at startup. If its 54 // set to a non-empty string, we log the build information at process startup. 55 LogBuildInfoAtStartup string 56 57 // LogBuildInfoToStdout controls whether we log build information to stdout or stderr. 58 // If it is set to a non-empty string then the build info will be logged to stdout, 59 // otherwise it will be logged to stderr (assuming LogBuildInfoAtStartup is also 60 // non-empty). 61 LogBuildInfoToStdout string 62 63 // goVersion is the current runtime version. 64 goVersion = runtime.Version() 65 66 // buildInfoMetricName is the emitted build information metric's name. 67 buildInfoMetricName = "build-information" 68 69 // buildAgeMetricName is the emitted build age metric's name. 70 buildAgeMetricName = "build-age" 71 ) 72 73 var ( 74 errAlreadyStarted = errors.New("reporter already started") 75 errNotStarted = errors.New("reporter not started") 76 errBuildTimeNegative = errors.New("reporter build time must be non-negative") 77 ) 78 79 // LogBuildInfo logs the build information using the default logger. 80 func LogBuildInfo() { 81 LogBuildInfoWithLogger(log.Default()) 82 } 83 84 // LogBuildInfoJSON logs the build information in JSON using the default logger. 85 func LogBuildInfoJSON() { 86 err := LogBuildInfoWithLoggerJSON(log.Default(), json.Marshal) 87 if err != nil { 88 log.Default().Fatalf("Error converting build info to JSON %s", err.Error()) 89 } 90 } 91 92 // LogBuildInfoWithLogger logs the build information using the provided logger 93 func LogBuildInfoWithLogger(logger *log.Logger) { 94 logger.Printf("Go Runtime version: %s\n", goVersion) 95 logger.Printf("Build Version: %s\n", Version) 96 logger.Printf("Build Revision: %s\n", Revision) 97 logger.Printf("Build Branch: %s\n", Branch) 98 logger.Printf("Build Date: %s\n", BuildDate) 99 logger.Printf("Build TimeUnix: %s\n", BuildTimeUnix) 100 } 101 102 // LogBuildInfoWithLoggerJSON logs the build information using the provided logger in JSON 103 func LogBuildInfoWithLoggerJSON(logger *log.Logger, jsonMarshalFunc func(interface{}) ([]byte, error)) error { 104 buildMap := make(map[string]string) 105 buildMap["go_runtime_version"] = goVersion 106 buildMap["build_version"] = Version 107 buildMap["build_revision"] = Revision 108 buildMap["build_branch"] = Branch 109 buildMap["build_date"] = BuildDate 110 buildMap["build_time_unix"] = BuildTimeUnix 111 112 jsonOut, err := jsonMarshalFunc(buildMap) 113 if err != nil { 114 return err 115 } 116 // If we're logging in pure JSON, remove the timestamp flag since that's in plaintext. Set it back after 117 // emitting the Build Info. 118 log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) 119 logger.Printf(string(jsonOut)) 120 log.SetFlags(log.LstdFlags) 121 return nil 122 } 123 124 func init() { 125 if LogBuildInfoAtStartup != "" { 126 logger := log.Default() 127 if LogBuildInfoToStdout != "" { 128 logger = log.New(os.Stdout, "", log.LstdFlags) 129 } 130 LogBuildInfoWithLogger(logger) 131 } 132 } 133 134 type buildReporter struct { 135 sync.Mutex 136 137 opts Options 138 buildTime time.Time 139 active bool 140 closeCh chan struct{} 141 doneCh chan struct{} 142 } 143 144 // NewBuildReporter returns a new build version reporter. 145 func NewBuildReporter( 146 opts Options, 147 ) Reporter { 148 return &buildReporter{ 149 opts: opts, 150 } 151 } 152 153 func (b *buildReporter) Start() error { 154 const ( 155 base = 10 156 bitSize = 64 157 ) 158 sec, err := strconv.ParseInt(BuildTimeUnix, base, bitSize) 159 if err != nil { 160 return err 161 } 162 if sec < 0 { 163 return errBuildTimeNegative 164 } 165 buildTime := time.Unix(sec, 0) 166 167 b.Lock() 168 defer b.Unlock() 169 if b.active { 170 return errAlreadyStarted 171 } 172 b.buildTime = buildTime 173 b.active = true 174 b.closeCh = make(chan struct{}) 175 b.doneCh = make(chan struct{}) 176 go b.report() 177 return nil 178 } 179 180 func (b *buildReporter) report() { 181 tags := map[string]string{ 182 "revision": Revision, 183 "branch": Branch, 184 "build-date": BuildDate, 185 "build-version": Version, 186 "go-version": goVersion, 187 } 188 189 for k, v := range b.opts.CustomBuildTags() { 190 tags[k] = v 191 } 192 193 scope := b.opts.MetricsScope().Tagged(tags) 194 buildInfoGauge := scope.Gauge(buildInfoMetricName) 195 buildAgeGauge := scope.Gauge(buildAgeMetricName) 196 buildInfoGauge.Update(1.0) 197 buildAgeGauge.Update(float64(time.Since(b.buildTime))) 198 199 ticker := time.NewTicker(b.opts.ReportInterval()) 200 defer func() { 201 close(b.doneCh) 202 ticker.Stop() 203 }() 204 205 for { 206 select { 207 case <-ticker.C: 208 buildInfoGauge.Update(1.0) 209 buildAgeGauge.Update(float64(time.Since(b.buildTime))) 210 211 case <-b.closeCh: 212 return 213 } 214 } 215 } 216 217 func (b *buildReporter) Stop() error { 218 b.Lock() 219 defer b.Unlock() 220 if !b.active { 221 return errNotStarted 222 } 223 close(b.closeCh) 224 <-b.doneCh 225 b.active = false 226 return nil 227 }