Line data Source code
1 : // Copyright The OpenTelemetry Authors
2 : // SPDX-License-Identifier: Apache-2.0
3 :
4 : import 'package:dartastic_opentelemetry_api/dartastic_opentelemetry_api.dart';
5 :
6 : import '../../otel.dart';
7 :
8 : /// Implementation of the W3C Baggage specification for context propagation.
9 : ///
10 : /// This propagator handles the extraction and injection of baggage information
11 : /// following the W3C Baggage specification as defined at:
12 : /// https://www.w3.org/TR/baggage/
13 : ///
14 : /// Baggage allows for propagating key-value pairs alongside the trace context
15 : /// across service boundaries. This enables the correlation of related telemetry
16 : /// using application-specific or domain-specific properties.
17 : class W3CBaggagePropagator
18 : implements TextMapPropagator<Map<String, String>, String> {
19 : /// The standard header name for W3C baggage as defined in the specification
20 : static const _baggageHeader = 'baggage';
21 :
22 : /// Extracts baggage information from the carrier and updates the context.
23 : ///
24 : /// This method parses the W3C baggage header and creates a new baggage
25 : /// context to return as part of the updated Context.
26 : ///
27 : /// @param context The current context
28 : /// @param carrier The carrier containing the baggage header
29 : /// @param getter The getter used to extract values from the carrier
30 : /// @return A new Context with the extracted baggage
31 1 : @override
32 : Context extract(
33 : Context context,
34 : Map<String, String> carrier,
35 : TextMapGetter<String> getter,
36 : ) {
37 1 : final value = getter.get(_baggageHeader);
38 2 : OTelLog.debug('Extracting baggage: $value');
39 1 : if (value == null || value.isEmpty) {
40 : // Return context with empty baggage instead of original context
41 0 : return OTel.context();
42 : }
43 :
44 1 : final entries = <String, BaggageEntry>{};
45 1 : final pairs = value.split(',');
46 2 : for (final pair in pairs) {
47 1 : final trimmedPair = pair.trim();
48 1 : if (trimmedPair.isEmpty) continue;
49 :
50 1 : final keyValue = trimmedPair.split('=');
51 2 : if (keyValue.length != 2) continue;
52 :
53 3 : final key = _decodeComponent(keyValue[0].trim());
54 1 : if (key.isEmpty) continue;
55 :
56 2 : final valueAndMetadata = keyValue[1].split(';');
57 3 : final value = _decodeComponent(valueAndMetadata[0].trim());
58 : String? metadata;
59 2 : if (valueAndMetadata.length > 1) {
60 3 : metadata = valueAndMetadata.sublist(1).join(';').trim();
61 : }
62 :
63 2 : entries[key] = OTel.baggageEntry(value, metadata);
64 : }
65 :
66 1 : final baggage = OTel.baggage(entries);
67 1 : return context.withBaggage(baggage);
68 : }
69 :
70 : /// Injects baggage from the context into the carrier.
71 : ///
72 : /// This method serializes the baggage from the context into the
73 : /// W3C baggage header format and adds it to the carrier.
74 : ///
75 : /// @param context The context containing baggage to be injected
76 : /// @param carrier The carrier to inject the baggage header into
77 : /// @param setter The setter used to add values to the carrier
78 1 : @override
79 : void inject(
80 : Context context,
81 : Map<String, String> carrier,
82 : TextMapSetter<String> setter,
83 : ) {
84 1 : if (OTelLog.isDebug()) {
85 2 : OTelLog.debug('Injecting baggage. Context: $context');
86 : }
87 1 : final contextBaggage = context.baggage;
88 : if (contextBaggage != null) {
89 1 : if (OTelLog.isDebug()) {
90 1 : OTelLog.debug(
91 2 : 'Context baggage: $contextBaggage (${contextBaggage.runtimeType})',
92 : );
93 : }
94 :
95 : final baggage = contextBaggage;
96 1 : final entries = baggage.getAllEntries();
97 3 : if (OTelLog.isDebug()) OTelLog.debug('Baggage entries: $entries');
98 :
99 1 : if (entries.isEmpty) {
100 2 : if (OTelLog.isDebug()) OTelLog.debug('Empty baggage entries');
101 : return;
102 : }
103 :
104 3 : final serializedEntries = entries.entries.map((entry) {
105 2 : final key = _encodeComponent(entry.key);
106 3 : final value = _encodeComponent(entry.value.value);
107 2 : final metadata = entry.value.metadata;
108 1 : if (OTelLog.isDebug()) {
109 1 : OTelLog.debug(
110 1 : 'Processing entry - Key: $key, Value: $value, Metadata: $metadata',
111 : );
112 : }
113 1 : if (metadata != null && metadata.isNotEmpty) {
114 1 : return '$key=$value;$metadata';
115 : }
116 1 : return '$key=$value';
117 1 : }).join(',');
118 :
119 1 : if (OTelLog.isDebug()) {
120 2 : OTelLog.debug('Setting baggage header to: $serializedEntries');
121 : }
122 1 : if (serializedEntries.isNotEmpty) {
123 1 : setter.set(_baggageHeader, serializedEntries);
124 : }
125 : }
126 : }
127 :
128 : /// Returns the list of propagation fields used by this propagator.
129 : ///
130 : /// @return A list containing the baggage header name
131 0 : @override
132 : List<String> fields() => const [_baggageHeader];
133 :
134 : /// Encodes a component for use in the baggage header.
135 : ///
136 : /// @param value The value to encode
137 : /// @return The encoded value
138 1 : String _encodeComponent(String value) {
139 1 : return Uri.encodeComponent(
140 : value,
141 2 : ).replaceAll('%20', '+').replaceAll('*', '%2A');
142 : }
143 :
144 : /// Decodes a component from the baggage header.
145 : ///
146 : /// @param value The value to decode
147 : /// @return The decoded value
148 1 : String _decodeComponent(String value) {
149 2 : return Uri.decodeComponent(value.replaceAll('+', '%20'));
150 : }
151 : }
|