serializers.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. import logging
  2. from rest_framework import serializers
  3. from events.serializers import SentrySDKEventSerializer
  4. from glitchtip.serializers import FlexibleDateTimeField
  5. from .models import Span, TransactionEvent, TransactionGroup
  6. logger = logging.getLogger(__name__)
  7. class TransactionGroupSerializer(serializers.ModelSerializer):
  8. avgDuration = serializers.DurationField(source="avg_duration", read_only=True)
  9. transactionCount = serializers.IntegerField(
  10. source="transaction_count", read_only=True
  11. )
  12. class Meta:
  13. model = TransactionGroup
  14. fields = [
  15. "id",
  16. "transaction",
  17. "project",
  18. "op",
  19. "method",
  20. "avgDuration",
  21. "transactionCount",
  22. ]
  23. class SpanSerializer(serializers.ModelSerializer):
  24. spanId = serializers.CharField(source="span_id", read_only=True)
  25. parentSpanId = serializers.CharField(source="parent_span_id", read_only=True)
  26. startTimestamp = serializers.DateTimeField(source="start_timestamp", read_only=True)
  27. start_timestamp = FlexibleDateTimeField(write_only=True)
  28. timestamp = FlexibleDateTimeField(write_only=True)
  29. description = serializers.CharField()
  30. class Meta:
  31. model = Span
  32. fields = [
  33. "spanId",
  34. "span_id",
  35. "parent_span_id",
  36. "parentSpanId",
  37. "op",
  38. "description",
  39. "startTimestamp",
  40. "start_timestamp",
  41. "timestamp",
  42. "tags",
  43. "data",
  44. ]
  45. extra_kwargs = {
  46. "start_timestamp": {"write_only": True},
  47. "span_id": {"write_only": True},
  48. "parent_span_id": {"write_only": True},
  49. }
  50. def to_internal_value(self, data):
  51. # Coerce tags to strings
  52. # Must be done here to avoid failing child CharField validation
  53. if tags := data.get("tags"):
  54. data["tags"] = {key: str(value) for key, value in tags.items()}
  55. return super().to_internal_value(data)
  56. def validate_description(self, value):
  57. # No documented max length here, so we truncate
  58. max_length = self.Meta.model._meta.get_field("description").max_length
  59. if value and len(value) > max_length:
  60. logger.warning("Span description truncation %s", value)
  61. return value[:max_length]
  62. return value
  63. class TransactionEventSerializer(SentrySDKEventSerializer):
  64. type = serializers.CharField(required=False)
  65. contexts = serializers.JSONField()
  66. measurements = serializers.JSONField(required=False)
  67. spans = serializers.ListField(
  68. child=SpanSerializer(), required=False, allow_empty=True
  69. )
  70. start_timestamp = FlexibleDateTimeField()
  71. timestamp = FlexibleDateTimeField()
  72. transaction = serializers.CharField()
  73. def create(self, validated_data):
  74. data = validated_data
  75. contexts = data["contexts"]
  76. project = self.context.get("project")
  77. trace_id = contexts["trace"]["trace_id"]
  78. tags = []
  79. if environment := data.get("environment"):
  80. environment = self.get_environment(data["environment"], project)
  81. tags.append(("environment", environment.name))
  82. if release := data.get("release"):
  83. release = self.get_release(release, project)
  84. tags.append(("release", release.version))
  85. defaults = {}
  86. defaults["tags"] = {tag[0]: [tag[1]] for tag in tags}
  87. group, group_created = TransactionGroup.objects.get_or_create(
  88. project=self.context.get("project"),
  89. transaction=data["transaction"],
  90. op=contexts["trace"]["op"],
  91. method=data.get("request", {}).get("method"),
  92. defaults=defaults,
  93. )
  94. # Merge tags, only save if necessary
  95. update_group = False
  96. if not group_created:
  97. for tag in tags:
  98. if tag[0] not in group.tags:
  99. group.tags[tag[0]] = tag[1]
  100. update_group = True
  101. elif tag[1] not in group.tags[tag[0]]:
  102. group.tags[tag[0]].append(tag[1])
  103. update_group = True
  104. if update_group:
  105. group.save(update_fields=["tags"])
  106. transaction = TransactionEvent.objects.create(
  107. group=group,
  108. data={
  109. "request": data.get("request"),
  110. "sdk": data.get("sdk"),
  111. "platform": data.get("platform"),
  112. },
  113. trace_id=trace_id,
  114. event_id=data["event_id"],
  115. timestamp=data["timestamp"],
  116. start_timestamp=data["start_timestamp"],
  117. duration=data["timestamp"] - data["start_timestamp"],
  118. tags={tag[0]: tag[1] for tag in tags},
  119. )
  120. first_span = SpanSerializer(
  121. data=contexts["trace"]
  122. | {
  123. "start_timestamp": data["start_timestamp"],
  124. "timestamp": data["timestamp"],
  125. }
  126. )
  127. is_valid = first_span.is_valid()
  128. if is_valid:
  129. spans = data.get("spans", []) + [first_span.validated_data]
  130. else:
  131. spans = data.get("spans")
  132. if spans:
  133. Span.objects.bulk_create(
  134. [
  135. Span(
  136. transaction=transaction,
  137. **span,
  138. )
  139. for span in spans
  140. ]
  141. )
  142. return transaction
  143. class TransactionSerializer(serializers.ModelSerializer):
  144. eventId = serializers.UUIDField(source="pk")
  145. startTimestamp = serializers.DateTimeField(source="start_timestamp")
  146. transaction = serializers.SerializerMethodField()
  147. op = serializers.SerializerMethodField()
  148. method = serializers.SerializerMethodField()
  149. class Meta:
  150. model = TransactionEvent
  151. fields = (
  152. "eventId",
  153. "timestamp",
  154. "startTimestamp",
  155. "transaction",
  156. "op",
  157. "method",
  158. )
  159. def get_transaction(self, obj):
  160. return obj.group.transaction
  161. def get_op(self, obj):
  162. return obj.group.op
  163. def get_method(self, obj):
  164. return obj.group.transaction
  165. class TransactionDetailSerializer(TransactionSerializer):
  166. spans = SpanSerializer(source="span_set", many=True)
  167. class Meta(TransactionSerializer.Meta):
  168. fields = TransactionSerializer.Meta.fields + ("spans",)