LCOV - code coverage report
Current view: top level - lib/src/context/propagation - w3c_baggage_propagator.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 96.2 % 53 51
Test Date: 2026-07-11 13:35:19 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.0-1