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  }