baggage.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { DEBUG_BUILD } from './debug-build.js';
  2. import { isString } from './is.js';
  3. import { logger } from './logger.js';
  4. const BAGGAGE_HEADER_NAME = 'baggage';
  5. const SENTRY_BAGGAGE_KEY_PREFIX = 'sentry-';
  6. const SENTRY_BAGGAGE_KEY_PREFIX_REGEX = /^sentry-/;
  7. /**
  8. * Max length of a serialized baggage string
  9. *
  10. * https://www.w3.org/TR/baggage/#limits
  11. */
  12. const MAX_BAGGAGE_STRING_LENGTH = 8192;
  13. /**
  14. * Takes a baggage header and turns it into Dynamic Sampling Context, by extracting all the "sentry-" prefixed values
  15. * from it.
  16. *
  17. * @param baggageHeader A very bread definition of a baggage header as it might appear in various frameworks.
  18. * @returns The Dynamic Sampling Context that was found on `baggageHeader`, if there was any, `undefined` otherwise.
  19. */
  20. function baggageHeaderToDynamicSamplingContext(
  21. // Very liberal definition of what any incoming header might look like
  22. baggageHeader,
  23. ) {
  24. if (!isString(baggageHeader) && !Array.isArray(baggageHeader)) {
  25. return undefined;
  26. }
  27. // Intermediary object to store baggage key value pairs of incoming baggage headers on.
  28. // It is later used to read Sentry-DSC-values from.
  29. let baggageObject = {};
  30. if (Array.isArray(baggageHeader)) {
  31. // Combine all baggage headers into one object containing the baggage values so we can later read the Sentry-DSC-values from it
  32. baggageObject = baggageHeader.reduce((acc, curr) => {
  33. const currBaggageObject = baggageHeaderToObject(curr);
  34. for (const key of Object.keys(currBaggageObject)) {
  35. acc[key] = currBaggageObject[key];
  36. }
  37. return acc;
  38. }, {});
  39. } else {
  40. // Return undefined if baggage header is an empty string (technically an empty baggage header is not spec conform but
  41. // this is how we choose to handle it)
  42. if (!baggageHeader) {
  43. return undefined;
  44. }
  45. baggageObject = baggageHeaderToObject(baggageHeader);
  46. }
  47. // Read all "sentry-" prefixed values out of the baggage object and put it onto a dynamic sampling context object.
  48. const dynamicSamplingContext = Object.entries(baggageObject).reduce((acc, [key, value]) => {
  49. if (key.match(SENTRY_BAGGAGE_KEY_PREFIX_REGEX)) {
  50. const nonPrefixedKey = key.slice(SENTRY_BAGGAGE_KEY_PREFIX.length);
  51. acc[nonPrefixedKey] = value;
  52. }
  53. return acc;
  54. }, {});
  55. // Only return a dynamic sampling context object if there are keys in it.
  56. // A keyless object means there were no sentry values on the header, which means that there is no DSC.
  57. if (Object.keys(dynamicSamplingContext).length > 0) {
  58. return dynamicSamplingContext ;
  59. } else {
  60. return undefined;
  61. }
  62. }
  63. /**
  64. * Turns a Dynamic Sampling Object into a baggage header by prefixing all the keys on the object with "sentry-".
  65. *
  66. * @param dynamicSamplingContext The Dynamic Sampling Context to turn into a header. For convenience and compatibility
  67. * with the `getDynamicSamplingContext` method on the Transaction class ,this argument can also be `undefined`. If it is
  68. * `undefined` the function will return `undefined`.
  69. * @returns a baggage header, created from `dynamicSamplingContext`, or `undefined` either if `dynamicSamplingContext`
  70. * was `undefined`, or if `dynamicSamplingContext` didn't contain any values.
  71. */
  72. function dynamicSamplingContextToSentryBaggageHeader(
  73. // this also takes undefined for convenience and bundle size in other places
  74. dynamicSamplingContext,
  75. ) {
  76. if (!dynamicSamplingContext) {
  77. return undefined;
  78. }
  79. // Prefix all DSC keys with "sentry-" and put them into a new object
  80. const sentryPrefixedDSC = Object.entries(dynamicSamplingContext).reduce(
  81. (acc, [dscKey, dscValue]) => {
  82. if (dscValue) {
  83. acc[`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`] = dscValue;
  84. }
  85. return acc;
  86. },
  87. {},
  88. );
  89. return objectToBaggageHeader(sentryPrefixedDSC);
  90. }
  91. /**
  92. * Will parse a baggage header, which is a simple key-value map, into a flat object.
  93. *
  94. * @param baggageHeader The baggage header to parse.
  95. * @returns a flat object containing all the key-value pairs from `baggageHeader`.
  96. */
  97. function baggageHeaderToObject(baggageHeader) {
  98. return baggageHeader
  99. .split(',')
  100. .map(baggageEntry => baggageEntry.split('=').map(keyOrValue => decodeURIComponent(keyOrValue.trim())))
  101. .reduce((acc, [key, value]) => {
  102. acc[key] = value;
  103. return acc;
  104. }, {});
  105. }
  106. /**
  107. * Turns a flat object (key-value pairs) into a baggage header, which is also just key-value pairs.
  108. *
  109. * @param object The object to turn into a baggage header.
  110. * @returns a baggage header string, or `undefined` if the object didn't have any values, since an empty baggage header
  111. * is not spec compliant.
  112. */
  113. function objectToBaggageHeader(object) {
  114. if (Object.keys(object).length === 0) {
  115. // An empty baggage header is not spec compliant: We return undefined.
  116. return undefined;
  117. }
  118. return Object.entries(object).reduce((baggageHeader, [objectKey, objectValue], currentIndex) => {
  119. const baggageEntry = `${encodeURIComponent(objectKey)}=${encodeURIComponent(objectValue)}`;
  120. const newBaggageHeader = currentIndex === 0 ? baggageEntry : `${baggageHeader},${baggageEntry}`;
  121. if (newBaggageHeader.length > MAX_BAGGAGE_STRING_LENGTH) {
  122. DEBUG_BUILD &&
  123. logger.warn(
  124. `Not adding key: ${objectKey} with val: ${objectValue} to baggage header due to exceeding baggage size limits.`,
  125. );
  126. return baggageHeader;
  127. } else {
  128. return newBaggageHeader;
  129. }
  130. }, '');
  131. }
  132. export { BAGGAGE_HEADER_NAME, MAX_BAGGAGE_STRING_LENGTH, SENTRY_BAGGAGE_KEY_PREFIX, SENTRY_BAGGAGE_KEY_PREFIX_REGEX, baggageHeaderToDynamicSamplingContext, dynamicSamplingContextToSentryBaggageHeader };
  133. //# sourceMappingURL=baggage.js.map