-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add Initial Support for Application Metrics (#25)
* feat: init application metrics * feat: add metrics to Terraform example * refactor: move metrics to transforms * docs: added comment on logging
- Loading branch information
Showing
18 changed files
with
225 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# metrics | ||
|
||
Contains interfaces and methods for generating application metrics and sending them to external services. Metrics can be generated anywhere in the application and optionally sent to a single external service. The naming convention for metrics names and attributes is PascalCase, also known as upper camel case (e.g. UpperCamelCase). | ||
|
||
Information for each metrics generator is available in the [GoDoc](https://pkg.go.dev/github.com/brexhq/substation/internal/metrics). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package metrics | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"time" | ||
|
||
"github.com/brexhq/substation/internal/json" | ||
) | ||
|
||
// AWSCloudWatchEmbeddedMetrics creates a metric in the AWS Embedded Metrics Format and writes it to standard output. This is the preferred method for generating metrics from AWS Lambda functions. Read more about the Embedded Metrics Format specification here: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html. | ||
type AWSCloudWatchEmbeddedMetrics struct{} | ||
|
||
/* | ||
Generate creates a metric with the AWSCloudWatchEmbeddedMetrics metrics generator. All Attributes in the metrics.Data struct are inserted as CloudWatch Metrics dimensions; if the generator is invoked from an AWS Lambda function, then the function name is automatically added as a dimension. This method creates a JSON object with the structure shown below, where references are filled in from the metrics.Data struct: | ||
{ | ||
"_aws": { | ||
"Timestamp": $currentTime, | ||
"CloudWatchMetrics": [ | ||
{ | ||
"Namespace": $metricsApplication, | ||
"Dimensions": [ | ||
[ | ||
$data.Attributes.key | ||
] | ||
], | ||
"Name": $data.Name, | ||
} | ||
] | ||
}, | ||
$data.Attributes.key: $data.Attributes.value, | ||
$data.Name: $data.Value | ||
} | ||
*/ | ||
func (m AWSCloudWatchEmbeddedMetrics) Generate(ctx context.Context, data Data) (err error) { | ||
emf := []byte{} | ||
|
||
// default values for CloudWatch metrics from Substation applications | ||
// if the metrics are generated from AWS Lambda, then the function name is automatically tagged | ||
emf, err = json.Set(emf, "_aws.Timestamp", time.Now().UnixMilli()) | ||
if err != nil { | ||
return fmt.Errorf("metrics log_embedded_metrics: %v", err) | ||
} | ||
|
||
emf, err = json.Set(emf, "_aws.CloudWatchMetrics.0.Namespace", metricsApplication) | ||
if err != nil { | ||
return fmt.Errorf("metrics log_embedded_metrics: %v", err) | ||
} | ||
|
||
if metricsAWSLambdaFunctionName != "" { | ||
attr := map[string]string{"FunctionName": metricsAWSLambdaFunctionName} | ||
data.AddAttributes(attr) | ||
} | ||
|
||
for key, val := range data.Attributes { | ||
emf, err = json.Set(emf, "_aws.CloudWatchMetrics.0.Dimensions.-1.-1", key) | ||
if err != nil { | ||
return fmt.Errorf("metrics log_embedded_metrics: %v", err) | ||
} | ||
|
||
emf, err = json.Set(emf, key, val) | ||
if err != nil { | ||
return fmt.Errorf("metrics log_embedded_metrics: %v", err) | ||
} | ||
} | ||
|
||
emf, err = json.Set(emf, "_aws.CloudWatchMetrics.0.Metrics.0.Name", data.Name) | ||
if err != nil { | ||
return fmt.Errorf("metrics log_embedded_metrics: %v", err) | ||
} | ||
|
||
emf, err = json.Set(emf, data.Name, data.Value) | ||
if err != nil { | ||
return fmt.Errorf("metrics log_embedded_metrics: %v", err) | ||
} | ||
|
||
// logging EMF to standard out in AWS Lambda automatically sends metrics to CloudWatch | ||
fmt.Println(string(emf)) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package metrics | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/brexhq/substation/internal/errors" | ||
) | ||
|
||
// unsupportedDestination is returned when an unsupported Metrics destination is referenced in Factory. | ||
const unsupportedDestination = errors.Error("unsupportedDestination") | ||
|
||
// referenced across all metrics generators | ||
var metricsDestination string | ||
var metricsApplication string | ||
|
||
// used when generating metrics from AWS Lambda functions | ||
var metricsAWSLambdaFunctionName string | ||
|
||
func init() { | ||
// determines if metrics should be generated across the application. the value from this environment variable is used to retrieve the metrics destination from the Factory. | ||
if m, ok := os.LookupEnv("SUBSTATION_METRICS"); ok { | ||
metricsDestination = m | ||
} | ||
|
||
metricsApplication = "Substation" | ||
|
||
metricsAWSLambdaFunctionName, _ = os.LookupEnv("AWS_LAMBDA_FUNCTION_NAME") | ||
} | ||
|
||
// Data contains a metric that can be sent to external services. | ||
type Data struct { | ||
// Contextual information related to the metric. If the external service accepts key-value pairs (e.g., identifiers, tags), then this is passed directly to the service. | ||
Attributes map[string]string | ||
|
||
// A short name that describes the metric. This is passed directly to the external service and should use the upper camel case (UpperCamelCase) naming convention. | ||
Name string | ||
|
||
// The metric data point. This value is converted to the correct data type before being sent to the external service. | ||
Value interface{} | ||
} | ||
|
||
// AddAttributes is a convenience method for adding attributes to a metric. | ||
func (d *Data) AddAttributes(attr map[string]string) { | ||
if d.Attributes == nil { | ||
d.Attributes = make(map[string]string) | ||
} | ||
|
||
for key, val := range attr { | ||
d.Attributes[key] = val | ||
} | ||
} | ||
|
||
// Generator is an interface for creating a metric and sending it to an external service. | ||
type Generator interface { | ||
Generate(context.Context, Data) error | ||
} | ||
|
||
// Generate is a convenience function that encapsulates the Factory and creates a metric. If the SUBSTATION_METRICS environment variable is not set, then no metrics are created. | ||
func Generate(ctx context.Context, data Data) error { | ||
if metricsDestination == "" { | ||
return nil | ||
} | ||
|
||
gen, err := Factory(metricsDestination) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err := gen.Generate(ctx, data); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Factory returns a configured metrics Generator. This is the recommended method for retrieving ready-to-use Generators. | ||
func Factory(destination string) (Generator, error) { | ||
switch destination { | ||
case "AWS_CLOUDWATCH_EMBEDDED_METRICS": | ||
var m AWSCloudWatchEmbeddedMetrics | ||
return m, nil | ||
default: | ||
return nil, fmt.Errorf("metrics destination %s: %v", destination, unsupportedDestination) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.