baggage.js 5.9 KB

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