lograge 0.15.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 61fb6a597eb84cb10c97a1155d5dc983811c159fecdc00625db65fb46b3cf693
4
- data.tar.gz: da3bfa1e8eaa35ad80caa7c743f826e7560fc8961c19f5fe342cde8fb0a830cf
3
+ metadata.gz: 7417503c9af3927cc3291f8030b9cc506d42d8756538dc656218d5a2249f977d
4
+ data.tar.gz: a09ba47fcf99837649481b588b7eb1d90714a7ef3e454d18e09b1b3d894bbfb4
5
5
  SHA512:
6
- metadata.gz: 6fe3ff2a303961c236c2407edfd585ffcffe2c5026453d497a95d731a81769e92e5814fcfbaa5c4d2e442b2b8112d597874ca4c64f9045ec8c0415acc223db60
7
- data.tar.gz: 537eae531458214b65cc3776e959a6c592bfcef17803421335d52b63b021172c62bf1f2da2b9218a4b47f1a2a00dca3945a3b3e5e7fd324744aa7be6427aaeee
6
+ metadata.gz: b171ecf0bba8e016a819606cfd579cf96b4e790b95ff0a6aba75d1ba3e1a3b180f12ef279bade671d09e1f0aca9090d61ef8bb051674ec60255f58c95492ca7e
7
+ data.tar.gz: 7e537bde3728955c50e6a142c43d126fafeb69ded6d7a8b9ce18410e311be49a166924ba5090f5a7279b156b0b028e9f113616d39c953fc0f39c2cfd9180dd4a
@@ -4,7 +4,7 @@ module Lograge
4
4
  module Formatters
5
5
  class Cee
6
6
  def call(data)
7
- "@cee: #{JSON.dump(data)}"
7
+ "@cee: #{Helpers::SafeJson.dump(data)}"
8
8
  end
9
9
  end
10
10
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Lograge
6
+ module Formatters
7
+ module Helpers
8
+ # Serializes log data to JSON without dropping the whole log event when
9
+ # the data cannot be generated as-is. Newer versions of the json gem are
10
+ # stricter about their input:
11
+ #
12
+ # * json 3.1 rejects non-ASCII ASCII-8BIT (binary) strings, which are
13
+ # common in exception messages and HTTP response bodies.
14
+ # * json 3.0 rejects hashes with keys that serialize to the same name,
15
+ # such as +:status+ and <tt>"status"</tt>.
16
+ #
17
+ # Strings that are invalid UTF-8 have always been rejected.
18
+ #
19
+ # The data is only normalized after the first attempt fails, so well
20
+ # formed events are serialized exactly as before and pay no extra cost.
21
+ module SafeJson
22
+ # ArgumentError is included because, on TruffleRuby, json 3's pure-Ruby
23
+ # generator serializes objects such as Symbols and Times through
24
+ # ActiveSupport's encoder, which before Rails 8.1 passes the
25
+ # +quirks_mode+ option that json 3 no longer accepts. Normalizing turns
26
+ # those objects into plain strings, which avoids that code path.
27
+ GENERATION_ERRORS = [::JSON::GeneratorError, ::EncodingError, ::ArgumentError].freeze
28
+
29
+ module_function
30
+
31
+ def dump(data)
32
+ ::JSON.dump(data)
33
+ rescue *GENERATION_ERRORS
34
+ ::JSON.dump(normalize(data))
35
+ end
36
+
37
+ # Returns a copy of +value+ that the json gem can always generate:
38
+ #
39
+ # * every hash key is a UTF-8 string. When two keys serialize to the same
40
+ # name, the first key keeps its position and the later value wins,
41
+ # which matches how the log subscriber merges custom options.
42
+ # * every string and symbol becomes a valid UTF-8 string. Bytes that
43
+ # cannot be converted are replaced with U+FFFD.
44
+ # * other objects are kept if they can be generated on their own, and
45
+ # are otherwise replaced by their normalized +to_s+ (for example an
46
+ # exception whose message holds binary bytes).
47
+ def normalize(value)
48
+ case value
49
+ when Hash then normalize_hash(value)
50
+ when Array then value.map { |element| normalize(element) }
51
+ when String then to_utf8(value)
52
+ when Symbol then to_utf8(value.to_s)
53
+ when Numeric, true, false, nil then value
54
+ else normalize_object(value)
55
+ end
56
+ end
57
+
58
+ def normalize_hash(hash)
59
+ hash.each_with_object({}) do |(key, value), normalized|
60
+ normalized[to_utf8(key.to_s)] = normalize(value)
61
+ end
62
+ end
63
+
64
+ def normalize_object(object)
65
+ ::JSON.dump(object)
66
+ object
67
+ rescue *GENERATION_ERRORS
68
+ to_utf8(object.to_s)
69
+ end
70
+
71
+ def to_utf8(string)
72
+ encoding = string.encoding
73
+ if encoding == Encoding::UTF_8
74
+ string.valid_encoding? ? string : string.scrub
75
+ elsif encoding == Encoding::BINARY || encoding.dummy?
76
+ # Binary strings carry no encoding information, and dummy encodings
77
+ # (such as UTF-7) have no converter. Treat them as UTF-8 on a
78
+ # best-effort basis so valid multibyte sequences survive.
79
+ string.dup.force_encoding(Encoding::UTF_8).scrub
80
+ else
81
+ string.encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -5,7 +5,7 @@ module Lograge
5
5
  module Formatters
