github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/x/instrument/invariant.go (about)

     1  // Copyright (c) 2018 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  	"fmt"
    25  	"os"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"go.uber.org/zap"
    30  )
    31  
    32  const (
    33  	// InvariantViolatedMetricName is the name of the metric emitted upon
    34  	// invocation of `EmitInvariantViolation`.
    35  	InvariantViolatedMetricName = "invariant_violated"
    36  
    37  	// InvariantViolatedLogFieldName is the name of the log field to be
    38  	// used when generating errors/log statements pertaining to the violation
    39  	// of an invariant.
    40  	InvariantViolatedLogFieldName = "violation"
    41  
    42  	// InvariantViolatedLogFieldValue is the value of the log field to be
    43  	// used when generating errors/log statements pertaining to the violation
    44  	// of an invariant.
    45  	InvariantViolatedLogFieldValue = InvariantViolatedMetricName
    46  
    47  	// ShouldPanicEnvironmentVariableName is the name of the environment variable
    48  	// that must be set to "true" in order for the invariant violated functions
    49  	// to panic after logging / emitting metrics. Should only be set in test
    50  	// environments.
    51  	ShouldPanicEnvironmentVariableName = "PANIC_ON_INVARIANT_VIOLATED"
    52  )
    53  
    54  // EmitInvariantViolation emits a metric to indicate a system invariant has
    55  // been violated. Users of this method are expected to monitor/alert off this
    56  // metric to ensure they're notified when such an event occurs. Further, they
    57  // should log further information to aid diagnostics of the system invariant
    58  // violated at the callsite of the violation. Optionally panics if the
    59  // ShouldPanicEnvironmentVariableName is set to "true".
    60  func EmitInvariantViolation(opts Options) {
    61  	// NB(prateek): there's no need to cache this metric. It should be never
    62  	// be called in production systems unless something is seriously messed
    63  	// up. At which point, the extra map alloc should be of no concern.
    64  	opts.MetricsScope().Counter(InvariantViolatedMetricName).Inc(1)
    65  
    66  	panicIfEnvSet()
    67  }
    68  
    69  // EmitAndLogInvariantViolation calls EmitInvariantViolation and then calls the provided function
    70  // with a supplied logger that is pre-configured with an invariant violated field. Optionally panics
    71  // if the ShouldPanicEnvironmentVariableName is set to "true".
    72  func EmitAndLogInvariantViolation(opts Options, f func(l *zap.Logger)) {
    73  	logger := opts.Logger().With(
    74  		zap.String(InvariantViolatedLogFieldName, InvariantViolatedLogFieldValue))
    75  	f(logger)
    76  
    77  	EmitInvariantViolation(opts)
    78  }
    79  
    80  // InvariantErrorf constructs a new error, prefixed with a string indicating that an invariant
    81  // violation occurred. Optionally panics if the ShouldPanicEnvironmentVariableName is set to "true".
    82  func InvariantErrorf(format string, a ...interface{}) error {
    83  	var (
    84  		invariantFormat = InvariantViolatedMetricName + ": " + format
    85  		err             = fmt.Errorf(invariantFormat, a...)
    86  	)
    87  
    88  	panicIfEnvSetWithMessage(err.Error())
    89  	return err
    90  }
    91  
    92  // SetShouldPanicEnvironmentVariable sets the env variable and returns a func to reset to the previous value.
    93  // Useful for tests to use a defer statement when they need to test a specific value.
    94  func SetShouldPanicEnvironmentVariable(value bool) func() {
    95  	restoreValue := os.Getenv(ShouldPanicEnvironmentVariableName)
    96  	_ = os.Setenv(ShouldPanicEnvironmentVariableName, strconv.FormatBool(value))
    97  	return func() {
    98  		_ = os.Setenv(ShouldPanicEnvironmentVariableName, restoreValue)
    99  	}
   100  }
   101  
   102  func panicIfEnvSet() {
   103  	panicIfEnvSetWithMessage("")
   104  }
   105  
   106  func panicIfEnvSetWithMessage(s string) {
   107  	envIsSet := strings.ToLower(os.Getenv(ShouldPanicEnvironmentVariableName)) == "true"
   108  
   109  	if envIsSet {
   110  		if s == "" {
   111  			s = "invariant violation detected"
   112  		}
   113  
   114  		panic(s)
   115  	}
   116  }