Skip to content

Commit 846c2ba

Browse files
sl0thentr0pysolnic
andauthored
Unify Logs and Metrics implementations (#2826)
* Unify Logs and Metrics implementations * Make `LogEvent` go through `scope.apply_to_telemetry` * Populate default and user attributes in `scope.apply_to_telemetry` * Simplify LogEvent to be more simpler with serialization logic, remove all unnecessary fields * Reduce hash allocations * Update sentry-ruby/lib/sentry/log_event.rb Co-authored-by: Peter Solnica <[email protected]> * Fix transform_values * Optimize scope attribute setting --------- Co-authored-by: Peter Solnica <[email protected]>
1 parent 43bb257 commit 846c2ba

File tree

12 files changed

+208
-315
lines changed

12 files changed

+208
-315
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
```
4444
- Prevent SDK crash when SDK logging fails ([#2817](https://github.com/getsentry/sentry-ruby/pull/2817))
4545

46+
### Internal
47+
48+
- Unify Logs and Metrics implementations ([#2826](https://github.com/getsentry/sentry-ruby/pull/2826))
49+
4650
## 6.2.0
4751

4852
### Features

sentry-ruby/lib/sentry/client.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ def capture_event(event, scope, hint = {})
107107
# @return [LogEvent]
108108
def buffer_log_event(event, scope)
109109
return unless event.is_a?(LogEvent)
110-
@log_event_buffer.add_event(scope.apply_to_event(event))
110+
@log_event_buffer.add_event(scope.apply_to_telemetry(event))
111111
event
112112
end
113113

sentry-ruby/lib/sentry/log_event.rb

Lines changed: 22 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,52 @@
11
# frozen_string_literal: true
22

3-
require "json"
3+
require "sentry/utils/telemetry_attributes"
44

55
module Sentry
66
# Event type that represents a log entry with its attributes
77
#
88
# @see https://develop.sentry.dev/sdk/telemetry/logs/#log-envelope-item-payload
99
class LogEvent
10+
include Sentry::Utils::TelemetryAttributes
11+
1012
TYPE = "log"
1113

1214
DEFAULT_PARAMETERS = [].freeze
13-
DEFAULT_ATTRIBUTES = {}.freeze
14-
15-
SERIALIZEABLE_ATTRIBUTES = %i[
16-
level
17-
body
18-
timestamp
19-
environment
20-
release
21-
server_name
22-
trace_id
23-
attributes
24-
contexts
25-
]
26-
27-
SENTRY_ATTRIBUTES = {
28-
"sentry.trace.parent_span_id" => :parent_span_id,
29-
"sentry.environment" => :environment,
30-
"sentry.release" => :release,
31-
"sentry.address" => :server_name,
32-
"sentry.sdk.name" => :sdk_name,
33-
"sentry.sdk.version" => :sdk_version,
34-
"sentry.message.template" => :template,
35-
"sentry.origin" => :origin
36-
}
3715

3816
PARAMETER_PREFIX = "sentry.message.parameter"
3917

40-
USER_ATTRIBUTES = {
41-
"user.id" => :user_id,
42-
"user.name" => :user_username,
43-
"user.email" => :user_email
44-
}
45-
4618
LEVELS = %i[trace debug info warn error fatal].freeze
4719

48-
attr_accessor :level, :body, :template, :attributes, :user, :origin
49-
50-
attr_reader :configuration, *(SERIALIZEABLE_ATTRIBUTES - %i[level body attributes])
51-
52-
SERIALIZERS = %i[
53-
attributes
54-
body
55-
level
56-
parent_span_id
57-
sdk_name
58-
sdk_version
59-
template
60-
timestamp
61-
trace_id
62-
user_id
63-
user_username
64-
user_email
65-
].map { |name| [name, :"serialize_#{name}"] }.to_h
20+
attr_accessor :level, :body, :template, :attributes, :origin, :trace_id, :span_id
21+
attr_reader :timestamp
6622

6723
TOKEN_REGEXP = /%\{(\w+)\}/
6824

69-
def initialize(configuration: Sentry.configuration, **options)
70-
@configuration = configuration
25+
def initialize(**options)
7126
@type = TYPE
72-
@server_name = configuration.server_name
73-
@environment = configuration.environment
74-
@release = configuration.release
7527
@timestamp = Sentry.utc_now
7628
@level = options.fetch(:level)
7729
@body = options[:body]
7830
@template = @body if is_template?
79-
@attributes = options[:attributes] || DEFAULT_ATTRIBUTES
80-
@user = options[:user] || {}
31+
@attributes = options[:attributes] || {}
8132
@origin = options[:origin]
82-
@contexts = {}
33+
@trace_id = nil
34+
@span_id = nil
8335
end
8436

8537
def to_h
86-
SERIALIZEABLE_ATTRIBUTES.each_with_object({}) do |name, memo|
87-
memo[name] = serialize(name)
88-
end
38+
{
39+
level: level.to_s,
40+
timestamp: timestamp.to_f,
41+
trace_id: @trace_id,
42+
span_id: @span_id,
43+
body: serialize_body,
44+
attributes: serialize_attributes
45+
}.compact
8946
end
9047

9148
private
9249

93-
def serialize(name)
94-
serializer = SERIALIZERS[name]
95-
96-
if serializer
97-
__send__(serializer)
98-
else
99-
public_send(name)
100-
end
101-
end
102-
103-
def serialize_level
104-
level.to_s
105-
end
106-
107-
def serialize_sdk_name
108-
Sentry.sdk_meta["name"]
109-
end
110-
111-
def serialize_sdk_version
112-
Sentry.sdk_meta["version"]
113-
end
114-
115-
def serialize_timestamp
116-
timestamp.to_f
117-
end
118-
119-
def serialize_trace_id
120-
contexts.dig(:trace, :trace_id)
121-
end
122-
123-
def serialize_parent_span_id
124-
contexts.dig(:trace, :parent_span_id)
125-
end
126-
12750
def serialize_body
12851
if parameters.empty?
12952
body
@@ -134,61 +57,14 @@ def serialize_body
13457
end
13558
end
13659

137-
def serialize_user_id
138-
user[:id]
139-
end
140-
141-
def serialize_user_username
142-
user[:username]
143-
end
144-
145-
def serialize_user_email
146-
user[:email]
147-
end
148-
149-
def serialize_template
150-
template if has_parameters?
151-
end
152-
15360
def serialize_attributes
154-
hash = {}
155-
156-
attributes.each do |key, value|
157-
hash[key] = attribute_hash(value)
158-
end
159-
160-
SENTRY_ATTRIBUTES.each do |key, name|
161-
if (value = serialize(name))
162-
hash[key] = attribute_hash(value)
163-
end
164-
end
165-
166-
USER_ATTRIBUTES.each do |key, name|
167-
if (value = serialize(name))
168-
hash[key] = value
169-
end
170-
end
171-
172-
hash
61+
populate_sentry_attributes!
62+
@attributes.transform_values! { |v| attribute_hash(v) }
17363
end
17464

175-
def attribute_hash(value)
176-
case value
177-
when String
178-
{ value: value, type: "string" }
179-
when TrueClass, FalseClass
180-
{ value: value, type: "boolean" }
181-
when Integer
182-
{ value: value, type: "integer" }
183-
when Float
184-
{ value: value, type: "double" }
185-
else
186-
begin
187-
{ value: JSON.generate(value), type: "string" }
188-
rescue
189-
{ value: value, type: "string" }
190-
end
191-
end
65+
def populate_sentry_attributes!
66+
@attributes["sentry.origin"] ||= @origin if @origin
67+
@attributes["sentry.message.template"] ||= template if has_parameters?
19268
end
19369

19470
def parameters
Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# frozen_string_literal: true
22

3+
require "sentry/utils/telemetry_attributes"
4+
35
module Sentry
46
class MetricEvent
5-
attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes, :user
6-
attr_writer :trace_id, :span_id, :attributes, :user
7+
include Sentry::Utils::TelemetryAttributes
8+
9+
attr_reader :name, :type, :value, :unit, :timestamp, :trace_id, :span_id, :attributes
10+
attr_writer :trace_id, :span_id, :attributes
711

812
def initialize(
913
name:,
@@ -21,13 +25,9 @@ def initialize(
2125
@timestamp = Sentry.utc_now
2226
@trace_id = nil
2327
@span_id = nil
24-
@user = {}
2528
end
2629

2730
def to_h
28-
populate_default_attributes!
29-
populate_user_attributes!
30-
3131
{
3232
name: @name,
3333
type: @type,
@@ -42,44 +42,8 @@ def to_h
4242

4343
private
4444

45-
def populate_default_attributes!
46-
configuration = Sentry.configuration
47-
return unless configuration
48-
49-
default_attributes = {
50-
"sentry.environment" => configuration.environment,
51-
"sentry.release" => configuration.release,
52-
"sentry.sdk.name" => Sentry.sdk_meta["name"],
53-
"sentry.sdk.version" => Sentry.sdk_meta["version"],
54-
"server.address" => configuration.server_name
55-
}.compact
56-
57-
@attributes = default_attributes.merge(@attributes)
58-
end
59-
60-
def populate_user_attributes!
61-
return unless @user
62-
return unless Sentry.initialized? && Sentry.configuration.send_default_pii
63-
64-
user_attributes = {
65-
"user.id" => @user[:id],
66-
"user.name" => @user[:username],
67-
"user.email" => @user[:email]
68-
}.compact
69-
70-
@attributes = user_attributes.merge(@attributes)
71-
end
72-
7345
def serialize_attributes
74-
@attributes.transform_values do |v|
75-
case v
76-
when Integer then { type: "integer", value: v }
77-
when Float then { type: "double", value: v }
78-
when TrueClass, FalseClass then { type: "boolean", value: v }
79-
when String then { type: "string", value: v }
80-
else { type: "string", value: v.to_s }
81-
end
82-
end
46+
@attributes.transform_values! { |v| attribute_hash(v) }
8347
end
8448
end
8549
end

sentry-ruby/lib/sentry/scope.rb

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def clear
4646
# @param hint [Hash] the hint data that'll be passed to event processors.
4747
# @return [Event]
4848
def apply_to_event(event, hint = nil)
49-
unless event.is_a?(CheckInEvent) || event.is_a?(LogEvent)
49+
unless event.is_a?(CheckInEvent)
5050
event.tags = tags.merge(event.tags)
5151
event.user = user.merge(event.user)
5252
event.extra = extra.merge(event.extra)
@@ -60,10 +60,6 @@ def apply_to_event(event, hint = nil)
6060
event.attachments = attachments
6161
end
6262

63-
if event.is_a?(LogEvent)
64-
event.user = user.merge(event.user)
65-
end
66-
6763
if span
6864
event.contexts[:trace] ||= span.get_trace_context
6965

@@ -92,18 +88,31 @@ def apply_to_event(event, hint = nil)
9288
# A leaner version of apply_to_event that applies to
9389
# lightweight payloads like Logs and Metrics.
9490
#
95-
# Only adds trace_id, span_id and user from the scope.
91+
# Adds trace_id, span_id, user from the scope and default attributes from configuration.
9692
#
97-
# @param telemetry [MetricEvent]
98-
# @return [MetricEvent]
93+
# @param telemetry [MetricEvent, LogEvent] the telemetry event to apply scope context to
94+
# @return [MetricEvent, LogEvent] the telemetry event with scope context applied
9995
def apply_to_telemetry(telemetry)
10096
# TODO-neel when new scope set_attribute api is added: add them here
101-
telemetry.user = user.merge(telemetry.user)
102-
10397
trace_context = span ? span.get_trace_context : propagation_context.get_trace_context
10498
telemetry.trace_id = trace_context[:trace_id]
10599
telemetry.span_id = trace_context[:span_id]
106100

101+
configuration = Sentry.configuration
102+
return telemetry unless configuration
103+
104+
telemetry.attributes["sentry.sdk.name"] ||= Sentry.sdk_meta["name"]
105+
telemetry.attributes["sentry.sdk.version"] ||= Sentry.sdk_meta["version"]
106+
telemetry.attributes["sentry.environment"] ||= configuration.environment if configuration.environment
107+
telemetry.attributes["sentry.release"] ||= configuration.release if configuration.release
108+
telemetry.attributes["server.address"] ||= configuration.server_name if configuration.server_name
109+
110+
if configuration.send_default_pii && !user.empty?
111+
telemetry.attributes["user.id"] ||= user[:id] if user[:id]
112+
telemetry.attributes["user.name"] ||= user[:username] if user[:username]
113+
telemetry.attributes["user.email"] ||= user[:email] if user[:email]
114+
end
115+
107116
telemetry
108117
end
109118

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
require "json"
4+
5+
module Sentry
6+
module Utils
7+
module TelemetryAttributes
8+
private
9+
10+
def attribute_hash(value)
11+
case value
12+
when String
13+
{ value: value, type: "string" }
14+
when TrueClass, FalseClass
15+
{ value: value, type: "boolean" }
16+
when Integer
17+
{ value: value, type: "integer" }
18+
when Float
19+
{ value: value, type: "double" }
20+
else
21+
begin
22+
{ value: JSON.generate(value), type: "string" }
23+
rescue
24+
{ value: value, type: "string" }
25+
end
26+
end
27+
end
28+
end
29+
end
30+
end

0 commit comments

Comments
 (0)