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 }