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  }