Skip to content

Commit a0c21bc

Browse files
authored
Merge branch 'main' into hannahkm/continue-on-error-multios
2 parents 3c30cea + d642159 commit a0c21bc

File tree

12 files changed

+186
-37
lines changed

12 files changed

+186
-37
lines changed

.github/workflows/service-extensions-publish.yml

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ on:
77
workflow_dispatch:
88
inputs:
99
tag_name:
10-
description: 'Docker image tag to use for the package'
11-
required: true
12-
default: 'dev'
10+
description: 'Docker image tag to use for the package (default to selected branch name)'
11+
required: false
1312
commit_sha:
14-
description: 'Commit SHA to checkout'
15-
required: true
13+
description: 'Commit SHA to checkout (default to latest commit on selected branch)'
14+
required: false
1615
set_as_latest:
1716
description: 'Set the tag as latest'
1817
required: false
@@ -23,9 +22,8 @@ permissions:
2322
packages: write
2423

2524
env:
26-
TAG_NAME: ${{ github.ref_name || github.event.inputs.tag_name }}
27-
REF_NAME: ${{ github.ref || github.event.inputs.commit_sha }}
28-
COMMIT_SHA: ${{ github.sha || github.event.inputs.commit_sha }}
25+
TAG_NAME: ${{ github.event.inputs.tag_name || github.ref_name }}
26+
COMMIT_SHA: ${{ github.event.inputs.commit_sha || github.sha }}
2927
PUSH_LATEST: ${{ github.event.inputs.set_as_latest || 'true' }}
3028
REGISTRY_IMAGE: ghcr.io/datadog/dd-trace-go/service-extensions-callout
3129

@@ -45,7 +43,7 @@ jobs:
4543
- name: Checkout
4644
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
4745
with:
48-
ref: ${{ env.REF_NAME }}
46+
ref: ${{ env.COMMIT_SHA }}
4947

5048
- name: Install Docker (only arm64)
5149
if: matrix.platform == 'linux/arm64'
@@ -119,9 +117,13 @@ jobs:
119117
id: tags
120118
run: |
121119
tagname=${TAG_NAME//\//-} # remove slashes from tag name
122-
echo "tags=-t ghcr.io/datadog/dd-trace-go/service-extensions-callout:${tagname} \
123-
-t ghcr.io/datadog/dd-trace-go/service-extensions-callout:${{ env.COMMIT_SHA }} \
124-
${{ env.PUSH_LATEST == 'true' && '-t ghcr.io/datadog/dd-trace-go/service-extensions-callout:latest' }}" >> $GITHUB_OUTPUT
120+
tags="tags=-t ghcr.io/datadog/dd-trace-go/service-extensions-callout:${tagname} \
121+
-t ghcr.io/datadog/dd-trace-go/service-extensions-callout:${{ env.COMMIT_SHA }}"
122+
if [ "${PUSH_LATEST}" == "true" ]; then
123+
tags="$tags -t ghcr.io/datadog/dd-trace-go/service-extensions-callout:latest"
124+
fi
125+
126+
echo $tags >> $GITHUB_OUTPUT
125127
126128
- name: Create manifest list and push
127129
working-directory: /tmp/digests

contrib/envoyproxy/go-control-plane/envoy_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
v3 "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
2424
"github.com/stretchr/testify/require"
2525
"google.golang.org/grpc"
26+
"google.golang.org/grpc/metadata"
2627
)
2728