6
6
  class Json
7
7
  def call(data)
8
- ::JSON.dump(data)
8
+ Helpers::SafeJson.dump(data)
9
9
  end
10
10
  end
11
11
  end
@@ -32,10 +32,10 @@ module Lograge
32
32
  def escape(string)
33
33
  value = string.is_a?(String) ? string.dup : string.to_s
34
34
 
35
- value.gsub!('\\', '\\\\')
36
- value.gsub!('\n', '\\n')
37
- value.gsub!('\r', '\\r')
38
- value.gsub!('\t', '\\t')
35
+ value.gsub!('\\') { '\\\\' }
36
+ value.gsub!("\n", '\\n')
37
+ value.gsub!("\r", '\\r')
38
+ value.gsub!("\t", '\\t')
39
39
 
40
40
  value
41
41
  end
@@ -33,7 +33,7 @@ module Lograge
33
33
  data.merge!(extract_runtimes(event, payload))
34
34
  data.merge!(extract_location)
35
35
  data.merge!(extract_unpermitted_params)
36
- data.merge!(custom_options(event))
36
+ merge_options(data, custom_options(event))
37
37
  end
38
38
 
39
39
  %i[initial_data extract_status extract_runtimes
@@ -70,7 +70,27 @@ module Lograge
70
70
 
71
71
  def custom_options(event)
72
72
  options = Lograge.custom_options(event) || {}
73
- options.merge event.payload[:custom_payload] || {}
73
+ merge_options(options.dup, event.payload[:custom_payload] || {})
74
+ end
75
+
76
+ # Merges +options+ into +data+. A String key overrides an existing Symbol
77
+ # key of the same name (and vice versa) instead of being added alongside
78
+ # it, so the two never end up serialized as duplicate keys.
79
+ def merge_options(data, options)
80
+ options.each do |key, value|
81
+ data[existing_key(data, key)] = value
82
+ end
83
+ data
84
+ end
85
+
86
+ def existing_key(data, key)
87
+ return key if data.key?(key)
88
+
89
+ counterpart = case key
90
+ when Symbol then key.to_s
91
+ when String then key.to_sym
92
+ end
93
+ counterpart && data.key?(counterpart) ? counterpart : key
74
94
  end
75
95
 
76
96
  def before_format(data, payload)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lograge
4
- VERSION = '0.15.1'
4
+ VERSION = '0.16.0'
5
5
  end
data/lib/lograge.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'logger'
4
4
  require 'lograge/version'
5
5
  require 'lograge/formatters/helpers/method_and_path'
6
+ require 'lograge/formatters/helpers/safe_json'
6
7
  require 'lograge/formatters/cee'
7
8
  require 'lograge/formatters/json'
8
9
  require 'lograge/formatters/graylog2'
@@ -115,7 +116,27 @@ module Lograge
115
116
  unsubscribe(:action_controller, subscriber)
116
117
  end
117
118
  end
119
+
120
+ remove_event_reporter_log_subscriptions
121
+ end
122
+
123
+ # Rails 8.2+ framework log subscribers inherit from
124
+ # ActiveSupport::EventReporter::LogSubscriber and are subscribed to
125
+ # ActiveSupport.event_reporter instead of ActiveSupport::Notifications.
126
+ def remove_event_reporter_log_subscriptions
127
+ rails_event_reporter_log_subscriber_classes.each do |klass|
128
+ ActiveSupport.event_reporter.unsubscribe(klass)
129
+ end
130
+ end
131
+
132
+ def rails_event_reporter_log_subscriber_classes
133
+ return [] unless defined?(ActiveSupport::EventReporter::LogSubscriber)
134
+
135
+ [ActionController::LogSubscriber, ActionView::LogSubscriber].select do |klass|
136
+ klass < ActiveSupport::EventReporter::LogSubscriber
137
+ end
118
138
  end
139
+ private_class_method :remove_event_reporter_log_subscriptions, :rails_event_reporter_log_subscriber_classes
119
140
 
120
141
  def unsubscribe(component, subscriber)
121
142
  events = subscriber.public_methods(false).reject { |method| method.to_s == 'call' }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lograge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.1
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mathias Meyer
@@ -207,6 +207,7 @@ files:
207
207
  - lib/lograge/formatters/cee.rb
208
208
  - lib/lograge/formatters/graylog2.rb
209
209
  - lib/lograge/formatters/helpers/method_and_path.rb
210
+ - lib/lograge/formatters/helpers/safe_json.rb
210
211
  - lib/lograge/formatters/json.rb
211
212
  - lib/lograge/formatters/key_value.rb
212
213
  - lib/lograge/formatters/key_value_deep.rb
@@ -246,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
247
  - !ruby/object:Gem::Version
247
248
  version: '0'
248
249
  requirements: []
249
- rubygems_version: 4.0.14
250
+ rubygems_version: 3.6.9
250
251
  specification_version: 4
251
252
  summary: Tame Rails' multi-line logging into a single line per request
252
253
  test_files: []