Skip to content

Commit a1c2091

Browse files
committed
Add support for message templates
1 parent c12ff5b commit a1c2091

6 files changed

Lines changed: 130 additions & 53 deletions

File tree

sentry-ruby/lib/sentry/client.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,17 @@ def event_from_check_in(
182182
end
183183

184184
# Initializes a LogEvent object with the given message and options
185+
#
186+
# @param message [String] the log message
187+
# @param level [Symbol] the log level (:trace, :debug, :info, :warn, :error, :fatal)
188+
# @param options [Hash] additional options
189+
# @option options [Array] :parameters Array of values to replace template tokens in the message
190+
#
191+
# @return [LogEvent] the created log event
185192
def event_from_log(message, level:, **options)
186193
return unless configuration.sending_allowed?
187194

188-
attributes = options.reject { |k, _| k == :level }
195+
attributes = options.reject { |k, _| k == :level || k == :severity }
189196

190197
LogEvent.new(
191198
level: level,

sentry-ruby/lib/sentry/log_event.rb

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,32 @@ class LogEvent < Event
2121
"sentry.release" => :release,
2222
"sentry.address" => :server_name,
2323
"sentry.sdk.name" => :sdk_name,
24-
"sentry.sdk.version" => :sdk_version
24+
"sentry.sdk.version" => :sdk_version,
25+
"sentry.message.template" => :template
2526
}
2627

2728
LEVELS = %i[trace debug info warn error fatal].freeze
2829

29-
attr_accessor :level, :body, :attributes, :trace_id
30+
attr_accessor :level, :body, :template, :attributes, :trace_id, :parameters
3031

3132
def initialize(configuration: Sentry.configuration, **options)
3233
super(configuration: configuration)
34+
3335
@type = TYPE
3436
@level = options.fetch(:level)
3537
@body = options[:body]
38+
@template = @body
3639
@attributes = options[:attributes] || {}
40+
@parameters = @attributes[:parameters] || []
41+
42+
if has_template_parameters?
43+
@attributes["sentry.message.template"] = @body
44+
45+
@parameters.each_with_index do |param, index|
46+
@attributes["sentry.message.parameters.#{index}"] = param
47+
end
48+
end
49+
3750
@contexts = {}
3851
end
3952

@@ -79,6 +92,14 @@ def serialize_parent_span_id
7992
@contexts.dig(:trace, :parent_span_id)
8093
end
8194

95+
def serialize_body
96+
if @parameters.empty?
97+
@body
98+
else
99+
sprintf(@body, *@parameters)
100+
end
101+
end
102+
82103
def serialize_attributes
83104
hash = @attributes.each_with_object({}) do |(key, value), memo|
84105
memo[key] = attribute_hash(value)
@@ -109,5 +130,9 @@ def value_type(value)
109130
"string"
110131
end
111132
end
133+
134+
def has_template_parameters?
135+
@parameters.is_a?(Array) && !@parameters.empty? && @body.is_a?(String) && @body.include?("%")
136+
end
112137
end
113138
end

sentry-ruby/lib/sentry/structured_logger.rb

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,78 +23,98 @@ class StructuredLogger
2323
# Severity number mapping for log levels according to the Sentry Logs Protocol
2424
# @see https://develop.sentry.dev/sdk/telemetry/logs/#log-severity-number
2525
LEVELS = {
26-
"trace" => 1,
27-
"debug" => 5,
28-
"info" => 9,
29-
"warn" => 13,
30-
"error" => 17,
31-
"fatal" => 21
26+
trace: 1,
27+
debug: 5,
28+
info: 9,
29+
warn: 13,
30+
error: 17,
31+
fatal: 21
3232
}.freeze
3333

3434
# @return [Configuration] The Sentry configuration
35+
# @!visibility private
3536
attr_reader :config
3637

3738
# Initializes a new StructuredLogger instance
39+
#
3840
# @param config [Configuration] The Sentry configuration
3941
def initialize(config)
4042
@config = config
4143
end
4244

4345
# Logs a message at TRACE level
46+
#
4447
# @param message [String] The log message
45-
# @param payload [Hash] Additional attributes to include with the log
48+
# @param parameters [Array] Array of values to replace template parameters in the message
49+
# @param attributes [Hash] Additional attributes to include with the log
4650
# @return [LogEvent, nil] The created log event or nil if logging is disabled
47-
def trace(message, payload = {})
48-
log(:trace, message, payload)
51+
def trace(message, parameters = [], **attributes)
52+
log(__method__, message, parameters: parameters, **attributes)
4953
end
5054

5155
# Logs a message at DEBUG level
56+
#
5257
# @param message [String] The log message
53-
# @param payload [Hash] Additional attributes to include with the log
58+
# @param parameters [Array] Array of values to replace template parameters in the message
59+
# @param attributes [Hash] Additional attributes to include with the log
5460
# @return [LogEvent, nil] The created log event or nil if logging is disabled
55-
def debug(message, payload = {})
56-
log(:debug, message, payload)
61+
def debug(message, parameters = [], **attributes)
62+
log(__method__, message, parameters: parameters, **attributes)
5763
end
5864

5965
# Logs a message at INFO level
66+
#
6067
# @param message [String] The log message
61-
# @param payload [Hash] Additional attributes to include with the log
68+
# @param parameters [Array] Array of values to replace template parameters in the message
69+
# @param attributes [Hash] Additional attributes to include with the log
6270
# @return [LogEvent, nil] The created log event or nil if logging is disabled
63-
def info(message, payload = {})
64-
log(:info, message, payload)
71+
def info(message, parameters = [], **attributes)
72+
log(__method__, message, parameters: parameters, **attributes)
6573
end
6674

6775
# Logs a message at WARN level
76+
#
6877
# @param message [String] The log message
69-
# @param payload [Hash] Additional attributes to include with the log
78+
# @param parameters [Array] Array of values to replace template parameters in the message
79+
# @param attributes [Hash] Additional attributes to include with the log
7080
# @return [LogEvent, nil] The created log event or nil if logging is disabled
71-
def warn(message, payload = {})
72-
log(:warn, message, payload)
81+
def warn(message, parameters = [], **attributes)
82+
log(__method__, message, parameters: parameters, **attributes)
7383
end
7484

7585
# Logs a message at ERROR level
86+
#
7687
# @param message [String] The log message
77-
# @param payload [Hash] Additional attributes to include with the log
88+
# @param parameters [Array] Array of values to replace template parameters in the message
89+
# @param attributes [Hash] Additional attributes to include with the log
7890
# @return [LogEvent, nil] The created log event or nil if logging is disabled
79-
def error(message, payload = {})
80-
log(:error, message, payload)
91+
def error(message, parameters = [], **attributes)
92+
log(__method__, message, parameters: parameters, **attributes)
8193
end
8294

8395
# Logs a message at FATAL level
96+
#
8497
# @param message [String] The log message
85-
# @param payload [Hash] Additional attributes to include with the log
98+
# @param parameters [Array] Array of values to replace template parameters in the message
99+
# @param attributes [Hash] Additional attributes to include with the log
86100
# @return [LogEvent, nil] The created log event or nil if logging is disabled
87-
def fatal(message, payload = {})
88-
log(:fatal, message, payload)
101+
def fatal(message, parameters = [], **attributes)
102+
log(__method__, message, parameters: parameters, **attributes)
89103
end
90104

91105
# Logs a message at the specified level
106+
#
92107
# @param level [Symbol] The log level (:trace, :debug, :info, :warn, :error, :fatal)
93108
# @param message [String] The log message
94-
# @param payload [Hash] Additional attributes to include with the log
109+
# @param parameters [Array] Array of values to replace template parameters in the message
110+
# @param attributes [Hash] Additional attributes to include with the log
95111
# @return [LogEvent, nil] The created log event or nil if logging is disabled
96-
def log(level, message, payload)
97-
Sentry.capture_log(message, level: level, severity: LEVELS[level], **payload)
112+
def log(level, message, parameters:, **attributes)
113+
if parameters.is_a?(Hash) && attributes.empty?
114+
Sentry.capture_log(message, level: level, severity: LEVELS[level], parameters: [], **parameters)
115+
else
116+
Sentry.capture_log(message, level: level, severity: LEVELS[level], parameters: parameters, **attributes)
117+
end
98118
end
99119
end
100120
end

sentry-ruby/spec/sentry/log_event_spec.rb

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,10 @@
4747
end
4848

4949
it "includes all required fields" do
50-
attributes = {
51-
"sentry.message.template" => "User %s has logged in!",
52-
"sentry.message.parameters.0" => "John"
53-
}
54-
5550
event = described_class.new(
5651
configuration: configuration,
5752
level: :info,
58-
body: "User John has logged in!",
59-
attributes: attributes
53+
body: "User John has logged in!"
6054
)
6155

6256
hash = event.to_hash
@@ -68,11 +62,6 @@
6862
attributes = hash[:attributes]
6963

7064
expect(attributes).to be_a(Hash)
71-
expect(attributes["sentry.message.template"]).to eq({ value: "User %s has logged in!", type: "string" })
72-
expect(attributes["sentry.message.parameters.0"]).to eq({ value: "John", type: "string" })
73-
expect(attributes["sentry.environment"]).to eq({ value: "test", type: "string" })
74-
expect(attributes["sentry.release"]).to eq({ value: "1.2.3", type: "string" })
75-
expect(attributes["sentry.address"]).to eq({ value: "server-123", type: "string" })
7665
expect(attributes["sentry.sdk.name"]).to eq({ value: "sentry.ruby", type: "string" })
7766
expect(attributes["sentry.sdk.version"]).to eq({ value: Sentry::VERSION, type: "string" })
7867
end

sentry-ruby/spec/sentry/structured_logger_spec.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,49 @@
4141
expect(log_event.body).to eql("Hello World")
4242
expect(log_event.attributes).to include(payload)
4343
end
44+
45+
it "logs with template parameters" do
46+
Sentry.logger.public_send(level, "Hello %s it is %s", ["Jane", "Monday"])
47+
48+
expect(logs).to_not be_empty
49+
50+
log_event = logs.last
51+
log_hash = log_event.to_hash
52+
53+
expect(log_event.type).to eql("log")
54+
expect(log_event.level).to eql(level.to_sym)
55+
expect(log_event.body).to eql("Hello %s it is %s")
56+
57+
expect(log_hash[:body]).to eql("Hello Jane it is Monday")
58+
59+
attributes = log_hash[:attributes]
60+
61+
expect(attributes["sentry.message.template"]).to eql({ value: "Hello %s it is %s", type: "string" })
62+
expect(attributes["sentry.message.parameters.0"]).to eql({ value: "Jane", type: "string" })
63+
expect(attributes["sentry.message.parameters.1"]).to eql({ value: "Monday", type: "string" })
64+
end
65+
66+
it "logs with template parameters and extra attributres" do
67+
Sentry.logger.public_send(level, "Hello %s it is %s", ["Jane", "Monday"], extra: 312)
68+
69+
expect(logs).to_not be_empty
70+
71+
log_event = logs.last
72+
log_hash = log_event.to_hash
73+
74+
expect(log_event.type).to eql("log")
75+
expect(log_event.level).to eql(level.to_sym)
76+
expect(log_event.body).to eql("Hello %s it is %s")
77+
78+
expect(log_hash[:body]).to eql("Hello Jane it is Monday")
79+
80+
attributes = log_hash[:attributes]
81+
82+
expect(attributes[:extra]).to eql({ value: 312, type: "integer" })
83+
expect(attributes["sentry.message.template"]).to eql({ value: "Hello %s it is %s", type: "string" })
84+
expect(attributes["sentry.message.parameters.0"]).to eql({ value: "Jane", type: "string" })
85+
expect(attributes["sentry.message.parameters.1"]).to eql({ value: "Monday", type: "string" })
86+
end
4487
end
4588
end
4689
end

sentry-ruby/spec/sentry/transport_spec.rb

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,11 @@
243243
Sentry::LogEvent.new(
244244
configuration: configuration,
245245
level: :info,
246-
body: "User John has logged in!",
246+
body: "User %s has logged in!",
247247
trace_id: "5b8efff798038103d269b633813fc60c",
248248
timestamp: 1544719860.0,
249249
attributes: {
250-
"sentry.message.template" => "User %s has logged in!",
251-
"sentry.message.parameters.0" => "John",
252-
"sentry.environment" => "production",
253-
"sentry.release" => "1.0.0",
254-
"sentry.trace.parent_span_id" => "b0e6f15b45c36b12"
250+
parameters: ["John"]
255251
}
256252
)
257253
end
@@ -299,10 +295,7 @@
299295
expect(log_event["attributes"]).to include(
300296
"sentry.message.template" => { "value" => "User %s has logged in!", "type" => "string" },
301297
"sentry.message.parameters.0" => { "value" => "John", "type" => "string" },
302-
"sentry.environment" => { "value" => "development", "type" => "string" },
303-
"sentry.release" => { "value" => "1.0.0", "type" => "string" },
304-
"sentry.trace.parent_span_id" => { "value" => "b0e6f15b45c36b12", "type" => "string" },
305-
"sentry.address" => { "value" => matching(/\w+/), "type" => "string" }
298+
"sentry.environment" => { "value" => "development", "type" => "string" }
306299
)
307300
end
308301
end
@@ -318,7 +311,7 @@
318311

319312
it "gracefully removes bad encoding breadcrumb message" do
320313
expect do
321-
serialized_result = JSON.generate(event.to_hash)
314+
JSON.generate(event.to_hash)
322315
end.not_to raise_error
323316
end
324317
end

0 commit comments

Comments
 (0)