Browse code

api, daemon, libnet: add a 'trigger' baggage member

Add an OTel span processor copying the 'trigger' baggage member
propagated through contexts to all children spans. It's used to identify
what triggered a trace / span (API call, libnet init, etc...)

All code paths that call libnet's `NewNetwork` set this baggage member
with a unique value.

For instance, this can be used to distinguish bridge's `createNetwork`
spans triggered by daemon / libnet initialization from custom network
creation triggerd by an API call.

Two util functions are added to wrap `baggage.New` and
`baggage.NewMemberRaw` to make it easier to deal with baggage and
members by panicking on error. These should not be used with dynamic
values.

Signed-off-by: Albin Kerouanton <albinker@gmail.com>

Albin Kerouanton authored on 2025/04/08 16:47:05
Showing 13 changed files
... ...
@@ -11,8 +11,10 @@ import (
11 11
 	"github.com/docker/docker/api/server/router"
12 12
 	"github.com/docker/docker/api/types"
13 13
 	"github.com/docker/docker/dockerversion"
14
+	"github.com/docker/docker/internal/otelutil"
14 15
 	"github.com/gorilla/mux"
15 16
 	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
17
+	"go.opentelemetry.io/otel/baggage"
16 18
 )
17 19
 
18 20
 // versionMatcher defines a variable matcher to be parsed by the router
... ...
@@ -42,7 +44,10 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc, operation string) ht
42 42
 
43 43
 		// use intermediate variable to prevent "should not use basic type
44 44
 		// string as key in context.WithValue" golint errors
45
-		ctx := context.WithValue(r.Context(), dockerversion.UAStringKey{}, r.Header.Get("User-Agent"))
45
+		ua := r.Header.Get("User-Agent")
46
+		ctx := baggage.ContextWithBaggage(context.WithValue(r.Context(), dockerversion.UAStringKey{}, ua), otelutil.MustNewBaggage(
47
+			otelutil.MustNewMemberRaw(otelutil.TriggerKey, "api"),
48
+		))
46 49
 
47 50
 		r = r.WithContext(ctx)
48 51
 		handlerFunc := s.handlerWithGlobalMiddlewares(handler)
