Skip to content

Commit e4d4fd5

Browse files
authored
Merge pull request #4 from harrisonhjones/feature-allow-customization-of-logger
Allow customization of the logger with NewWith
2 parents 96448d4 + 63a3716 commit e4d4fd5

File tree

7 files changed

+123
-15
lines changed

7 files changed

+123
-15
lines changed

.github/workflows/lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
steps:
1414
- uses: actions/checkout@v2
1515
- name: golangci-lint
16-
uses: golangci/golangci-lint-action@v1
16+
uses: golangci/golangci-lint-action@v2
1717
with:
18-
version: v1.26
18+
version: v1.29
1919
working-directory: emf

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414
# Dependency directories (remove the comment below to include it)
1515
# vendor/
1616

17+
# IDE artifacts
1718
.idea/

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Go implementation of AWS CloudWatch [Embedded Metric Format](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html)
77

88
It's aim is to simplify reporting metrics to CloudWatch:
9+
910
- using EMF avoids additional HTTP API calls to CloudWatch as metrics are logged in JSON format to stdout
1011
- no need for additional dependencies in your services (or mocks in tests) to report metrics from inside your code
1112
- built in support for default dimensions and properties for Lambda functions
@@ -14,27 +15,38 @@ It's aim is to simplify reporting metrics to CloudWatch:
1415
Supports namespaces, setting dimensions and properties as well as different contexts (at least partially).
1516

1617
Usage:
18+
1719
```
1820
emf.New().Namespace("mtg").Metric("totalWins", 1500).Log()
1921
2022
emf.New().Dimension("colour", "red").
2123
MetricAs("gameLength", 2, emf.Seconds).Log()
2224
2325
emf.New().DimensionSet(
24-
emf.NewDimension("format", "edh"),
26+
emf.NewDimension("format", "edh"),
2527
emf.NewDimension("commander", "Muldrotha")).
2628
MetricAs("wins", 1499, emf.Count).Log()
2729
```
2830

2931
You may also use the lib together with `defer`.
32+
3033
```
3134
m := emf.New() // sets up whatever you fancy here
3235
defer m.Log()
3336
3437
// any reporting metrics calls
3538
```
3639

40+
Customizing the logger:
41+
```
42+
emf.New(
43+
emf.WithWriter(os.Stderr), // Log to stderr.
44+
emf.WithTimestamp(time.Now().Add(-time.Hour)), // Record past metrics.
45+
)
46+
```
47+
3748
Functions for reporting metrics:
49+
3850
```
3951
func Metric(name string, value int)
4052
func Metrics(m map[string]int)
@@ -48,6 +60,7 @@ func MetricsFloatAs(m map[string]float64, unit MetricUnit)
4860
```
4961

