LCOV - code coverage report
Current view: top level - src/context/propagation - w3c_baggage_propagator.dart (source / functions) Coverage Total Hit
Test: lcov.info Lines: 96.3 % 54 52
Test Date: 2025-11-15 13:23:01 Functions: - 0 0

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

Generated by: LCOV version 2.0-1