github.com/mweagle/Sparta@v1.15.0/aws/cloudwatch/structured_metric.go (about)

     1  package cloudwatch
     2  
     3  // So a metric is the top level fields that map to the Metric
     4  // info in the serialization layer. So we need a map of names to their
     5  // info. And we can map the rest in the log/publish statement...
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"strings"
    12  	"time"
    13  )
    14  
    15  var envMap map[string]string
    16  
    17  func init() {
    18  	// Get them all and turn it into a map...
    19  	// Ref: https://docs.aws.amazon.com/lambda/latest/dg/lambda-environment-variables.html
    20  	envMap = make(map[string]string)
    21  	envVars := os.Environ()
    22  	for _, eachValue := range envVars {
    23  		parts := strings.Split(eachValue, "=")
    24  		if len(parts) == 2 {
    25  			envMap[parts[0]] = parts[1]
    26  		}
    27  	}
    28  }
    29  
    30  // MetricDirective represents an element in the array
    31  
    32  // MetricUnit Represents a MetricUnit type
    33  type MetricUnit string
    34  
    35  const (
    36  	// UnitSeconds Seconds
    37  	UnitSeconds MetricUnit = "Seconds"
    38  	// UnitMicroseconds Microseconds
    39  	UnitMicroseconds MetricUnit = "Microseconds"
    40  	// UnitMilliseconds Milliseconds
    41  	UnitMilliseconds MetricUnit = "Milliseconds"
    42  	// UnitBytes Bytes
    43  	UnitBytes MetricUnit = "Bytes"
    44  	//UnitKilobytes Kilobytes
    45  	UnitKilobytes MetricUnit = "Kilobytes"
    46  	//UnitMegabytes Megabytes
    47  	UnitMegabytes MetricUnit = "Megabytes"
    48  	//UnitGigabytes Gigabytes
    49  	UnitGigabytes MetricUnit = "Gigabytes"
    50  	//UnitTerabytes Terabytes
    51  	UnitTerabytes MetricUnit = "Terabytes"
    52  	//UnitBits Bits
    53  	UnitBits MetricUnit = "Bits"
    54  	//UnitKilobits Kilobits
    55  	UnitKilobits MetricUnit = "Kilobits"
    56  	//UnitMegabits Megabits
    57  	UnitMegabits MetricUnit = "Megabits"
    58  	//UnitGigabits Gigabits
    59  	UnitGigabits MetricUnit = "Gigabits"
    60  	//UnitTerabits Terabits
    61  	UnitTerabits MetricUnit = "Terabits"
    62  	//UnitPercent Percent
    63  	UnitPercent MetricUnit = "Percent"
    64  	//UnitCount Count
    65  	UnitCount MetricUnit = "Count"
    66  	//UnitBytesPerSecond BytesPerSecond
    67  	UnitBytesPerSecond MetricUnit = "Bytes/Second"
    68  	//UnitKilobytesPerSecond KilobytesPerSecond
    69  	UnitKilobytesPerSecond MetricUnit = "Kilobytes/Second"
    70  	//UnitMegabytesPerSecond MegabytesPerSecond
    71  	UnitMegabytesPerSecond MetricUnit = "Megabytes/Second"
    72  	//UnitGigabytesPerSecond GigabytesPerSecond
    73  	UnitGigabytesPerSecond MetricUnit = "Gigabytes/Second"
    74  	//UnitTerabytesPerSecond TerabytesPerSecond
    75  	UnitTerabytesPerSecond MetricUnit = "Terabytes/Second"
    76  	//UnitBitsPerSecond BitsPerSecond
    77  	UnitBitsPerSecond MetricUnit = "Bits/Second"
    78  	//UnitKilobitsPerSecond KilobitsPerSecond
    79  	UnitKilobitsPerSecond MetricUnit = "Kilobits/Second"
    80  	//UnitMegabitsPerSecond MegabitsPerSecond
    81  	UnitMegabitsPerSecond MetricUnit = "Megabits/Second"
    82  	//UnitGigabitsPerSecond GigabitsPerSecond
    83  	UnitGigabitsPerSecond MetricUnit = "Gigabits/Second"
    84  	//UnitTerabitsPerSecond TerabitsPerSecond
    85  	UnitTerabitsPerSecond MetricUnit = "Terabits/Second"
    86  	//UnitCountPerSecond CountPerSecond
    87  	UnitCountPerSecond MetricUnit = "Count/Second"
    88  	// UnitNone No units
    89  	UnitNone MetricUnit = "None"
    90  )
    91  
    92  // MetricValue represents a metric value
    93  type MetricValue struct {
    94  	Value interface{}
    95  	Unit  MetricUnit
    96  }
    97  
    98  // MetricDirective is the directive that encapsulates a metric
    99  type MetricDirective struct {
   100  	// Dimensions corresponds to the JSON schema field "Dimensions".
   101  	Dimensions map[string]string
   102  
   103  	// Metrics corresponds to the JSON schema field "Metrics".
   104  	Metrics map[string]MetricValue
   105  
   106  	// namespace corresponds to the JSON schema field "Namespace".
   107  	namespace string
   108  }
   109  
   110  // EmbeddedMetric represents an embedded metric that should be published
   111  type EmbeddedMetric struct {
   112  	metrics    []*MetricDirective
   113  	properties map[string]interface{}
   114  }
   115  
   116  // WithProperty is a fluent builder to add property to the EmbeddedMetric state.
   117  // Properties should be used for high cardintality values that need to be
   118  // searchable, but not treated as independent metrics
   119  func (em *EmbeddedMetric) WithProperty(key string, value interface{}) *EmbeddedMetric {
   120  	if em.properties == nil {
   121  		em.properties = make(map[string]interface{})
   122  	}
   123  	em.properties[key] = value
   124  	return em
   125  }
   126  
   127  // NewMetricDirective returns an initialized MetricDirective
   128  // that's included in the EmbeddedMetric instance
   129  func (em *EmbeddedMetric) NewMetricDirective(namespace string,
   130  	dimensions map[string]string) *MetricDirective {
   131  	md := &MetricDirective{
   132  		namespace:  namespace,
   133  		Dimensions: dimensions,
   134  		Metrics:    make(map[string]MetricValue),
   135  	}
   136  	if md.Dimensions == nil {
   137  		md.Dimensions = make(map[string]string)
   138  	}
   139  	em.metrics = append(em.metrics, md)
   140  	return md
   141  }
   142  
   143  // PublishToSink writes the EmbeddedMetric info to the provided writer
   144  func (em *EmbeddedMetric) PublishToSink(additionalProperties map[string]interface{},
   145  	sink io.Writer) {
   146  	// BEGIN - Preconditions
   147  	for _, eachDirective := range em.metrics {
   148  		// Precondition...
   149  		if len(eachDirective.Dimensions) > 9 {
   150  			fmt.Printf("DimensionSet for structured metric must not have more than 9 elements. Count: %d",
   151  				len(eachDirective.Dimensions))
   152  		}
   153  	}
   154  	// END - Preconditions
   155  	for eachKey, eachValue := range additionalProperties {
   156  		em = em.WithProperty(eachKey, eachValue)
   157  	}
   158  	rawJSON, rawJSONErr := json.Marshal(em)
   159  	var writtenErr error
   160  	if rawJSONErr == nil {
   161  		_, writtenErr = io.WriteString(sink, (string)(rawJSON))
   162  	} else {
   163  		_, writtenErr = io.WriteString(sink, fmt.Sprintf("Error publishing metric: %v", rawJSONErr))
   164  	}
   165  	if writtenErr != nil {
   166  		fmt.Printf("ERROR: %#v", writtenErr)
   167  	}
   168  }
   169  
   170  // Publish the metric to the logfile
   171  func (em *EmbeddedMetric) Publish(additionalProperties map[string]interface{}) {
   172  	em.PublishToSink(additionalProperties, os.Stdout)
   173  }
   174  
   175  // MarshalJSON is a custom marshaller to ensure that the marshalled
   176  // headers are always lowercase
   177  func (em *EmbeddedMetric) MarshalJSON() ([]byte, error) {
   178  	/* From: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Generation_CloudWatch_Agent.html
   179  
   180  	The logs must contain a log_group_name key that tells the agent which log group to use.
   181  
   182  	Each log event must be on a single line. In other words, a log event cannot contain the newline (\n) character.
   183  	*/
   184  	jsonMap := map[string]interface{}{
   185  		"log_group_name": envMap["AWS_LAMBDA_LOG_GROUP_NAME"],
   186  		"log_steam_name": envMap["AWS_LAMBDA_LOG_STREAM_NAME"],
   187  	}
   188  	for eachKey, eachValue := range em.properties {
   189  		jsonMap[eachKey] = eachValue
   190  	}
   191  	// Walk everything and create the references...
   192  	cwMetrics := &emfAWS{
   193  		Timestamp:         int((time.Now().UnixNano() / int64(time.Millisecond))),
   194  		CloudWatchMetrics: []emfAWSCloudWatchMetricsElem{},
   195  	}
   196  	for _, eachDirective := range em.metrics {
   197  		metricsElem := emfAWSCloudWatchMetricsElem{
   198  			Dimensions: [][]string{},
   199  			Namespace:  eachDirective.namespace,
   200  			Metrics:    []emfAWSCloudWatchMetricsElemMetricsElem{},
   201  		}
   202  
   203  		// Create the references and update the metrics...
   204  		for eachKey, eachMetric := range eachDirective.Metrics {
   205  			jsonMap[eachKey] = eachMetric.Value
   206  			metricsElem.Metrics = append(metricsElem.Metrics,
   207  				emfAWSCloudWatchMetricsElemMetricsElem{
   208  					Name: eachKey,
   209  					Unit: string(eachMetric.Unit),
   210  				})
   211  		}
   212  		for eachKey, eachValue := range eachDirective.Dimensions {
   213  			jsonMap[eachKey] = eachValue
   214  			metricsElem.Dimensions = append(metricsElem.Dimensions,
   215  				[]string{eachKey})
   216  		}
   217  		cwMetrics.CloudWatchMetrics = append(cwMetrics.CloudWatchMetrics,
   218  			metricsElem)
   219  	}
   220  	jsonMap["_aws"] = cwMetrics
   221  	return json.Marshal(jsonMap)
   222  }
   223  
   224  // JSON encoding the fields gives us the top level keys, which we need
   225  // to map to the Metrics...
   226  // https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html#CloudWatch_Embedded_Metric_Format_Specification_structure_target
   227  
   228  // NewEmbeddedMetric returns a new fully initialized embedded metric. Callers
   229  // should populate the Fields
   230  func NewEmbeddedMetric() (*EmbeddedMetric, error) {
   231  	embeddedMetric := &EmbeddedMetric{
   232  		metrics:    []*MetricDirective{},
   233  		properties: make(map[string]interface{}),
   234  	}
   235  	return embeddedMetric, nil
   236  }
   237  
   238  // NewEmbeddedMetricWithProperties returns an EmbeddedMetric with the
   239  // user supplied properties
   240  func NewEmbeddedMetricWithProperties(props map[string]interface{}) (*EmbeddedMetric, error) {
   241  	embeddedMetric := &EmbeddedMetric{
   242  		metrics:    []*MetricDirective{},
   243  		properties: props,
   244  	}
   245  	if embeddedMetric.properties == nil {
   246  		embeddedMetric.properties = make(map[string]interface{})
   247  	}
   248  	return embeddedMetric, nil
   249  }