5062
Functions for setting up dimensions:
63+
5164
```
5265
func Dimension(key, value string)
5366
func DimensionSet(dimensions ...Dimension) // use `func NewDimension` for creating one

emf/emf.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Spec available here: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html
1+
// Package emf implements the spec available here: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Embedded_Metric_Format_Specification.html
22
package emf
33

44
// Metadata struct as defined in AWS Embedded Metrics Format spec.

emf/logger.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,29 @@ type Context struct {
2424
values map[string]interface{}
2525
}
2626

27-
// New creates logger printing to os.Stdout, perfect for Lambda functions.
28-
func New() *Logger {
29-
return NewFor(os.Stdout)
27+
// LoggerOption defines a function that can be used to customize a logger.
28+
type LoggerOption func(l *Logger)
29+
30+
// WithWriter customizes the writer used by a logger.
31+
func WithWriter(w io.Writer) LoggerOption {
32+
return func(l *Logger) {
33+
l.out = w
34+
}
35+
}
36+
37+
// WithTimestamp customizes the timestamp used by a logger.
38+
func WithTimestamp(t time.Time) LoggerOption {
39+
return func(l *Logger) {
40+
l.timestamp = t.UnixNano() / int64(time.Millisecond)
41+
}
3042
}
3143

32-
// NewFor creates logger printing to any suitable writer.
33-
func NewFor(out io.Writer) *Logger {
44+
// New creates logger with reasonable defaults for Lambda functions:
45+
// - Prints to os.Stdout.
46+
// - Context based on Lambda environment variables.
47+
// - Timestamp set to the time when New was called.
48+
// Specify LoggerOptions to customize the logger.
49+
func New(opts ...LoggerOption) *Logger {
3450
values := make(map[string]interface{})
3551

3652
// set default properties for lambda function
@@ -48,12 +64,20 @@ func NewFor(out io.Writer) *Logger {
4864
values["traceId"] = amznTraceID
4965
}
5066

51-
return &Logger{
52-
out: out,
67+
// create a default logger
68+
l := &Logger{
69+
out: os.Stdout,
5370
defaultContext: newContext(values),
5471
values: values,
5572
timestamp: time.Now().UnixNano() / int64(time.Millisecond),
5673
}
74+
75+
// apply any options
76+
for _, opt := range opts {
77+
opt(l)
78+
}
79+
80+
return l
5781
}
5882

5983
// Dimension helps builds DimensionSet.
@@ -129,7 +153,7 @@ func (l *Logger) MetricAs(name string, value int, unit MetricUnit) *Logger {
129153
return l
130154
}
131155

132-
// Metrics puts all of the int metrics with MetricUnit on default context.
156+
// MetricsAs puts all of the int metrics with MetricUnit on default context.
133157
func (l *Logger) MetricsAs(m map[string]int, unit MetricUnit) *Logger {
134158
for name, value := range m {
135159
l.defaultContext.put(name, value, unit)

emf/logger_internal_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package emf
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"testing"
7+
"time"
8+
)
9+
10+
func TestNew(t *testing.T) {
11+
tcs := []struct {
12+
name string
13+
opts []LoggerOption
14+
expected *Logger
15+
}{
16+
{
17+
name: "default",
18+
expected: &Logger{
19+
out: os.Stdout,
20+
timestamp: time.Now().UnixNano() / int64(time.Millisecond),
21+
},
22+
},
23+
{
24+
name: "with options",
25+
opts: []LoggerOption{
26+
WithWriter(os.Stderr),
27+
WithTimestamp(time.Now().Add(time.Hour)),
28+
},
29+
expected: &Logger{
30+
out: os.Stderr,
31+
timestamp: time.Now().Add(time.Hour).UnixNano() / int64(time.Millisecond),
32+
},
33+
},
34+
}
35+
36+
for _, tc := range tcs {
37+
t.Run(tc.name, func(t *testing.T) {
38+
actual := New(tc.opts...)
39+
if err := loggersEqual(actual, tc.expected); err != nil {
40+
t.Errorf("logger does not match: %v", err)
41+
}
42+
})
43+
}
44+
45+
}
46+
47+
// loggersEqual returns a non-nil error if the loggers do not match.
48+
// Currently it only checks that the loggers' output writer and timestamp match.
49+
func loggersEqual(actual, expected *Logger) error {
50+
if actual.out != expected.out {
51+
return fmt.Errorf("output does not match")
52+
}
53+
54+
if err := approxInt64(actual.timestamp, expected.timestamp, 100 /* ms */); err != nil {
55+
return fmt.Errorf("timestamp %v", err)
56+
}
57+
58+
return nil
59+
}
60+
61+
func approxInt64(actual, expected, tolerance int64) error {
62+
diff := expected - actual
63+
if diff < 0 {
64+
diff = -diff
65+
}
66+
if diff > tolerance {
67+
return fmt.Errorf("value %v is out of tolerance %v±%v", actual, expected, tolerance)
68+
}
69+
return nil
70+
}

emf/logger_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ func TestEmf(t *testing.T) {
181181
}
182182

183183
var buf bytes.Buffer
184-
logger := emf.NewFor(&buf)
184+
logger := emf.New(emf.WithWriter(&buf))
185185
tc.given(logger)
186186
logger.Log()
187187

@@ -196,7 +196,7 @@ func TestEmf(t *testing.T) {
196196

197197
t.Run("no metrics set", func(t *testing.T) {
198198
var buf bytes.Buffer
199-
logger := emf.NewFor(&buf)
199+
logger := emf.New(emf.WithWriter(&buf))
200200
logger.Log()
201201

202202
if buf.String() != "" {
@@ -206,7 +206,7 @@ func TestEmf(t *testing.T) {
206206

207207
t.Run("new context, no metrics set", func(t *testing.T) {
208208
var buf bytes.Buffer
209-
logger := emf.NewFor(&buf)
209+
logger := emf.New(emf.WithWriter(&buf))
210210
logger.NewContext().Namespace("galaxy")
211211
logger.Log()
212212

0 commit comments

Comments
 (0)