diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 26ca9e4..b71ee72 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,12 +12,13 @@ jobs: runs-on: ubuntu-latest steps: - name: set up Go 1.x - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: ^1.14 + go-version: '1.20' + check-latest: true id: go - name: checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: modules run: go mod download - name: test diff --git a/README.md b/README.md index 3bd70d8..48cd509 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # aws-embedded-metrics-golang -![test](https://github.com/prozz/aws-embedded-metrics-golang/workflows/test/badge.svg?branch=master) -![golangci-lint](https://github.com/prozz/aws-embedded-metrics-golang/workflows/lint/badge.svg?branch=master) +![test](https://github.com/gaeste/aws-embedded-metrics-golang/workflows/test/badge.svg?branch=master) +![golangci-lint](https://github.com/gaeste/aws-embedded-metrics-golang/workflows/lint/badge.svg?branch=master) -Go implementation of AWS CloudWatch [Embedded Metric Format](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html) +Go implementation of [AWS CloudWatch Embedded Metric Format](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html). -It's aim is to simplify reporting metrics to CloudWatch: +The aim is to simplify reporting metrics to CloudWatch: - using EMF avoids additional HTTP API calls to CloudWatch as metrics are logged in JSON format to stdout - no need for additional dependencies in your services (or mocks in tests) to report metrics from inside your code @@ -16,7 +16,7 @@ Supports namespaces, setting dimensions and properties as well as different cont ## Installation ```shell -go get github.com/prozz/aws-embedded-metrics-golang +go get github.com/gaeste/aws-embedded-metrics-golang ``` ## Usage @@ -24,13 +24,24 @@ go get github.com/prozz/aws-embedded-metrics-golang ``` emf.New().Namespace("mtg").Metric("totalWins", 1500).Log() -emf.New().Dimension("colour", "red"). - MetricAs("gameLength", 2, emf.Seconds).Log() +emf.New(). + Dimension("colour", "red"). + MetricAs("gameLength", 2, emf.Seconds). + Log() -emf.New().DimensionSet( +emf.New(). + DimensionSet( emf.NewDimension("format", "edh"), - emf.NewDimension("commander", "Muldrotha")). - MetricAs("wins", 1499, emf.Count).Log() + emf.NewDimension("commander", "Muldrotha") + ). + MetricAs("wins", 1499, emf.Count). + Log() + +metrics := emf.New(). + Namespace("mtg"). + Metric("totalWins", 1500). + Build() +log.With(metrics).Info("log message with metrics attached as named value pair") ``` You may also use the lib together with `defer`. diff --git a/emf/logger.go b/emf/logger.go index 22223f4..425b013 100644 --- a/emf/logger.go +++ b/emf/logger.go @@ -195,6 +195,22 @@ func (l *Logger) MetricsFloatAs(m map[string]float64, unit MetricUnit) *Logger { // Log prints all Contexts and metric values to chosen output in Embedded Metric Format. func (l *Logger) Log() { + emf := l.build() + if len(emf) == 0 { + return + } + + buf, _ := json.Marshal(emf) + _, _ = fmt.Fprintln(l.out, string(buf)) +} + +// Build constructs the EMF structure as a map that includes all Contexts and metric values. +// The map is arranged according to Embedded Metric Format. +func (l *Logger) Build() map[string]interface{} { + return l.build() +} + +func (l *Logger) build() map[string]interface{} { var metrics []MetricDirective if len(l.defaultContext.metricDirective.Metrics) > 0 { metrics = append(metrics, l.defaultContext.metricDirective) @@ -206,7 +222,7 @@ func (l *Logger) Log() { } if len(metrics) == 0 { - return + return map[string]interface{}{} } l.values["_aws"] = Metadata{ @@ -214,8 +230,8 @@ func (l *Logger) Log() { Metrics: metrics, LogGroupName: l.logGroupName, } - buf, _ := json.Marshal(l.values) - _, _ = fmt.Fprintln(l.out, string(buf)) + + return l.values } // NewContext creates new context for given logger. diff --git a/emf/logger_test.go b/emf/logger_test.go index b35dd62..3340cb3 100644 --- a/emf/logger_test.go +++ b/emf/logger_test.go @@ -2,12 +2,12 @@ package emf_test import ( "bytes" - "io/ioutil" + "encoding/json" "os" "testing" + "github.com/gaeste/aws-embedded-metrics-golang/emf" "github.com/kinbiko/jsonassert" - "github.com/prozz/aws-embedded-metrics-golang/emf" ) func TestEmf(t *testing.T) { @@ -219,12 +219,17 @@ func TestEmf(t *testing.T) { logger.Log() println(buf.String()) - f, err := ioutil.ReadFile(tc.expected) + f, err := os.ReadFile(tc.expected) if err != nil { t.Fatal("unable to read file with expected json") } jsonassert.New(t).Assertf(buf.String(), string(f)) + + // test Build() to generate the same structure as Log() + metrics := logger.Build() + metricsJson, _ := json.Marshal(metrics) + jsonassert.New(t).Assertf(string(metricsJson), string(f)) }) } @@ -238,6 +243,14 @@ func TestEmf(t *testing.T) { } }) + t.Run("no metrics set, with build", func(t *testing.T) { + m := emf.New().Build() + + if len(m) > 0 { + t.Error("Map not empty") + } + }) + t.Run("new context, no metrics set", func(t *testing.T) { var buf bytes.Buffer logger := emf.New(emf.WithWriter(&buf)) @@ -248,6 +261,16 @@ func TestEmf(t *testing.T) { t.Error("Buffer not empty") } }) + + t.Run("new context, no metrics set, with build", func(t *testing.T) { + logger := emf.New() + logger.NewContext().Namespace("galaxy") + m := logger.Build() + + if len(m) > 0 { + t.Error("Map not empty") + } + }) } func setenv(t *testing.T, env map[string]string) { diff --git a/go.mod b/go.mod index bb83c80..f4bb0ec 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ -module github.com/prozz/aws-embedded-metrics-golang +module github.com/gaeste/aws-embedded-metrics-golang -go 1.14 +go 1.20 require github.com/kinbiko/jsonassert v1.1.1