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 }