github.com/google/cloudprober@v0.11.3/surfacers/surfacers.go (about) 1 // Copyright 2017-2021 The Cloudprober Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /* 16 Package surfacers is the base package for creating Surfacer objects that are 17 used for writing metics data to different monitoring services. 18 19 Any Surfacer that is created for writing metrics data to a monitor system 20 should implement the below Surfacer interface and should accept 21 metrics.EventMetrics object through a Write() call. Each new surfacer should 22 also plug itself in through the New() method defined here. 23 */ 24 package surfacers 25 26 import ( 27 "context" 28 "fmt" 29 "html/template" 30 "strings" 31 "sync" 32 33 "github.com/google/cloudprober/logger" 34 "github.com/google/cloudprober/metrics" 35 "github.com/google/cloudprober/surfacers/cloudwatch" 36 "github.com/google/cloudprober/surfacers/common/options" 37 "github.com/google/cloudprober/surfacers/common/transform" 38 "github.com/google/cloudprober/surfacers/datadog" 39 "github.com/google/cloudprober/surfacers/file" 40 "github.com/google/cloudprober/surfacers/postgres" 41 "github.com/google/cloudprober/surfacers/prometheus" 42 "github.com/google/cloudprober/surfacers/pubsub" 43 "github.com/google/cloudprober/surfacers/stackdriver" 44 "github.com/google/cloudprober/web/formatutils" 45 46 surfacerpb "github.com/google/cloudprober/surfacers/proto" 47 surfacerspb "github.com/google/cloudprober/surfacers/proto" 48 ) 49 50 var ( 51 userDefinedSurfacers = make(map[string]Surfacer) 52 userDefinedSurfacersMu sync.Mutex 53 ) 54 55 // StatusTmpl variable stores the HTML template suitable to generate the 56 // surfacers' status for cloudprober's /status page. It expects an array of 57 // SurfacerInfo objects as input. 58 var StatusTmpl = template.Must(template.New("statusTmpl").Parse(` 59 <table class="status-list"> 60 <tr> 61 <th>Type</th> 62 <th>Name</th> 63 <th>Conf</th> 64 </tr> 65 {{ range . }} 66 <tr> 67 <td>{{.Type}}</td> 68 <td>{{.Name}}</td> 69 <td> 70 {{if .Conf}} 71 <pre>{{.Conf}}</pre> 72 {{else}} 73 default 74 {{end}} 75 </td> 76 </tr> 77 {{ end }} 78 </table> 79 `)) 80 81 // Default surfacers. These surfacers are enabled if no surfacer is defined. 82 var defaultSurfacers = []*surfacerpb.SurfacerDef{ 83 &surfacerpb.SurfacerDef{ 84 Type: surfacerpb.Type_PROMETHEUS.Enum(), 85 }, 86 &surfacerpb.SurfacerDef{ 87 Type: surfacerpb.Type_FILE.Enum(), 88 }, 89 } 90 91 // Surfacer is an interface for all metrics surfacing systems 92 type Surfacer interface { 93 // Function for writing a piece of metric data to a specified metric 94 // store (or other location). 95 Write(ctx context.Context, em *metrics.EventMetrics) 96 } 97 98 type surfacerWrapper struct { 99 Surfacer 100 opts *options.Options 101 lvCache map[string]*metrics.EventMetrics 102 } 103 104 func (sw *surfacerWrapper) Write(ctx context.Context, em *metrics.EventMetrics) { 105 if !sw.opts.AllowEventMetrics(em) { 106 return 107 } 108 109 if sw.opts.AddFailureMetric { 110 if err := transform.AddFailureMetric(em); err != nil { 111 sw.opts.Logger.Warning(err.Error()) 112 } 113 } 114 115 if sw.opts.Config.GetExportAsGauge() && em.Kind == metrics.CUMULATIVE { 116 newEM, err := transform.CumulativeToGauge(em, sw.lvCache, sw.opts.Logger) 117 if err != nil { 118 sw.opts.Logger.Errorf("Error converting CUMULATIVE metrics to GAUGE: %v", err) 119 return 120 } 121 em = newEM 122 } 123 124 sw.Surfacer.Write(ctx, em) 125 } 126 127 // SurfacerInfo encapsulates a Surfacer and related info. 128 type SurfacerInfo struct { 129 Surfacer 130 Type string 131 Name string 132 Conf string 133 } 134 135 func inferType(s *surfacerpb.SurfacerDef) surfacerspb.Type { 136 switch s.Surfacer.(type) { 137 case *surfacerpb.SurfacerDef_PrometheusSurfacer: 138 return surfacerspb.Type_PROMETHEUS 139 case *surfacerpb.SurfacerDef_StackdriverSurfacer: 140 return surfacerspb.Type_STACKDRIVER 141 case *surfacerpb.SurfacerDef_FileSurfacer: 142 return surfacerspb.Type_FILE 143 case *surfacerpb.SurfacerDef_PostgresSurfacer: 144 return surfacerspb.Type_POSTGRES 145 case *surfacerpb.SurfacerDef_PubsubSurfacer: 146 return surfacerspb.Type_PUBSUB 147 case *surfacerpb.SurfacerDef_CloudwatchSurfacer: 148 return surfacerspb.Type_CLOUDWATCH 149 case *surfacerpb.SurfacerDef_DatadogSurfacer: 150 return surfacerspb.Type_DATADOG 151 } 152 153 return surfacerspb.Type_NONE 154 } 155 156 // initSurfacer initializes and returns a new surfacer based on the config. 157 func initSurfacer(ctx context.Context, s *surfacerpb.SurfacerDef, sType surfacerspb.Type) (Surfacer, interface{}, error) { 158 // Create a new logger 159 logName := s.GetName() 160 if logName == "" { 161 logName = strings.ToLower(s.GetType().String()) 162 } 163 164 l, err := logger.NewCloudproberLog(logName) 165 if err != nil { 166 return nil, nil, fmt.Errorf("unable to create cloud logger: %v", err) 167 } 168 169 opts, err := options.BuildOptionsFromConfig(s, l) 170 if err != nil { 171 return nil, nil, err 172 } 173 174 var conf interface{} 175 var surfacer Surfacer 176 177 switch sType { 178 case surfacerpb.Type_PROMETHEUS: 179 surfacer, err = prometheus.New(ctx, s.GetPrometheusSurfacer(), opts, l) 180 conf = s.GetPrometheusSurfacer() 181 case surfacerpb.Type_STACKDRIVER: 182 surfacer, err = stackdriver.New(ctx, s.GetStackdriverSurfacer(), opts, l) 183 conf = s.GetStackdriverSurfacer() 184 case surfacerpb.Type_FILE: 185 surfacer, err = file.New(ctx, s.GetFileSurfacer(), opts, l) 186 conf = s.GetFileSurfacer() 187 case surfacerpb.Type_POSTGRES: 188 surfacer, err = postgres.New(ctx, s.GetPostgresSurfacer(), l) 189 conf = s.GetPostgresSurfacer() 190 case surfacerpb.Type_PUBSUB: 191 surfacer, err = pubsub.New(ctx, s.GetPubsubSurfacer(), opts, l) 192 conf = s.GetPubsubSurfacer() 193 case surfacerpb.Type_CLOUDWATCH: 194 surfacer, err = cloudwatch.New(ctx, s.GetCloudwatchSurfacer(), opts, l) 195 conf = s.GetCloudwatchSurfacer() 196 case surfacerpb.Type_DATADOG: 197 surfacer, err = datadog.New(ctx, s.GetDatadogSurfacer(), opts, l) 198 conf = s.GetDatadogSurfacer() 199 case surfacerpb.Type_USER_DEFINED: 200 userDefinedSurfacersMu.Lock() 201 defer userDefinedSurfacersMu.Unlock() 202 surfacer = userDefinedSurfacers[s.GetName()] 203 if surfacer == nil { 204 return nil, nil, fmt.Errorf("unregistered user defined surfacer: %s", s.GetName()) 205 } 206 default: 207 return nil, nil, fmt.Errorf("unknown surfacer type: %s", s.GetType()) 208 } 209 210 return &surfacerWrapper{ 211 Surfacer: surfacer, 212 opts: opts, 213 lvCache: make(map[string]*metrics.EventMetrics), 214 }, conf, err 215 } 216 217 // Init initializes the surfacers from the config protobufs and returns them as 218 // a list. 219 func Init(ctx context.Context, sDefs []*surfacerpb.SurfacerDef) ([]*SurfacerInfo, error) { 220 // If no surfacers are defined, return default surfacers. This behavior 221 // can be disabled by explicitly specifying "surfacer {}" in the config. 222 if len(sDefs) == 0 { 223 sDefs = defaultSurfacers 224 } 225 226 var result []*SurfacerInfo 227 for _, sDef := range sDefs { 228 sType := sDef.GetType() 229 230 if sType == surfacerpb.Type_NONE { 231 // Don't do anything if surfacer type is NONE and nothing is defined inside 232 // it: for example: "surfacer{}". This is one of the ways to disable 233 // surfacers as not adding surfacers at all results in default surfacers 234 // being added automatically. 235 if sDef.Surfacer == nil { 236 continue 237 } 238 sType = inferType(sDef) 239 } 240 241 s, conf, err := initSurfacer(ctx, sDef, sType) 242 if err != nil { 243 return nil, err 244 } 245 246 result = append(result, &SurfacerInfo{ 247 Surfacer: s, 248 Type: sType.String(), 249 Name: sDef.GetName(), 250 Conf: formatutils.ConfToString(conf), 251 }) 252 } 253 return result, nil 254 } 255 256 // Register allows you to register a user defined surfacer with cloudprober. 257 // Example usage: 258 // import ( 259 // "github.com/google/cloudprober" 260 // "github.com/google/cloudprober/surfacers" 261 // ) 262 // 263 // s := &FancySurfacer{} 264 // surfacers.Register("fancy_surfacer", s) 265 // pr, err := cloudprober.InitFromConfig(*configFile) 266 // if err != nil { 267 // log.Exitf("Error initializing cloudprober. Err: %v", err) 268 // } 269 func Register(name string, s Surfacer) { 270 userDefinedSurfacersMu.Lock() 271 defer userDefinedSurfacersMu.Unlock() 272 userDefinedSurfacers[name] = s 273 }