... ...
@@ -31,6 +31,7 @@ import (
31 31
 	"github.com/docker/docker/daemon/initlayer"
32 32
 	"github.com/docker/docker/errdefs"
33 33
 	"github.com/docker/docker/internal/nlwrap"
34
+	"github.com/docker/docker/internal/otelutil"
34 35
 	"github.com/docker/docker/internal/usergroup"
35 36
 	"github.com/docker/docker/libcontainerd/remote"
36 37
 	"github.com/docker/docker/libnetwork"
... ...
@@ -51,6 +52,7 @@ import (
51 51
 	"github.com/opencontainers/selinux/go-selinux/label"
52 52
 	"github.com/pkg/errors"
53 53
 	"github.com/vishvananda/netlink"
54
+	"go.opentelemetry.io/otel/baggage"
54 55
 	"golang.org/x/sys/unix"
55 56
 )
56 57
 
... ...
@@ -849,7 +851,9 @@ func (daemon *Daemon) initNetworkController(cfg *config.Config, activeSandboxes
849 849
 		return err
850 850
 	}
851 851
 
852
-	ctx := context.TODO()
852
+	ctx := baggage.ContextWithBaggage(context.TODO(), otelutil.MustNewBaggage(
853
+		otelutil.MustNewMemberRaw(otelutil.TriggerKey, "daemon.initNetworkController"),
854
+	))
853 855
 	daemon.netController, err = libnetwork.New(ctx, netOptions...)
854 856
 	if err != nil {
855 857
 		return fmt.Errorf("error obtaining controller instance: %v", err)
... ...
@@ -21,6 +21,7 @@ import (
21 21
 	"github.com/docker/docker/daemon/config"
22 22
 	"github.com/docker/docker/daemon/network"
23 23
 	"github.com/docker/docker/errdefs"
24
+	"github.com/docker/docker/internal/otelutil"
24 25
 	"github.com/docker/docker/libnetwork"
25 26
 	lncluster "github.com/docker/docker/libnetwork/cluster"
26 27
 	"github.com/docker/docker/libnetwork/driverapi"
... ...
@@ -32,6 +33,7 @@ import (
32 32
 	"github.com/docker/docker/opts"
33 33
 	"github.com/docker/docker/pkg/plugingetter"
34 34
 	"github.com/docker/go-connections/nat"
35
+	"go.opentelemetry.io/otel/baggage"
35 36
 )
36 37
 
37 38
 // PredefinedNetworkError is returned when user tries to create predefined network that already exists.
... ...
@@ -203,11 +205,14 @@ func (daemon *Daemon) setupIngress(cfg *config.Config, create *clustertypes.Netw
203 203
 		daemon.releaseIngress(staleID)
204 204
 	}
205 205
 
206
-	if _, err := daemon.createNetwork(context.TODO(), cfg, create.CreateRequest, create.ID, true); err != nil {
206
+	ctx := baggage.ContextWithBaggage(context.TODO(), otelutil.MustNewBaggage(
207
+		otelutil.MustNewMemberRaw(otelutil.TriggerKey, "daemon.setupIngress"),
208
+	))
209
+	if _, err := daemon.createNetwork(ctx, cfg, create.CreateRequest, create.ID, true); err != nil {
207 210
 		// If it is any other error other than already
208 211
 		// exists error log error and return.
209 212
 		if _, ok := err.(libnetwork.NetworkNameError); !ok {
210
-			log.G(context.TODO()).Errorf("Failed creating ingress network: %v", err)
213
+			log.G(ctx).Errorf("Failed creating ingress network: %v", err)
211 214
 			return
212 215
 		}
213 216
 		// Otherwise continue down the call to create or recreate sandbox.
214 217
new file mode 100644
... ...
@@ -0,0 +1,43 @@
0
+package otelutil
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/containerd/log"
6
+	"go.opentelemetry.io/otel/baggage"
7
+)
8
+
9
+// TriggerKey is the key used for the 'trigger' member in the baggage. It is
10
+// used to know what triggered a code path (e.g. API call, libnet init, etc...)
11
+const TriggerKey = "trigger"
12
+
13
+// MustNewBaggage creates an OTel Baggage containing the provided members. It
14
+// panics if the baggage cannot be created.
15
+//
16
+// DO NOT USE this function with dynamic values.
17
+func MustNewBaggage(members ...baggage.Member) baggage.Baggage {
18
+	b, err := baggage.New(members...)
19
+	if err != nil {
20
+		log.G(context.Background()).WithFields(log.Fields{
21
+			"error":   err,
22
+			"members": members,
23
+		}).Fatal("OTel baggage creation failure")
24
+	}
25
+	return b
26
+}
27
+
28
+// MustNewMemberRaw creates an OTel Baggage member with the provided key and
29
+// value. It panics if the key or value aren't valid UTF-8 strings.
30
+//
31
+// DO NOT USE this function with dynamic key/value.
32
+func MustNewMemberRaw(key, value string) baggage.Member {
33
+	m, err := baggage.NewMemberRaw(key, value)
34
+	if err != nil {
35
+		log.G(context.Background()).WithFields(log.Fields{
36
+			"error": err,
37
+			"key":   key,
38
+			"value": value,
39
+		}).Fatal("OTel baggage member creation failure")
40
+	}
41
+	return m
42
+}
... ...
@@ -5,6 +5,8 @@ import (
5 5
 
6 6
 	"github.com/containerd/log"
7 7
 	"github.com/moby/buildkit/util/tracing/detect"
8
+	"go.opentelemetry.io/contrib/processors/baggagecopy"
9
+	"go.opentelemetry.io/otel/baggage"
8 10
 	"go.opentelemetry.io/otel/sdk/resource"
9 11
 	sdktrace "go.opentelemetry.io/otel/sdk/trace"
10 12
 	"go.opentelemetry.io/otel/trace"
... ...
@@ -31,6 +33,7 @@ func NewTracerProvider(ctx context.Context, allowNoop bool) (trace.TracerProvide
31 31
 		sdktrace.WithResource(resource.Default()),
32 32
 		sdktrace.WithSyncer(detect.Recorder),
33 33
 		sdktrace.WithBatcher(exp),
34
+		sdktrace.WithSpanProcessor(baggagecopy.NewSpanProcessor(func(member baggage.Member) bool { return true })),
34 35
 	)
35 36
 	return tp, tp.Shutdown
36 37
 }
... ...
@@ -5,7 +5,9 @@ import (
5 5
 	"fmt"
6 6
 	"strconv"
7 7
 
8
+	"github.com/docker/docker/internal/otelutil"
8 9
 	"github.com/docker/docker/libnetwork/drivers/bridge"
10
+	"go.opentelemetry.io/otel/baggage"
9 11
 )
10 12
 
11 13
 const libnGWNetwork = "docker_gwbridge"
... ...
@@ -15,7 +17,11 @@ func getPlatformOption() EndpointOption {
15 15
 }
16 16
 
17 17
 func (c *Controller) createGWNetwork() (*Network, error) {
18
-	n, err := c.NewNetwork(context.TODO(), "bridge", libnGWNetwork, "",
18
+	ctx := baggage.ContextWithBaggage(context.TODO(), otelutil.MustNewBaggage(
19
+		otelutil.MustNewMemberRaw(otelutil.TriggerKey, "libnetwork.Controller.createGWNetwork"),
20
+	))
21
+
22
+	n, err := c.NewNetwork(ctx, "bridge", libnGWNetwork, "",
19 23
 		NetworkOptionDriverOpts(map[string]string{
20 24
 			bridge.BridgeName:         libnGWNetwork,
21 25
 			bridge.EnableICC:          strconv.FormatBool(false),
... ...
@@ -9,10 +9,12 @@ import (
9 9
 	"net"
10 10
 
11 11
 	"github.com/containerd/log"
12
+	"github.com/docker/docker/internal/otelutil"
12 13
 	"github.com/docker/docker/libnetwork/datastore"
13 14
 	"github.com/docker/docker/libnetwork/types"
14 15
 	"go.opentelemetry.io/otel"
15 16
 	"go.opentelemetry.io/otel/attribute"
17
+	"go.opentelemetry.io/otel/baggage"
16 18
 	"go.opentelemetry.io/otel/trace"
17 19
 )
18 20
 
... ...
@@ -49,9 +51,12 @@ func (d *driver) populateNetworks() error {
49 49
 		return nil
50 50
 	}
51 51
 
52
+	ctx := baggage.ContextWithBaggage(context.TODO(), otelutil.MustNewBaggage(
53
+		otelutil.MustNewMemberRaw(otelutil.TriggerKey, spanPrefix+".initStore"),
54
+	))
52 55
 	for _, kvo := range kvol {
53 56
 		ncfg := kvo.(*networkConfiguration)
54
-		if err = d.createNetwork(context.TODO(), ncfg); err != nil {
57
+		if err = d.createNetwork(ctx, ncfg); err != nil {
55 58
 			log.G(context.TODO()).Warnf("could not create bridge network for id %s bridge name %s while booting up from persistent state: %v", ncfg.ID, ncfg.BridgeName, err)
56 59
 		}
57 60
 		log.G(context.TODO()).Debugf("Network (%.7s) restored", ncfg.ID)
... ...
@@ -100,6 +100,7 @@ require (
100 100
 	go.etcd.io/bbolt v1.3.11
101 101
 	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0
102 102
 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0
103
+	go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0
103 104
 	go.opentelemetry.io/otel v1.31.0
104 105
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0
105 106
 	go.opentelemetry.io/otel/sdk v1.31.0
... ...
@@ -614,6 +614,8 @@ go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.
614 614
 go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0/go.mod h1:3qi2EEwMgB4xnKgPLqsDP3j9qxnHDZeHsnAxfjQqTko=
615 615
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
616 616
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
617
+go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0 h1:SUsGRzllvPRJK6VKn1S3lsItIoQaLvExUh63cD+J+D8=
618
+go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0/go.mod h1:68LCyaHcLhUf3tciKAAbSFKkr4Pkrt24ei0/xHm0No8=
617 619
 go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
618 620
 go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
619 621
 go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08=
620 622
new file mode 100644
... ...
@@ -0,0 +1,201 @@
0
+                                 Apache License
1
+                           Version 2.0, January 2004
2
+                        http://www.apache.org/licenses/
3
+
4
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
5
+
6
+   1. Definitions.
7
+
8
+      "License" shall mean the terms and conditions for use, reproduction,
9
+      and distribution as defined by Sections 1 through 9 of this document.
10
+
11
+      "Licensor" shall mean the copyright owner or entity authorized by
12
+      the copyright owner that is granting the License.
13
+
14
+      "Legal Entity" shall mean the union of the acting entity and all
15
+      other entities that control, are controlled by, or are under common
16
+      control with that entity. For the purposes of this definition,
17
+      "control" means (i) the power, direct or indirect, to cause the
18
+      direction or management of such entity, whether by contract or
19
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
20
+      outstanding shares, or (iii) beneficial ownership of such entity.
21
+
22
+      "You" (or "Your") shall mean an individual or Legal Entity
23
+      exercising permissions granted by this License.
24
+
25
+      "Source" form shall mean the preferred form for making modifications,
26
+      including but not limited to software source code, documentation
27
+      source, and configuration files.
28
+
29
+      "Object" form shall mean any form resulting from mechanical
30
+      transformation or translation of a Source form, including but
31
+      not limited to compiled object code, generated documentation,
32
+      and conversions to other media types.
33
+
34
+      "Work" shall mean the work of authorship, whether in Source or
35
+      Object form, made available under the License, as indicated by a
36
+      copyright notice that is included in or attached to the work
37
+      (an example is provided in the Appendix below).
38
+
39
+      "Derivative Works" shall mean any work, whether in Source or Object
40
+      form, that is based on (or derived from) the Work and for which the
41
+      editorial revisions, annotations, elaborations, or other modifications
42
+      represent, as a whole, an original work of authorship. For the purposes
43
+      of this License, Derivative Works shall not include works that remain
44
+      separable from, or merely link (or bind by name) to the interfaces of,
45
+      the Work and Derivative Works thereof.
46
+
47
+      "Contribution" shall mean any work of authorship, including
48
+      the original version of the Work and any modifications or additions
49
+      to that Work or Derivative Works thereof, that is intentionally
50
+      submitted to Licensor for inclusion in the Work by the copyright owner
51
+      or by an individual or Legal Entity authorized to submit on behalf of
52
+      the copyright owner. For the purposes of this definition, "submitted"
53
+      means any form of electronic, verbal, or written communication sent
54
+      to the Licensor or its representatives, including but not limited to
55
+      communication on electronic mailing lists, source code control systems,
56
+      and issue tracking systems that are managed by, or on behalf of, the
57
+      Licensor for the purpose of discussing and improving the Work, but
58
+      excluding communication that is conspicuously marked or otherwise
59
+      designated in writing by the copyright owner as "Not a Contribution."
60
+
61
+      "Contributor" shall mean Licensor and any individual or Legal Entity
62
+      on behalf of whom a Contribution has been received by Licensor and
63
+      subsequently incorporated within the Work.
64
+
65
+   2. Grant of Copyright License. Subject to the terms and conditions of
66
+      this License, each Contributor hereby grants to You a perpetual,
67
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
68
+      copyright license to reproduce, prepare Derivative Works of,
69
+      publicly display, publicly perform, sublicense, and distribute the
70
+      Work and such Derivative Works in Source or Object form.
71
+
72
+   3. Grant of Patent License. Subject to the terms and conditions of
73
+      this License, each Contributor hereby grants to You a perpetual,
74
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
75
+      (except as stated in this section) patent license to make, have made,
76
+      use, offer to sell, sell, import, and otherwise transfer the Work,
77
+      where such license applies only to those patent claims licensable
78
+      by such Contributor that are necessarily infringed by their
79
+      Contribution(s) alone or by combination of their Contribution(s)
80
+      with the Work to which such Contribution(s) was submitted. If You
81
+      institute patent litigation against any entity (including a
82
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
83
+      or a Contribution incorporated within the Work constitutes direct
84
+      or contributory patent infringement, then any patent licenses
85
+      granted to You under this License for that Work shall terminate
86
+      as of the date such litigation is filed.
87
+
88
+   4. Redistribution. You may reproduce and distribute copies of the
89
+      Work or Derivative Works thereof in any medium, with or without
90
+      modifications, and in Source or Object form, provided that You
91
+      meet the following conditions:
92
+
93
+      (a) You must give any other recipients of the Work or
94
+          Derivative Works a copy of this License; and
95
+
96
+      (b) You must cause any modified files to carry prominent notices
97
+          stating that You changed the files; and
98
+
99
+      (c) You must retain, in the Source form of any Derivative Works
100
+          that You distribute, all copyright, patent, trademark, and
101
+          attribution notices from the Source form of the Work,
102
+          excluding those notices that do not pertain to any part of
103
+          the Derivative Works; and
104
+
105
+      (d) If the Work includes a "NOTICE" text file as part of its
106
+          distribution, then any Derivative Works that You distribute must
107
+          include a readable copy of the attribution notices contained
108
+          within such NOTICE file, excluding those notices that do not
109
+          pertain to any part of the Derivative Works, in at least one
110
+          of the following places: within a NOTICE text file distributed
111
+          as part of the Derivative Works; within the Source form or
112
+          documentation, if provided along with the Derivative Works; or,
113
+          within a display generated by the Derivative Works, if and
114
+          wherever such third-party notices normally appear. The contents
115
+          of the NOTICE file are for informational purposes only and
116
+          do not modify the License. You may add Your own attribution
117
+          notices within Derivative Works that You distribute, alongside
118
+          or as an addendum to the NOTICE text from the Work, provided
119
+          that such additional attribution notices cannot be construed
120
+          as modifying the License.
121
+
122
+      You may add Your own copyright statement to Your modifications and
123
+      may provide additional or different license terms and conditions
124
+      for use, reproduction, or distribution of Your modifications, or
125
+      for any such Derivative Works as a whole, provided Your use,
126
+      reproduction, and distribution of the Work otherwise complies with
127
+      the conditions stated in this License.
128
+
129
+   5. Submission of Contributions. Unless You explicitly state otherwise,
130
+      any Contribution intentionally submitted for inclusion in the Work
131
+      by You to the Licensor shall be under the terms and conditions of
132
+      this License, without any additional terms or conditions.
133
+      Notwithstanding the above, nothing herein shall supersede or modify
134
+      the terms of any separate license agreement you may have executed
135
+      with Licensor regarding such Contributions.
136
+
137
+   6. Trademarks. This License does not grant permission to use the trade
138
+      names, trademarks, service marks, or product names of the Licensor,
139
+      except as required for reasonable and customary use in describing the
140
+      origin of the Work and reproducing the content of the NOTICE file.
141
+
142
+   7. Disclaimer of Warranty. Unless required by applicable law or
143
+      agreed to in writing, Licensor provides the Work (and each
144
+      Contributor provides its Contributions) on an "AS IS" BASIS,
145
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
146
+      implied, including, without limitation, any warranties or conditions
147
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
148
+      PARTICULAR PURPOSE. You are solely responsible for determining the
149
+      appropriateness of using or redistributing the Work and assume any
150
+      risks associated with Your exercise of permissions under this License.
151
+
152
+   8. Limitation of Liability. In no event and under no legal theory,
153
+      whether in tort (including negligence), contract, or otherwise,
154
+      unless required by applicable law (such as deliberate and grossly
155
+      negligent acts) or agreed to in writing, shall any Contributor be
156
+      liable to You for damages, including any direct, indirect, special,
157
+      incidental, or consequential damages of any character arising as a
158
+      result of this License or out of the use or inability to use the
159
+      Work (including but not limited to damages for loss of goodwill,
160
+      work stoppage, computer failure or malfunction, or any and all
161
+      other commercial damages or losses), even if such Contributor
162
+      has been advised of the possibility of such damages.
163
+
164
+   9. Accepting Warranty or Additional Liability. While redistributing
165
+      the Work or Derivative Works thereof, You may choose to offer,
166
+      and charge a fee for, acceptance of support, warranty, indemnity,
167
+      or other liability obligations and/or rights consistent with this
168
+      License. However, in accepting such obligations, You may act only
169
+      on Your own behalf and on Your sole responsibility, not on behalf
170
+      of any other Contributor, and only if You agree to indemnify,
171
+      defend, and hold each Contributor harmless for any liability
172
+      incurred by, or claims asserted against, such Contributor by reason
173
+      of your accepting any such warranty or additional liability.
174
+
175
+   END OF TERMS AND CONDITIONS
176
+
177
+   APPENDIX: How to apply the Apache License to your work.
178
+
179
+      To apply the Apache License to your work, attach the following
180
+      boilerplate notice, with the fields enclosed by brackets "[]"
181
+      replaced with your own identifying information. (Don't include
182
+      the brackets!)  The text should be enclosed in the appropriate
183
+      comment syntax for the file format. We also recommend that a
184
+      file or class name and description of purpose be included on the
185
+      same "printed page" as the copyright notice for easier
186
+      identification within third-party archives.
187
+
188
+   Copyright [yyyy] [name of copyright owner]
189
+
190
+   Licensed under the Apache License, Version 2.0 (the "License");
191
+   you may not use this file except in compliance with the License.
192
+   You may obtain a copy of the License at
193
+
194
+       http://www.apache.org/licenses/LICENSE-2.0
195
+
196
+   Unless required by applicable law or agreed to in writing, software
197
+   distributed under the License is distributed on an "AS IS" BASIS,
198
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
199
+   See the License for the specific language governing permissions and
200
+   limitations under the License.
0 201
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+// Copyright The OpenTelemetry Authors
1
+// SPDX-License-Identifier: Apache-2.0
2
+
3
+// Package baggagecopy is an OpenTelemetry [Span Processor] that reads key/values
4
+// stored in [Baggage] in the starting span's parent context and adds them as
5
+// attributes to the span.
6
+//
7
+// Keys and values added to Baggage will appear on all subsequent child spans for
8
+// a trace within this service and will be propagated to external services via
9
+// propagation headers.
10
+// If the external services also have a Baggage span processor, the keys and
11
+// values will appear in those child spans as well.
12
+//
13
+// Do not put sensitive information in Baggage.
14
+//
15
+// # Usage
16
+//
17
+// Add the span processor when configuring the tracer provider.
18
+//
19
+// The convenience function [AllowAllBaggageKeys] is provided to
20
+// allow all baggage keys to be copied to the span. Alternatively, you can
21
+// provide a custom baggage key predicate to select which baggage keys you want
22
+// to copy.
23
+//
24
+// [Span Processor]: https://opentelemetry.io/docs/specs/otel/trace/sdk/#span-processor
25
+// [Baggage]: https://opentelemetry.io/docs/specs/otel/api/baggage
26
+package baggagecopy // import "go.opentelemetry.io/contrib/processors/baggagecopy"
0 27
new file mode 100644
... ...
@@ -0,0 +1,63 @@
0
+// Copyright The OpenTelemetry Authors
1
+// SPDX-License-Identifier: Apache-2.0
2
+
3
+package baggagecopy // import "go.opentelemetry.io/contrib/processors/baggagecopy"
4
+
5
+import (
6
+	"context"
7
+
8
+	"go.opentelemetry.io/otel/attribute"
9
+	"go.opentelemetry.io/otel/baggage"
10
+	"go.opentelemetry.io/otel/sdk/trace"
11
+)
12
+
13
+// Filter returns true if the baggage member should be added to a span.
14
+type Filter func(member baggage.Member) bool
15
+
16
+// AllowAllMembers allows all baggage members to be added to a span.
17
+var AllowAllMembers Filter = func(baggage.Member) bool { return true }
18
+
19
+// SpanProcessor is a [trace.SpanProcessor] implementation that adds baggage
20
+// members onto a span as attributes.
21
+type SpanProcessor struct {
22
+	filter Filter
23
+}
24
+
25
+var _ trace.SpanProcessor = (*SpanProcessor)(nil)
26
+
27
+// NewSpanProcessor returns a new [SpanProcessor].
28
+//
29
+// The Baggage span processor duplicates onto a span the attributes found
30
+// in Baggage in the parent context at the moment the span is started.
31
+// The passed filter determines which baggage members are added to the span.
32
+//
33
+// If filter is nil, all baggage members will be added.
34
+func NewSpanProcessor(filter Filter) *SpanProcessor {
35
+	return &SpanProcessor{
36
+		filter: filter,
37
+	}
38
+}
39
+
40
+// OnStart is called when a span is started and adds span attributes for baggage contents.
41
+func (processor SpanProcessor) OnStart(ctx context.Context, span trace.ReadWriteSpan) {
42
+	filter := processor.filter
43
+	if filter == nil {
44
+		filter = AllowAllMembers
45
+	}
46
+
47
+	for _, member := range baggage.FromContext(ctx).Members() {
48
+		if filter(member) {
49
+			span.SetAttributes(attribute.String(member.Key(), member.Value()))
50
+		}
51
+	}
52
+}
53
+
54
+// OnEnd is called when span is finished and is a no-op for this processor.
55
+func (processor SpanProcessor) OnEnd(s trace.ReadOnlySpan) {}
56
+
57
+// Shutdown is called when the SDK shuts down and is a no-op for this processor.
58
+func (processor SpanProcessor) Shutdown(context.Context) error { return nil }
59
+
60
+// ForceFlush exports all ended spans to the configured Exporter that have not yet
61
+// been exported and is a no-op for this processor.
62
+func (processor SpanProcessor) ForceFlush(context.Context) error { return nil }
... ...
@@ -1288,6 +1288,9 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
1288 1288
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/request
1289 1289
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconv
1290 1290
 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp/internal/semconvutil
1291
+# go.opentelemetry.io/contrib/processors/baggagecopy v0.4.0
1292
+## explicit; go 1.22
1293
+go.opentelemetry.io/contrib/processors/baggagecopy
1291 1294
 # go.opentelemetry.io/otel v1.31.0
1292 1295
 ## explicit; go 1.22
1293 1296
 go.opentelemetry.io/otel