github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/test/framework/metrics/ginkgo_metrics.go (about)

     1  // Copyright (c) 2021, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package metrics
     5  
     6  import (
     7  	"fmt"
     8  	"github.com/onsi/ginkgo/v2"
     9  	"github.com/onsi/ginkgo/v2/types"
    10  	"go.uber.org/zap"
    11  	"go.uber.org/zap/zapcore"
    12  	"k8s.io/apimachinery/pkg/util/uuid"
    13  	neturl "net/url"
    14  	"os"
    15  	"strings"
    16  )
    17  
    18  const (
    19  	Duration          = "duration"
    20  	Status            = "status"
    21  	attempts          = "attempts"
    22  	test              = "test"
    23  	codeLocation      = "code_location"
    24  	stageName         = "stage_name"
    25  	fileName          = "file_name"
    26  	BuildURL          = "build_url"
    27  	JenkinsJob        = "jenkins_job"
    28  	BranchName        = "branch_name"
    29  	CommitHash        = "commit_hash"
    30  	KubernetesVersion = "kubernetes_version"
    31  	TestEnv           = "test_env"
    32  	Label             = "label"
    33  
    34  	MetricsIndex     = "metrics"
    35  	TestLogIndex     = "testlogs"
    36  	searchWriterKey  = "searchWriter"
    37  	timeFormatString = "2006.01.02"
    38  	searchURL        = "SEARCH_HTTP_ENDPOINT"
    39  	searchPW         = "SEARCH_PASSWORD"
    40  	searchUser       = "SEARCH_USERNAME"
    41  	stageNameEnv     = "STAGE_NAME"
    42  	airGapTestEnv    = "AIR_GAPPED"
    43  )
    44  
    45  var logger = internalLogger()
    46  
    47  func internalLogger() *zap.SugaredLogger {
    48  	cfg := zap.Config{
    49  		Encoding: "json",
    50  		Level:    zap.NewAtomicLevelAt(zapcore.InfoLevel),
    51  		EncoderConfig: zapcore.EncoderConfig{
    52  			MessageKey:   "msg",
    53  			EncodeTime:   zapcore.EpochMillisTimeEncoder,
    54  			CallerKey:    "caller",
    55  			EncodeCaller: zapcore.ShortCallerEncoder,
    56  		},
    57  		OutputPaths: []string{"stdout"},
    58  	}
    59  
    60  	log, err := cfg.Build()
    61  	if err != nil {
    62  		panic("failed to create internal logger")
    63  	}
    64  	return log.Sugar()
    65  }
    66  
    67  // NewLogger generates a new logger, and tees ginkgo output to the search db
    68  func NewLogger(pkg string, ind string, level string, paths ...string) (*zap.SugaredLogger, error) {
    69  	var messageKey = zapcore.OmitKey
    70  	if ind == TestLogIndex {
    71  		messageKey = "msg"
    72  	}
    73  	logLevel, err := zap.ParseAtomicLevel(level)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	cfg := zap.Config{
    78  		Encoding: "json",
    79  		Level:    logLevel,
    80  		EncoderConfig: zapcore.EncoderConfig{
    81  
    82  			MessageKey: messageKey,
    83  
    84  			LevelKey: zapcore.OmitKey,
    85  
    86  			TimeKey:    "timestamp",
    87  			EncodeTime: zapcore.EpochMillisTimeEncoder,
    88  
    89  			CallerKey:    "caller",
    90  			EncodeCaller: zapcore.ShortCallerEncoder,
    91  		},
    92  	}
    93  
    94  	outputPaths, err := configureOutputs(ind)
    95  	if err != nil {
    96  		logger.Errorf("failed to configure outputs: %v", err)
    97  		return nil, err
    98  	}
    99  	cfg.OutputPaths = append(outputPaths, paths...)
   100  	log, err := cfg.Build()
   101  	if err != nil {
   102  		logger.Errorf("error creating %s logger %v", pkg, err)
   103  		return nil, err
   104  	}
   105  
   106  	suiteUUID := uuid.NewUUID()
   107  	sugaredLogger := log.Sugar().With("suite_uuid", suiteUUID).With("package", pkg)
   108  	return configureLoggerWithJenkinsEnv(sugaredLogger), nil
   109  }
   110  
   111  func configureLoggerWithJenkinsEnv(log *zap.SugaredLogger) *zap.SugaredLogger {
   112  	const separator = "/"
   113  	log, _ = withEnvVar(log, KubernetesVersion, "K8S_VERSION_LABEL")
   114  	log, branchName := withEnvVar(log, BranchName, "BRANCH_NAME")
   115  	log, _ = withEnvVarMutate(log, BuildURL, "BUILD_URL", func(buildURL string) string {
   116  		buildURL, _ = neturl.QueryUnescape(buildURL)
   117  		return buildURL
   118  	})
   119  	log, _ = withEnvVarMutate(log, JenkinsJob, "JOB_NAME", func(jobName string) string {
   120  		jobName, _ = neturl.QueryUnescape(jobName)
   121  		jobNameSplit := strings.Split(jobName, separator)
   122  		return jobNameSplit[0]
   123  	})
   124  	log, _ = withEnvVarMutate(log, CommitHash, "GIT_COMMIT", func(gitCommit string) string {
   125  		return branchName + separator + gitCommit
   126  	})
   127  	log, _ = withEnvVar(log, TestEnv, "TEST_ENV")
   128  	return log
   129  }
   130  
   131  // withEnvVarMutate enriches the logger with the mutated value of envVar, if it exists
   132  func withEnvVarMutate(log *zap.SugaredLogger, withKey, envVar string, mutateFunc func(string) string) (*zap.SugaredLogger, string) {
   133  	val := os.Getenv(envVar)
   134  	if len(val) > 0 {
   135  		val = mutateFunc(val)
   136  		log = log.With(withKey, val)
   137  	}
   138  	return log, val
   139  }
   140  
   141  // withEnvVar enriches the logger with the value of envVar, if it exists in the environment
   142  func withEnvVar(log *zap.SugaredLogger, withKey, envVar string) (*zap.SugaredLogger, string) {
   143  	return withEnvVarMutate(log, withKey, envVar, func(val string) string {
   144  		return val
   145  	})
   146  }
   147  
   148  // configureOutputs configures the search output path if it is available
   149  func configureOutputs(ind string) ([]string, error) {
   150  	var outputs []string
   151  
   152  	// Do not register outputs for an Air Gapped installation
   153  	if os.Getenv(airGapTestEnv) == "true" {
   154  		return outputs, nil
   155  	}
   156  
   157  	searchWriter, err := SearchWriterFromEnv(ind)
   158  	sinkKey := fmt.Sprintf("%s%s", searchWriterKey, ind)
   159  	// Register SearchWriter
   160  	if err == nil {
   161  		if err := zap.RegisterSink(sinkKey, func(u *neturl.URL) (zap.Sink, error) {
   162  			return searchWriter, nil
   163  		}); err != nil {
   164  			return nil, err
   165  		}
   166  		outputs = append(outputs, sinkKey+":search")
   167  	}
   168  
   169  	return outputs, nil
   170  }
   171  
   172  func EmitFail(log *zap.SugaredLogger) {
   173  	spec := ginkgo.CurrentSpecReport()
   174  	log = log.With(Status, types.SpecStateFailed)
   175  	emitInternal(log, spec)
   176  }
   177  
   178  func Emit(log *zap.SugaredLogger) {
   179  	spec := ginkgo.CurrentSpecReport()
   180  	// EmitFail should be only called by the fail handler to emit test failed metrics
   181  	if spec.State != types.SpecStateFailed {
   182  		log = log.With(Status, spec.State)
   183  	}
   184  	emitInternal(log, spec)
   185  }
   186  
   187  func emitInternal(log *zap.SugaredLogger, spec ginkgo.SpecReport) {
   188  	t := spec.FullText()
   189  	l := spec.Labels()
   190  	log = withCodeLocation(log, spec)
   191  	log, _ = withEnvVar(log, stageName, stageNameEnv)
   192  	log.With(attempts, spec.NumAttempts,
   193  		test, t,
   194  		Label, l,
   195  		fileName, spec.FileName()).
   196  		Info()
   197  }
   198  
   199  func withCodeLocation(log *zap.SugaredLogger, spec ginkgo.SpecReport) *zap.SugaredLogger {
   200  	filePath := spec.LeafNodeLocation.FileName
   201  	if len(filePath) < 1 {
   202  		return log
   203  	}
   204  	lineNumber := spec.LeafNodeLocation.LineNumber
   205  	split := strings.Split(filePath, "/")
   206  	fileName := split[len(split)-1]
   207  	return log.With(codeLocation, fmt.Sprintf("%s:%d", fileName, lineNumber))
   208  }
   209  
   210  func DurationMillis() int64 {
   211  	// this value is in nanoseconds, so we need to divide by one million
   212  	// to convert to milliseconds
   213  	spec := ginkgo.CurrentSpecReport()
   214  	return int64(spec.RunTime) / 1_000_000
   215  }