Skip to content

Commit 3174a81

Browse files
Merge pull request #7 from DataDog/darcy.rayner/debug-logging
Darcy.rayner/debug logging
2 parents e996df8 + 637f418 commit 3174a81

File tree

11 files changed

+171
-47
lines changed

11 files changed

+171
-47
lines changed

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,19 @@ Datadog's Lambda Go client library enables distributed tracing between serverful
88
go get github.com/DataDog/datadog-lambda-go
99
```
1010

11-
The following Datadog environment variables should be defined via the AWS CLI or Serverless Framework:
11+
You can set the following environment variables via the AWS CLI or Serverless Framework
1212

13-
- DD_API_KEY
13+
### DD_API_KEY
1414

15-
Set the following Datadog environment variable to `datadoghq.eu` to send your data to the Datadog EU site.
15+
Your datadog API key
1616

17-
- DD_SITE
17+
### DD_SITE
18+
19+
Which Datadog site to use. Set this to `datadoghq.eu` to send your data to the Datadog EU site.
20+
21+
### DD_LOG_LEVEL
22+
23+
How much logging datadog-lambda-go should do. Set this to "debug" for extensive logs.
1824

1925
## Usage
2026

@@ -35,7 +41,6 @@ func main() {
3541
lambda.Start(ddlambda.WrapHandler(myHandler, &ddlambda.Config{
3642
BatchInterval: time.Seconds * 15
3743
APIKey: "my-api-key",
38-
AppKey: "my-app-key",
3944
}))
4045
*/
4146
}

ddlambda.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import (
1414
"net/http"
1515
"os"
1616
"runtime"
17+
"strings"
1718
"time"
1819

20+
"github.com/DataDog/datadog-lambda-go/internal/logger"
1921
"github.com/DataDog/datadog-lambda-go/internal/metrics"
2022
"github.com/DataDog/datadog-lambda-go/internal/trace"
2123
"github.com/DataDog/datadog-lambda-go/internal/wrapper"
@@ -35,6 +37,9 @@ type (
3537
// Site is the host to send metrics to. If empty, this value is read from the 'DD_SITE' environment variable, or if that is empty
3638
// will default to 'datadoghq.com'.
3739
Site string
40+
41+
// DebugLogging will turn on extended debug logging.
42+
DebugLogging bool
3843
}
3944
)
4045

@@ -43,12 +48,24 @@ const (
4348
DatadogAPIKeyEnvVar = "DD_API_KEY"
4449
// DatadogSiteEnvVar is the environment variable that will be used as the API host.
4550
DatadogSiteEnvVar = "DD_SITE"
51+
// DatadogLogLevelEnvVar is the environment variable that will be used to check the log level.
52+
// if it equals "debug" everything will be logged.
53+
DatadogLogLevelEnvVar = "DD_LOG_LEVEL"
4654
)
4755

4856
// WrapHandler is used to instrument your lambda functions, reading in context from API Gateway.
4957
// It returns a modified handler that can be passed directly to the lambda.Start function.
5058
func WrapHandler(handler interface{}, cfg *Config) interface{} {
5159

60+
logLevel := os.Getenv(DatadogLogLevelEnvVar)
61+
if strings.EqualFold(logLevel, "debug") {
62+
logger.SetLogLevel(logger.LevelDebug)
63+
}
64+
65+
if cfg != nil && cfg.DebugLogging {
66+
logger.SetLogLevel(logger.LevelDebug)
67+
}
68+
5269
// Set up state that is shared between handler invocations
5370
tl := trace.Listener{}
5471
ml := metrics.MakeListener(cfg.toMetricsConfig())
@@ -79,6 +96,7 @@ func GetContext() context.Context {
7996
func DistributionWithContext(ctx context.Context, metric string, value float64, tags ...string) {
8097
pr := metrics.GetProcessor(GetContext())
8198
if pr == nil {
99+
logger.Error(fmt.Errorf("couldn't get metrics processor from current context"))
82100
return
83101
}
84102

@@ -91,6 +109,7 @@ func DistributionWithContext(ctx context.Context, metric string, value float64,
91109
Values: []metrics.MetricValue{},
92110
}
93111
m.AddPoint(time.Now(), value)
112+
logger.Debug(fmt.Sprintf("adding metric \"%s\", with value %f", metric, value))
94113
pr.AddMetric(&m)
95114
}
96115

@@ -115,9 +134,13 @@ func (cfg *Config) toMetricsConfig() metrics.Config {
115134
mc.APIKey = os.Getenv(DatadogAPIKeyEnvVar)
116135

117136
}
137+
if mc.APIKey == "" {
138+
logger.Error(fmt.Errorf("couldn't read DD_API_KEY from environment"))
139+
}
118140
if mc.Site == "" {
119141
mc.Site = os.Getenv(DatadogSiteEnvVar)
120142
}
143+
121144
return mc
122145
}
123146

internal/logger/log.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package logger
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"log"
7+
)
8+
9+
// LogLevel represents the level of logging that should be performed
10+
type LogLevel int
11+
12+
const (
13+
// LevelDebug logs all information
14+
LevelDebug LogLevel = iota
15+
// LevelError only logs errors
16+
LevelError LogLevel = iota
17+
)
18+
19+
var (
20+
logLevel = LevelError
21+
)
22+
23+
// SetLogLevel set the level of logging for the ddlambda
24+
func SetLogLevel(ll LogLevel) {
25+
logLevel = ll
26+
}
27+
28+
// Error logs a structured error message to stdout
29+
func Error(err error) {
30+
31+
type logStructure struct {
32+
Status string `json:"status"`
33+
Message string `json:"message"`
34+
}
35+
36+
finalMessage := logStructure{
37+
Status: "error",
38+
Message: fmt.Sprintf("datadog: %s", err.Error()),
39+
}
40+
result, _ := json.Marshal(finalMessage)
41+
42+
log.Println(string(result))
43+
}
44+
45+
// Debug logs a structured log message to stdout
46+
func Debug(message string) {
47+
if logLevel > LevelDebug {
48+
return
49+
}
50+
type logStructure struct {
51+
Status string `json:"status"`
52+
Message string `json:"message"`
53+
}
54+
finalMessage := logStructure{
55+
Status: "debug",
56+
Message: fmt.Sprintf("datadog: %s", message),
57+
}
58+
59+
result, _ := json.Marshal(finalMessage)
60+
61+
log.Println(string(result))
62+
}

internal/metrics/api.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import (
1313
"context"
1414
"encoding/json"
1515
"fmt"
16+
"io/ioutil"
1617
"net/http"
18+
19+
"github.com/DataDog/datadog-lambda-go/internal/logger"
1720
)
1821

1922
type (
@@ -70,14 +73,18 @@ func (cl *APIClient) SendMetrics(metrics []APIMetric) error {
7073
}
7174
body := bytes.NewBuffer(content)
7275

73-
req, err := http.NewRequest("POST", cl.makeRoute("series"), body)
76+
// For the moment we only support distribution metrics.
77+
// Other metric types use the "series" endpoint, which takes an identical payload.
78+
req, err := http.NewRequest("POST", cl.makeRoute("distribution_points"), body)
7479
if err != nil {
7580
return fmt.Errorf("Couldn't create send metrics request:%v", err)
7681
}
7782
req = req.WithContext(cl.context)
7883

7984
defer req.Body.Close()
8085

86+
logger.Debug(fmt.Sprintf("Sending payload with body %s", content))
87+
8188
cl.addAPICredentials(req)
8289

8390
resp, err := cl.httpClient.Do(req)
@@ -87,8 +94,13 @@ func (cl *APIClient) SendMetrics(metrics []APIMetric) error {
8794
}
8895
defer resp.Body.Close()
8996

90-
if resp.StatusCode != http.StatusCreated {
91-
return fmt.Errorf("Failed to send metrics to API. Status Code %d", resp.StatusCode)
97+
if resp.StatusCode < 200 || resp.StatusCode > 299 {
98+
bodyBytes, err := ioutil.ReadAll(resp.Body)
99+
body := ""
100+
if err == nil {
101+
body = string(bodyBytes)
102+
}
103+
return fmt.Errorf("Failed to send metrics to API. Status Code %d, Body %s", resp.StatusCode, body)
92104
}
93105
return nil
94106
}
@@ -100,7 +112,9 @@ func (cl *APIClient) addAPICredentials(req *http.Request) {
100112
}
101113

102114
func (cl *APIClient) makeRoute(route string) string {
103-
return fmt.Sprintf("%s/%s", cl.baseAPIURL, route)
115+
url := fmt.Sprintf("%s/%s", cl.baseAPIURL, route)
116+
logger.Debug(fmt.Sprintf("posting to url %s", url))
117+
return url
104118
}
105119

106120
func marshalAPIMetricsModel(metrics []APIMetric) ([]byte, error) {

internal/metrics/api_test.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,8 @@ func TestSendMetricsSuccess(t *testing.T) {
5353
body, _ := ioutil.ReadAll(r.Body)
5454
s := string(body)
5555

56-
assert.Equal(t, "/series?api_key=12345", r.URL.String())
57-
assert.Equal(t, "{\"series\":[{\"metric\":\"metric-1\",\"tags\":[\"a\",\"b\",\"c\"],\"type\":\"distribution\",\"points\":[[1,2],[3,4],[5,6]]}]}", s)
56+
assert.Equal(t, "/distribution_points?api_key=12345", r.URL.String())
57+
assert.Equal(t, "{\"series\":[{\"metric\":\"metric-1\",\"tags\":[\"a\",\"b\",\"c\"],\"type\":\"distribution\",\"points\":[[1,[2]],[3,[4]],[5,[6]]]}]}", s)
5858

5959
}))
6060
defer server.Close()
@@ -65,8 +65,10 @@ func TestSendMetricsSuccess(t *testing.T) {
6565
Host: nil,
6666
Tags: []string{"a", "b", "c"},
6767
MetricType: DistributionType,
68-
Points: [][]float64{
69-
{float64(1), float64(2)}, {float64(3), float64(4)}, {float64(5), float64(6)},
68+
Points: []interface{}{
69+
[]interface{}{float64(1), []interface{}{float64(2)}},
70+
[]interface{}{float64(3), []interface{}{float64(4)}},
71+
[]interface{}{float64(5), []interface{}{float64(6)}},
7072
},
7173
},
7274
}
@@ -86,8 +88,8 @@ func TestSendMetricsBadRequest(t *testing.T) {
8688
body, _ := ioutil.ReadAll(r.Body)
8789
s := string(body)
8890

89-
assert.Equal(t, "/series?api_key=12345", r.URL.String())
90-
assert.Equal(t, "{\"series\":[{\"metric\":\"metric-1\",\"tags\":[\"a\",\"b\",\"c\"],\"type\":\"distribution\",\"points\":[[1,2],[3,4],[5,6]]}]}", s)
91+
assert.Equal(t, "/distribution_points?api_key=12345", r.URL.String())
92+
assert.Equal(t, "{\"series\":[{\"metric\":\"metric-1\",\"tags\":[\"a\",\"b\",\"c\"],\"type\":\"distribution\",\"points\":[[1,[2]],[3,[4]],[5,[6]]]}]}", s)
9193

9294
}))
9395
defer server.Close()
@@ -98,8 +100,10 @@ func TestSendMetricsBadRequest(t *testing.T) {
98100
Host: nil,
99101
Tags: []string{"a", "b", "c"},
100102
MetricType: DistributionType,
101-
Points: [][]float64{
102-
{float64(1), float64(2)}, {float64(3), float64(4)}, {float64(5), float64(6)},
103+
Points: []interface{}{
104+
[]interface{}{float64(1), []interface{}{float64(2)}},
105+
[]interface{}{float64(3), []interface{}{float64(4)}},
106+
[]interface{}{float64(5), []interface{}{float64(6)}},
103107
},
104108
},
105109
}
@@ -124,8 +128,10 @@ func TestSendMetricsCantReachServer(t *testing.T) {
124128
Host: nil,
125129
Tags: []string{"a", "b", "c"},
126130
MetricType: DistributionType,
127-
Points: [][]float64{
128-
{float64(1), float64(2)}, {float64(3), float64(4)}, {float64(5), float64(6)},
131+
Points: []interface{}{
132+
[]interface{}{float64(1), []interface{}{float64(2)}},
133+
[]interface{}{float64(3), []interface{}{float64(4)}},
134+
[]interface{}{float64(5), []interface{}{float64(6)}},
129135
},
130136
},
131137
}

internal/metrics/batcher_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,10 @@ func TestToAPIMetricsSameInterval(t *testing.T) {
138138
Tags: []string{"a", "b", "c"},
139139
MetricType: DistributionType,
140140
Interval: nil,
141-
Points: [][]float64{
142-
{floatTime, 1}, {floatTime, 2}, {floatTime, 3},
141+
Points: []interface{}{
142+
[]interface{}{floatTime, []interface{}{float64(1)}},
143+
[]interface{}{floatTime, []interface{}{float64(2)}},
144+
[]interface{}{floatTime, []interface{}{float64(3)}},
143145
},
144146
},
145147
}

internal/metrics/listener.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func MakeListener(config Config) Listener {
3838
if site == "" {
3939
site = defaultSite
4040
}
41-
baseAPIURL := fmt.Sprintf("https://api.%s/api/v1/", config.Site)
41+
baseAPIURL := fmt.Sprintf("https://api.%s/api/v1", site)
4242

4343
apiClient := MakeAPIClient(context.Background(), baseAPIURL, config.APIKey)
4444
if config.BatchInterval <= 0 {

internal/metrics/model.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ type (
2323

2424
// APIMetric is a metric that can be marshalled to send to the metrics API
2525
APIMetric struct {
26-
Name string `json:"metric"`
27-
Host *string `json:"host,omitempty"`
28-
Tags []string `json:"tags,omitempty"`
29-
MetricType MetricType `json:"type"`
30-
Interval *float64 `json:"interval,omitempty"`
31-
Points [][]float64 `json:"points"`
26+
Name string `json:"metric"`
27+
Host *string `json:"host,omitempty"`
28+
Tags []string `json:"tags,omitempty"`
29+
MetricType MetricType `json:"type"`
30+
Interval *float64 `json:"interval,omitempty"`
31+
Points []interface{} `json:"points"`
3232
}
3333

3434
// MetricValue represents a datapoint for a metric
@@ -75,12 +75,12 @@ func (d *Distribution) Join(metric Metric) {
7575

7676
// ToAPIMetric converts a distribution into an API ready format.
7777
func (d *Distribution) ToAPIMetric(interval time.Duration) []APIMetric {
78-
points := make([][]float64, len(d.Values))
78+
points := make([]interface{}, len(d.Values))
7979

8080
for i, val := range d.Values {
8181
currentTime := float64(val.Timestamp.Unix())
8282

83-
points[i] = []float64{currentTime, val.Value}
83+
points[i] = []interface{}{currentTime, []interface{}{val.Value}}
8484
}
8585

8686
return []APIMetric{

internal/metrics/processor.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@ package metrics
1010

1111
import (
1212
"context"
13+
"fmt"
1314
"sync"
1415
"time"
1516

17+
"github.com/DataDog/datadog-lambda-go/internal/logger"
1618
"github.com/cenkalti/backoff"
1719
)
1820

@@ -110,9 +112,15 @@ func (p *processor) processMetrics() {
110112
if shouldExit && p.shouldRetryOnFail {
111113
// If we are shutting down, and we just failed to send our last batch, do a retry
112114
bo := backoff.WithMaxRetries(backoff.NewConstantBackOff(defaultRetryInterval), 2)
113-
backoff.Retry(p.sendMetricsBatch, bo)
115+
err := backoff.Retry(p.sendMetricsBatch, bo)
116+
if err != nil {
117+
logger.Error(fmt.Errorf("failed to flush metrics to datadog API after retry: %v", err))
118+
}
114119
} else {
115-
p.sendMetricsBatch()
120+
err := p.sendMetricsBatch()
121+
if err != nil {
122+
logger.Error(fmt.Errorf("failed to flush metrics to datadog API: %v", err))
123+
}
116124
}
117125
}
118126
}

0 commit comments

Comments
 (0)