2829
func TestAppSec(t *testing.T) {
@@ -273,6 +274,45 @@ func TestGeneratedSpan(t *testing.T) {
273274
require.Equal(t, "server", span.Tag("span.kind"))
274275
require.Equal(t, "Mistake Not...", span.Tag("http.useragent"))
275276
})
277+
278+
t.Run("span-with-injected-context", func(t *testing.T) {
279+
client, mt, cleanup := setup()
280+
defer cleanup()
281+
282+
ctx := context.Background()
283+
284+
// add metadata to the context
285+
ctx = metadata.AppendToOutgoingContext(ctx,
286+
"x-datadog-trace-id", "12345",
287+
"x-datadog-parent-id", "67890",
288+
)
289+
290+
stream, err := client.Process(ctx)
291+
require.NoError(t, err)
292+
293+
end2EndStreamRequest(t, stream, "/../../../resource-span/.?id=test", "GET", map[string]string{"user-agent": "Mistake Not...", "test-key": "test-value"}, map[string]string{"response-test-key": "response-test-value"}, false)
294+
295+
err = stream.CloseSend()
296+
require.NoError(t, err)
297+
stream.Recv() // to flush the spans
298+
299+
finished := mt.FinishedSpans()
300+
require.Len(t, finished, 1)
301+
302+
// Check for tags
303+
span := finished[0]
304+
require.Equal(t, "http.request", span.OperationName())
305+
require.Equal(t, "https://datadoghq.com/../../../resource-span/.?id=test", span.Tag("http.url"))
306+
require.Equal(t, "GET", span.Tag("http.method"))
307+
require.Equal(t, "datadoghq.com", span.Tag("http.host"))
308+
require.Equal(t, "GET /resource-span", span.Tag("resource.name"))
309+
require.Equal(t, "server", span.Tag("span.kind"))
310+
require.Equal(t, "Mistake Not...", span.Tag("http.useragent"))
311+
312+
// Check for trace context
313+
require.Equal(t, uint64(12345), span.Context().TraceID())
314+
require.Equal(t, uint64(67890), span.ParentID())
315+
})
276316
}
277317

