Skip to content

Commit 167abc2

Browse files
jdgeislerMovieStoreGuy
authored andcommitted
[receiver/datadogreceiver] add json handling for the api/v2/series endpoint (open-telemetry#36218)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Adding json handling for the `api/v2/series` endpoint. The datadog api client libraries use json messages, however only protobuf messages are currently supported in the` api/v2/series` endpoint, so requests fail with `proto: illegal wireType 6` If `Content-Type: application/json` is set, then we handle the json message. Otherwise, we handle the protobuf message. <!-- Issue number (e.g. open-telemetry#1234) or full URL to issue, if applicable. --> #### Link to tracking issue Fixes open-telemetry#36079 <!--Describe what testing was performed and which tests were added.--> #### Testing Added test with a json metric payload that now passes. Additionally, I also tested these changes in my own image and confirmed that the datadog api client libraries can now successfully ship metrics to the `api/v2/series` endpoint. I also confirmed with the following curl: ``` curl -X POST \ -H "Content-Type: application/json" \ -H "DD-API-KEY: your_api_key_here" \ -d '{ "series": [ { "resources": [ { "name": "dummyhost", "type": "host" } ], "tags": ["env:test"], "metric": "test.metric", "points": [ { "timestamp": 1730829575, "value": 1.0 } ], "type": 3 } ] }' \ https://datadog-receiver/api/v2/series {"errors":[]} ``` --------- Co-authored-by: Sean Marciniak <30928402+MovieStoreGuy@users.noreply.github.com>
1 parent 6bfcf63 commit 167abc2

File tree

3 files changed

+119
-4
lines changed

3 files changed

+119
-4
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: 'enhancement'
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: 'datadogreceiver'
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Add json handling for the `api/v2/series` endpoint in the datadogreceiver"
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [36079]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: []

receiver/datadogreceiver/internal/translator/series.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package translator // import "github.com/open-telemetry/opentelemetry-collector-contrib/receiver/datadogreceiver/internal/translator"
55

66
import (
7+
"encoding/json"
78
"io"
89
"net/http"
910
"strings"
@@ -27,19 +28,27 @@ type SeriesList struct {
2728
Series []datadogV1.Series `json:"series"`
2829
}
2930

30-
// TODO: add handling for JSON format in additional to protobuf?
3131
func (mt *MetricsTranslator) HandleSeriesV2Payload(req *http.Request) (mp []*gogen.MetricPayload_MetricSeries, err error) {
3232
buf := GetBuffer()
3333
defer PutBuffer(buf)
3434
if _, err := io.Copy(buf, req.Body); err != nil {
3535
return mp, err
3636
}
3737

38+
contentType := req.Header.Get("Content-Type")
39+
3840
pl := new(gogen.MetricPayload)
39-
if err := pl.Unmarshal(buf.Bytes()); err != nil {
40-
return mp, err
41-
}
4241

42+
// handle json messages if set, otherwise handle protobuf
43+
if contentType == "application/json" {
44+
if err := json.Unmarshal(buf.Bytes(), &pl); err != nil {
45+
return mp, err
46+
}
47+
} else {
48+
if err := pl.Unmarshal(buf.Bytes()); err != nil {
49+
return mp, err
50+
}
51+
}
4352
return pl.GetSeries(), nil
4453
}
4554

receiver/datadogreceiver/receiver_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,85 @@ func TestDatadogMetricsV2_EndToEnd(t *testing.T) {
459459
assert.Equal(t, pcommon.Timestamp(1636629071*1_000_000_000), metric.Sum().DataPoints().At(1).StartTimestamp())
460460
}
461461

462+
func TestDatadogMetricsV2_EndToEndJSON(t *testing.T) {
463+
cfg := createDefaultConfig().(*Config)
464+
cfg.Endpoint = "localhost:0" // Using a randomly assigned address
465+
sink := new(consumertest.MetricsSink)
466+
467+
dd, err := newDataDogReceiver(
468+
cfg,
469+
receivertest.NewNopSettings(),
470+
)
471+
require.NoError(t, err, "Must not error when creating receiver")
472+
dd.(*datadogReceiver).nextMetricsConsumer = sink
473+
474+
require.NoError(t, dd.Start(context.Background(), componenttest.NewNopHost()))
475+
defer func() {
476+
require.NoError(t, dd.Shutdown(context.Background()))
477+
}()
478+
479+
metricsPayloadV2 := []byte(`{
480+
"series": [
481+
{
482+
"metric": "system.load.1",
483+
"type": 1,
484+
"points": [
485+
{
486+
"timestamp": 1636629071,
487+
"value": 1.5
488+
},
489+
{
490+
"timestamp": 1636629081,
491+
"value": 2.0
492+
}
493+
],
494+
"resources": [
495+
{
496+
"name": "dummyhost",
497+
"type": "host"
498+
}
499+
]
500+
}
501+
]
502+
}`)
503+
504+
req, err := http.NewRequest(
505+
http.MethodPost,
506+
fmt.Sprintf("http://%s/api/v2/series", dd.(*datadogReceiver).address),
507+
io.NopCloser(bytes.NewReader(metricsPayloadV2)),
508+
)
509+
510+
req.Header.Set("Content-Type", "application/json")
511+
512+
require.NoError(t, err, "Must not error when creating request")
513+
514+
resp, err := http.DefaultClient.Do(req)
515+
require.NoError(t, err, "Must not error performing request")
516+
517+
body, err := io.ReadAll(resp.Body)
518+
require.NoError(t, multierr.Combine(err, resp.Body.Close()), "Must not error when reading body")
519+
require.JSONEq(t, `{"errors": []}`, string(body), "Expected JSON response to be `{\"errors\": []}`, got %s", string(body))
520+
require.Equal(t, http.StatusAccepted, resp.StatusCode)
521+
522+
mds := sink.AllMetrics()
523+
require.Len(t, mds, 1)
524+
got := mds[0]
525+
require.Equal(t, 1, got.ResourceMetrics().Len())
526+
metrics := got.ResourceMetrics().At(0).ScopeMetrics().At(0).Metrics()
527+
assert.Equal(t, 1, metrics.Len())
528+
metric := metrics.At(0)
529+
assert.Equal(t, pmetric.MetricTypeSum, metric.Type())
530+
assert.Equal(t, "system.load.1", metric.Name())
531+
assert.Equal(t, pmetric.AggregationTemporalityDelta, metric.Sum().AggregationTemporality())
532+
assert.False(t, metric.Sum().IsMonotonic())
533+
assert.Equal(t, pcommon.Timestamp(1636629071*1_000_000_000), metric.Sum().DataPoints().At(0).Timestamp())
534+
assert.Equal(t, 1.5, metric.Sum().DataPoints().At(0).DoubleValue())
535+
assert.Equal(t, pcommon.Timestamp(0), metric.Sum().DataPoints().At(0).StartTimestamp())
536+
assert.Equal(t, pcommon.Timestamp(1636629081*1_000_000_000), metric.Sum().DataPoints().At(1).Timestamp())
537+
assert.Equal(t, 2.0, metric.Sum().DataPoints().At(1).DoubleValue())
538+
assert.Equal(t, pcommon.Timestamp(1636629071*1_000_000_000), metric.Sum().DataPoints().At(1).StartTimestamp())
539+
}
540+
462541
func TestDatadogSketches_EndToEnd(t *testing.T) {
463542
cfg := createDefaultConfig().(*Config)
464543
cfg.Endpoint = "localhost:0" // Using a randomly assigned address

0 commit comments

Comments
 (0)