278318
func TestXForwardedForHeaderClientIp(t *testing.T) {

contrib/envoyproxy/go-control-plane/fakehttp.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,28 @@ func splitPseudoHeaders(receivedHeaders []*corev3.HeaderValue) (headers map[stri
7676
return headers, pseudoHeaders
7777
}
7878

79+
// mergeMetadataHeaders merges the metadata headers of the grpc connection into the http headers of the request
80+
// - Skip pseudo headers and headers that are already set
81+
// - Set headers keys to be canonical
82+
func mergeMetadataHeaders(md metadata.MD, headers http.Header) {
83+
for k, v := range md {
84+
if strings.HasPrefix(k, ":") {
85+
continue
86+
}
87+
88+
// Skip the content-type header of the grpc request
89+
// Note: all envoy set headers are lower-case
90+
if k == "content-type" {
91+
continue
92+
}
93+
94+
k = http.CanonicalHeaderKey(k)
95+
if _, ok := headers[k]; !ok {
96+
headers[k] = v
97+
}
98+
}
99+
}
100+
79101
func createFakeResponseWriter(w http.ResponseWriter, res *extproc.ProcessingRequest_ResponseHeaders) error {
80102
headers, pseudoHeaders := splitPseudoHeaders(res.ResponseHeaders.GetHeaders().GetHeaders())
81103

@@ -103,6 +125,13 @@ func newRequest(ctx context.Context, req *extproc.ProcessingRequest_RequestHeade
103125
return nil, err
104126
}
105127

128+
var remoteAddr string
129+
md, ok := metadata.FromIncomingContext(ctx)
130+
if ok {
131+
mergeMetadataHeaders(md, headers)
132+
remoteAddr = getRemoteAddr(md)
133+
}
134+
106135
parsedURL, err := url.Parse(fmt.Sprintf("%s://%s%s", pseudoHeaders[":scheme"], pseudoHeaders[":authority"], pseudoHeaders[":path"]))
107136
if err != nil {
108137
return nil, fmt.Errorf(
@@ -113,12 +142,6 @@ func newRequest(ctx context.Context, req *extproc.ProcessingRequest_RequestHeade
113142
err)
114143
}
115144

116-
var remoteAddr string
117-
md, ok := metadata.FromIncomingContext(ctx)
118-
if ok {
119-
remoteAddr = getRemoteAddr(md)
120-
}
121-
122145
var tlsState *tls.ConnectionState
123146
if pseudoHeaders[":scheme"] == "https" {
124147
tlsState = &tls.ConnectionState{}

ddtrace/tracer/slog.go

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,16 @@ import (
1313
"gopkg.in/DataDog/dd-trace-go.v1/internal/log"
1414
)
1515

16+
// groupOrAttrs holds either a group name or a list of slog.Attrs.
17+
type groupOrAttrs struct {
18+
group string // group name if non-empty
19+
attrs []slog.Attr // attrs if non-empty
20+
}
21+
1622
// slogHandler implements the slog.Handler interface to dispatch messages to our
1723
// internal logger.
1824
type slogHandler struct {
19-
attrs []string
20-
groups []string
25+
goas []groupOrAttrs
2126
}
2227

2328
func (h slogHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
@@ -30,10 +35,30 @@ func (h slogHandler) Enabled(ctx context.Context, lvl slog.Level) bool {
3035
}
3136

3237
func (h slogHandler) Handle(ctx context.Context, r slog.Record) error {
33-
parts := make([]string, 0, len(h.attrs)+r.NumAttrs())
34-
parts = append(parts, h.attrs...)
38+
goas := h.goas
39+
40+
if r.NumAttrs() == 0 {
41+
// If the record has no Attrs, remove groups at the end of the list; they are empty.
42+
for len(goas) > 0 && goas[len(goas)-1].group != "" {
43+
goas = goas[:len(goas)-1]
44+
}
45+
}
46+
47+
parts := make([]string, 0, len(goas)+r.NumAttrs())
48+
formatGroup := ""
49+
50+
for _, goa := range goas {
51+
if goa.group != "" {
52+
formatGroup += goa.group + "."
53+
} else {
54+
for _, a := range goa.attrs {
55+
parts = append(parts, formatGroup+a.String())
56+
}
57+
}
58+
}
59+
3560
r.Attrs(func(a slog.Attr) bool {
36-
parts = append(parts, formatAttr(a, h.groups))
61+
parts = append(parts, formatGroup+a.String())
3762
return true
3863
})
3964

@@ -51,18 +76,25 @@ func (h slogHandler) Handle(ctx context.Context, r slog.Record) error {
5176
return nil
5277
}
5378

54-
func (h slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
55-
for _, a := range attrs {
56-
h.attrs = append(h.attrs, formatAttr(a, h.groups))
57-
}
79+
func (h slogHandler) withGroupOrAttrs(goa groupOrAttrs) slogHandler {
80+
h.goas = append(h.goas, goa)
5881
return h
5982
}
6083

84+
// WithGroup returns a new Handler whose group consist of
85+
// both the receiver's groups and the arguments.
6186
func (h slogHandler) WithGroup(name string) slog.Handler {
62-
h.groups = append(h.groups, name)
63-
return h
87+
if name == "" {
88+
return h
89+
}
90+
return h.withGroupOrAttrs(groupOrAttrs{group: name})
6491
}
6592

66-
func formatAttr(a slog.Attr, groups []string) string {
67-
return strings.Join(append(groups, a.String()), ".")
93+
// WithAttrs returns a new Handler whose attributes consist of
94+
// both the receiver's attributes and the arguments.
95+
func (h slogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
96+
if len(attrs) == 0 {
97+
return h
98+
}
99+
return h.withGroupOrAttrs(groupOrAttrs{attrs: attrs})
68100
}

ddtrace/tracer/slog_test.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,17 @@ func Test_slogHandler(t *testing.T) {
3737
l.Error("error test", "n", 3)
3838
log.Flush() // needed to get the error log flushed
3939

40+
// Check that chaining works as expected.
41+
l = l.With("baz", "qux")
42+
l = l.WithGroup("c").WithGroup("d")
43+
l.Info("info test", "n", 1)
44+
45+
log.Flush()
46+
4047
// Check that the logs were written correctly.
41-
require.Len(t, rl.Logs(), 3)
48+
require.Len(t, rl.Logs(), 4)
4249
require.Contains(t, rl.Logs()[0], "info test foo=bar a.b.n=1")
4350
require.Contains(t, rl.Logs()[1], "warn test foo=bar a.b.n=2")
4451
require.Contains(t, rl.Logs()[2], "error test foo=bar a.b.n=3")
52+
require.Contains(t, rl.Logs()[3], "info test foo=bar a.b.baz=qux a.b.c.d.n=1")
4553
}

ddtrace/tracer/transport.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ func newHTTPTransport(url string, client *http.Client) *httpTransport {
104104
if eid := internal.EntityID(); eid != "" {
105105
defaultHeaders["Datadog-Entity-ID"] = eid
106106
}
107+
if extEnv := internal.ExternalEnvironment(); extEnv != "" {
108+
defaultHeaders["Datadog-External-Env"] = extEnv
109+
}
107110
return &httpTransport{
108111
traceURL: fmt.Sprintf("%s/v0.4/traces", url),
109112
statsURL: fmt.Sprintf("%s/v0.6/stats", url),

ddtrace/tracer/transport_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,3 +396,30 @@ func TestWithUDS(t *testing.T) {
396396
assert.Len(rt.reqs, 1)
397397
assert.Equal(hits, 2)
398398
}
399+
400+
func TestExternalEnvironment(t *testing.T) {
401+
t.Setenv("DD_EXTERNAL_ENV", "it-false,cn-nginx-webserver,pu-75a2b6d5-3949-4afb-ad0d-92ff0674e759")
402+
assert := assert.New(t)
403+
found := false
404+
srv := httptest.NewServer(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
405+
extEnv := r.Header.Get("Datadog-External-Env")
406+
if extEnv == "" {
407+
return
408+
}
409+
assert.Equal("it-false,cn-nginx-webserver,pu-75a2b6d5-3949-4afb-ad0d-92ff0674e759", extEnv)
410+
found = true
411+
}))
412+
defer srv.Close()
413+
414+
u, err := url.Parse(srv.URL)
415+
assert.NoError(err)
416+
c := &http.Client{}
417+
trc := newTracer(WithAgentTimeout(2), WithAgentAddr(u.Host), WithHTTPClient(c))
418+
defer trc.Stop()
419+
420+
p, err := encode(getTestTrace(1, 1))
421+
assert.NoError(err)
422+
_, err = trc.config.transport.send(p)
423+
assert.NoError(err)
424+
assert.True(found)
425+
}

internal/container_linux.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var (
4848
// containerID is the containerID read at init from /proc/self/cgroup
4949
containerID string
5050

51-
// entityID is the entityID to use for the container. It is the `cid-<containerID>` if the container id available,
51+
// entityID is the entityID to use for the container. It is the `ci-<containerID>` if the container id available,
5252
// otherwise the cgroup node controller's inode prefixed with `in-` or an empty string on incompatible OS.
5353
// We use the memory controller on cgroupv1 and the root cgroup on cgroupv2.
5454
entityID string
@@ -151,7 +151,7 @@ func readEntityID(mountPath, cgroupPath string, isHostCgroupNamespace bool) stri
151151
// First try to emit the containerID if available. It will be retrieved if the container is
152152
// running in the host cgroup namespace, independently of the cgroup version.
153153
if containerID != "" {
154-
return "cid-" + containerID
154+
return "ci-" + containerID
155155
}
156156
// Rely on the inode if we're not running in the host cgroup namespace.
157157
if isHostCgroupNamespace {
@@ -161,7 +161,7 @@ func readEntityID(mountPath, cgroupPath string, isHostCgroupNamespace bool) stri
161161
}
162162

163163
// EntityID attempts to return the container ID or the cgroup node controller's inode if the container ID
164-
// is not available. The cid is prefixed with `cid-` and the inode with `in-`.
164+
// is not available. The cid is prefixed with `ci-` and the inode with `in-`.
165165
func EntityID() string {
166166
return entityID
167167
}

internal/container_linux_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func TestReadEntityIDPrioritizeCID(t *testing.T) {
9393

9494
containerID = "fakeContainerID"
9595
eid := readEntityID("", "", true)
96-
assert.Equal(t, "cid-fakeContainerID", eid)
96+
assert.Equal(t, "ci-fakeContainerID", eid)
9797
}
9898

9999
func TestReadEntityIDFallbackOnInode(t *testing.T) {

internal/container_stub.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ func ContainerID() string {
1313
}
1414

1515
// EntityID attempts to return the container ID or the cgroup v2 node inode if the container ID is not available.
16-
// The cid is prefixed with `cid-` and the inode with `in-`.
16+
// The cid is prefixed with `ci-` and the inode with `in-`.
1717
func EntityID() string {
1818
return ""
1919
}

internal/env.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,8 @@ func BoolVal(val string, def bool) bool {
133133
}
134134
return v
135135
}
136+
137+
// ExternalEnvironment returns the value of the DD_EXTERNAL_ENV environment variable.
138+
func ExternalEnvironment() string {
139+
return os.Getenv("DD_EXTERNAL_ENV")
140+
}

0 commit comments

Comments
 (0)