semantic_logger 4.18.0 → 5.0.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +42 -81
  3. data/Rakefile +10 -3
  4. data/lib/semantic_logger/appender/async.rb +86 -173
  5. data/lib/semantic_logger/appender/cloudwatch_logs.rb +4 -4
  6. data/lib/semantic_logger/appender/elasticsearch.rb +6 -182
  7. data/lib/semantic_logger/appender/elasticsearch_base.rb +212 -0
  8. data/lib/semantic_logger/appender/elasticsearch_http.rb +2 -2
  9. data/lib/semantic_logger/appender/file.rb +20 -4
  10. data/lib/semantic_logger/appender/graylog.rb +2 -2
  11. data/lib/semantic_logger/appender/http.rb +27 -2
  12. data/lib/semantic_logger/appender/io.rb +8 -4
  13. data/lib/semantic_logger/appender/kafka.rb +2 -2
  14. data/lib/semantic_logger/appender/loki.rb +2 -4
  15. data/lib/semantic_logger/appender/mongodb.rb +3 -6
  16. data/lib/semantic_logger/appender/new_relic_logs.rb +2 -2
  17. data/lib/semantic_logger/appender/open_telemetry.rb +8 -6
  18. data/lib/semantic_logger/appender/opensearch.rb +35 -0
  19. data/lib/semantic_logger/appender/rabbitmq.rb +3 -3
  20. data/lib/semantic_logger/appender/sentry_ruby.rb +2 -2
  21. data/lib/semantic_logger/appender/splunk.rb +2 -2
  22. data/lib/semantic_logger/appender/splunk_http.rb +3 -3
  23. data/lib/semantic_logger/appender/syslog.rb +5 -4
  24. data/lib/semantic_logger/appender/tcp.rb +2 -2
  25. data/lib/semantic_logger/appender/udp.rb +2 -2
  26. data/lib/semantic_logger/appender/wrapper.rb +4 -4
  27. data/lib/semantic_logger/appender.rb +30 -19
  28. data/lib/semantic_logger/appenders.rb +26 -5
  29. data/lib/semantic_logger/base.rb +100 -21
  30. data/lib/semantic_logger/concerns/compatibility.rb +2 -2
  31. data/lib/semantic_logger/core_ext/process.rb +34 -0
  32. data/lib/semantic_logger/formatters/base.rb +46 -7
  33. data/lib/semantic_logger/formatters/color.rb +6 -3
  34. data/lib/semantic_logger/formatters/default.rb +6 -4
  35. data/lib/semantic_logger/formatters/ecs.rb +151 -0
  36. data/lib/semantic_logger/formatters/fluentd.rb +15 -4
  37. data/lib/semantic_logger/formatters/json.rb +6 -1
  38. data/lib/semantic_logger/formatters/logfmt.rb +2 -2
  39. data/lib/semantic_logger/formatters/loki.rb +4 -4
  40. data/lib/semantic_logger/formatters/open_telemetry.rb +15 -5
  41. data/lib/semantic_logger/formatters/pattern.rb +235 -0
  42. data/lib/semantic_logger/formatters/raw.rb +2 -2
  43. data/lib/semantic_logger/formatters/signalfx.rb +2 -2
  44. data/lib/semantic_logger/formatters/syslog.rb +14 -3
  45. data/lib/semantic_logger/formatters/syslog_cee.rb +1 -1
  46. data/lib/semantic_logger/formatters.rb +2 -0
  47. data/lib/semantic_logger/log.rb +18 -4
  48. data/lib/semantic_logger/logger.rb +2 -2
  49. data/lib/semantic_logger/metric/new_relic.rb +2 -2
  50. data/lib/semantic_logger/metric/signalfx.rb +2 -2
  51. data/lib/semantic_logger/metric/statsd.rb +2 -2
  52. data/lib/semantic_logger/processor.rb +21 -0
  53. data/lib/semantic_logger/queue_processor.rb +369 -0
  54. data/lib/semantic_logger/reporters/minitest.rb +4 -4
  55. data/lib/semantic_logger/semantic_logger.rb +103 -11
  56. data/lib/semantic_logger/subscriber.rb +15 -2
  57. data/lib/semantic_logger/sync_processor.rb +25 -3
  58. data/lib/semantic_logger/test/capture_log_events.rb +2 -2
  59. data/lib/semantic_logger/test/minitest.rb +8 -4
  60. data/lib/semantic_logger/test/rspec.rb +249 -0
  61. data/lib/semantic_logger/utils.rb +83 -4
  62. data/lib/semantic_logger/version.rb +1 -1
  63. data/lib/semantic_logger.rb +9 -0
  64. metadata +17 -8
  65. data/lib/semantic_logger/appender/async_batch.rb +0 -93
@@ -5,7 +5,7 @@ rescue LoadError
5
5
  'Gem elasticsearch is required for logging to Elasticsearch. Please add the gem "elasticsearch" to your Gemfile.'
6
6
  end
7
7
 
8
- require "date"
8
+ require "semantic_logger/appender/elasticsearch_base"
9
9
 
10
10
  # Forward all log messages to Elasticsearch.
11
11
  #
@@ -17,190 +17,14 @@ require "date"
17
17
  # )
18
18
  module SemanticLogger
19
19
  module Appender
20
- class Elasticsearch < SemanticLogger::Subscriber
21
- attr_accessor :url, :index, :date_pattern, :type, :client, :flush_interval, :timeout_interval, :batch_size,
22
- :elasticsearch_args
23
-
24
- # Create Elasticsearch appender over persistent HTTP(S)
25
- #
26
- # Parameters:
27
- # index: [String]
28
- # Prefix of the index to store the logs in Elasticsearch.
29
- # The final index appends the date so that indexes are used per day.
30
- # I.e. The final index will look like 'semantic_logger-YYYY.MM.DD'
31
- # Default: 'semantic_logger'
32
- #
33
- # date_pattern: [String]
34
- # The time format used to generate the full index name. Useful
35
- # if you want monthly indexes ('%Y.%m') or weekly ('%Y.%W').
36
- # Default: '%Y.%m.%d'
37
- #
38
- # type: [String]
39
- # Document type to associate with logs when they are written.
40
- # Deprecated in Elasticsearch 7.0.0.
41
- # Default: 'log'
42
- #
43
- # level: [:trace | :debug | :info | :warn | :error | :fatal]
44
- # Override the log level for this appender.
45
- # Default: SemanticLogger.default_level
46
- #
47
- # formatter: [Object|Proc|Symbol|Hash]
48
- # An instance of a class that implements #call, or a Proc to be used to format
49
- # the output from this appender
50
- # Default: :raw_json (See: #call)
51
- #
52
- # filter: [Regexp|Proc]
53
- # RegExp: Only include log messages where the class name matches the supplied.
54
- # regular expression. All other messages will be ignored.
55
- # Proc: Only include log messages where the supplied Proc returns true
56
- # The Proc must return true or false.
57
- #
58
- # host: [String]
59
- # Name of this host to appear in log messages.
60
- # Default: SemanticLogger.host
61
- #
62
- # application: [String]
63
- # Name of this application to appear in log messages.
64
- # Default: SemanticLogger.application
65
- #
66
- # Elasticsearch Parameters:
67
- # url: [String]
68
- # Fully qualified address to the Elasticsearch service.
69
- # Default: 'http://localhost:9200'
70
- #
71
- # hosts: [String|Hash|Array]
72
- # Single host passed as a String or Hash, or multiple hosts
73
- # passed as an Array; `host` or `url` keys are also valid.
74
- # Note:
75
- # :url above is ignored when supplying this option.
76
- #
77
- # resurrect_after [Float]
78
- # After how many seconds a dead connection should be tried again.
79
- #
80
- # reload_connections [true|false|Integer]
81
- # Reload connections after X requests.
82
- # Default: false
83
- #
84
- # randomize_hosts [true|false]
85
- # Shuffle connections on initialization and reload.
86
- # Default: false
87
- #
88
- # sniffer_timeout [Integer]
89
- # Timeout for reloading connections in seconds.
90
- # Default: 1
91
- #
92
- # retry_on_failure [true|false|Integer]
93
- # Retry X times when request fails before raising and exception.
94
- # Default: false
95
- #
96
- # retry_on_status [Array<Number>]
97
- # Retry when specific status codes are returned.
98
- #
99
- # reload_on_failure [true|false]
100
- # Reload connections after failure.
101
- # Default: false
102
- #
103
- # request_timeout [Integer]
104
- # The request timeout to be passed to transport in options.
105
- #
106
- # adapter [Symbol]
107
- # A specific adapter for Faraday (e.g. `:patron`)
108
- #
109
- # transport_options [Hash]
110
- # Options to be passed to the `Faraday::Connection` constructor.
111
- #
112
- # transport_class [Constant]
113
- # A specific transport class to use, will be initialized by
114
- # the client and passed hosts and all arguments.
115
- #
116
- # transport [Object]
117
- # A specific transport instance.
118
- #
119
- # serializer_class [Constant]
120
- # A specific serializer class to use, will be initialized by
121
- # the transport and passed the transport instance.
122
- #
123
- # selector [Elasticsearch::Transport::Transport::Connections::Selector::Base]
124
- # An instance of selector strategy derived from `Elasticsearch::Transport::Transport::Connections::Selector::Base`.
125
- #
126
- # send_get_body_as [String]
127
- # Specify the HTTP method to use for GET requests with a body.
128
- # Default: 'GET'
129
- def initialize(url: "http://localhost:9200",
130
- index: "semantic_logger",
131
- date_pattern: "%Y.%m.%d",
132
- type: "log",
133
- level: nil,
134
- formatter: nil,
135
- filter: nil,
136
- application: nil,
137
- environment: nil,
138
- host: nil,
139
- data_stream: false,
140
- **elasticsearch_args,
141
- &block)
142
- @url = url
143
- @index = index
144
- @date_pattern = date_pattern
145
- @type = type
146
- @elasticsearch_args = elasticsearch_args.dup
147
- @elasticsearch_args[:url] = url if url && !elasticsearch_args[:hosts]
148
- @elasticsearch_args[:logger] = logger
149
- @data_stream = data_stream
150
-
151
- super(level: level, formatter: formatter, filter: filter, application: application, environment: environment, host: host, metrics: false, &block)
152
- reopen
153
- end
154
-
155
- def reopen
156
- @client = ::Elasticsearch::Client.new(@elasticsearch_args)
157
- end
158
-
159
- # Log to the index for today
160
- def log(log)
161
- bulk_payload = formatter.call(log, self)
162
- write_to_elasticsearch([bulk_index(log), bulk_payload])
163
- true
164
- end
165
-
166
- def batch(logs)
167
- messages = []
168
- logs.each do |log|
169
- messages << bulk_index(log) << formatter.call(log, self)
170
- end
171
-
172
- write_to_elasticsearch(messages)
173
- true
174
- end
20
+ class Elasticsearch < ElasticsearchBase
21
+ # Backwards compatible accessor for the client arguments.
22
+ alias elasticsearch_args client_args
175
23
 
176
24
  private
177
25
 
178
- def write_to_elasticsearch(messages)
179
- bulk_result =
180
- if @data_stream
181
- @client.bulk(index: index, body: messages)
182
- else
183
- @client.bulk(body: messages)
184
- end
185
-
186
- return unless bulk_result["errors"]
187
-
188
- failed = bulk_result["items"].reject { |x| x["status"] == 201 }
189
- logger.error("ElasticSearch: Write failed. Messages discarded. : #{failed}")
190
- end
191
-
192
- def bulk_index(log)
193
- expanded_index_name = log.time.strftime("#{index}-#{date_pattern}")
194
- return {"create" => {}} if @data_stream
195
-
196
- bulk_index = {"index" => {"_index" => expanded_index_name}}
197
- bulk_index["index"].merge!({"_type" => type}) if version_supports_type?
198
- bulk_index
199
- end
200
-
201
- def default_formatter
202
- time_key = @data_stream ? "@timestamp" : :timestamp
203
- SemanticLogger::Formatters::Raw.new(time_format: :iso_8601, time_key: time_key)
26
+ def client_class
27
+ ::Elasticsearch::Client
204
28
  end
205
29
 
206
30
  def version_supports_type?
@@ -0,0 +1,212 @@
1
+ require "date"
2
+
3
+ module SemanticLogger
4
+ module Appender
5
+ # Abstract base appender for Elasticsearch-compatible search engines.
6
+ #
7
+ # Implements the shared bulk-indexing pipeline used by both the
8
+ # {Elasticsearch} and {OpenSearch} appenders. Subclasses only need to
9
+ # supply the backing client class (and, optionally, whether the server
10
+ # version still supports document `_type`).
11
+ #
12
+ # This class is internal: applications add an appender via
13
+ # `SemanticLogger.add_appender(appender: :elasticsearch, ...)` or
14
+ # `appender: :opensearch`, never by referencing this class directly.
15
+ class ElasticsearchBase < SemanticLogger::Subscriber
16
+ attr_accessor :url, :index, :date_pattern, :type, :client, :flush_interval, :timeout_interval, :batch_size,
17
+ :client_args
18
+
19
+ # Create an Elasticsearch-compatible appender over persistent HTTP(S).
20
+ #
21
+ # Parameters:
22
+ # index: [String]
23
+ # Prefix of the index to store the logs in.
24
+ # The final index appends the date so that indexes are used per day.
25
+ # I.e. The final index will look like 'semantic_logger-YYYY.MM.DD'
26
+ # Default: 'semantic_logger'
27
+ #
28
+ # date_pattern: [String]
29
+ # The time format used to generate the full index name. Useful
30
+ # if you want monthly indexes ('%Y.%m') or weekly ('%Y.%W').
31
+ # Default: '%Y.%m.%d'
32
+ #
33
+ # type: [String]
34
+ # Document type to associate with logs when they are written.
35
+ # Deprecated in Elasticsearch 7.0.0, unused by OpenSearch.
36
+ # Default: 'log'
37
+ #
38
+ # level: [:trace | :debug | :info | :warn | :error | :fatal]
39
+ # Override the log level for this appender.
40
+ # Default: SemanticLogger.default_level
41
+ #
42
+ # formatter: [Object|Proc|Symbol|Hash]
43
+ # An instance of a class that implements #call, or a Proc to be used to format
44
+ # the output from this appender
45
+ # Default: :raw_json (See: #call)
46
+ #
47
+ # filter: [Regexp|Proc]
48
+ # RegExp: Only include log messages where the class name matches the supplied.
49
+ # regular expression. All other messages will be ignored.
50
+ # Proc: Only include log messages where the supplied Proc returns true
51
+ # The Proc must return true or false.
52
+ #
53
+ # host: [String]
54
+ # Name of this host to appear in log messages.
55
+ # Default: SemanticLogger.host
56
+ #
57
+ # application: [String]
58
+ # Name of this application to appear in log messages.
59
+ # Default: SemanticLogger.application
60
+ #
61
+ # Client Parameters (passed through to the backing client):
62
+ # url: [String]
63
+ # Fully qualified address to the service.
64
+ # Default: 'http://localhost:9200'
65
+ #
66
+ # hosts: [String|Hash|Array]
67
+ # Single host passed as a String or Hash, or multiple hosts
68
+ # passed as an Array; `host` or `url` keys are also valid.
69
+ # Note:
70
+ # :url above is ignored when supplying this option.
71
+ #
72
+ # resurrect_after [Float]
73
+ # After how many seconds a dead connection should be tried again.
74
+ #
75
+ # reload_connections [true|false|Integer]
76
+ # Reload connections after X requests.
77
+ # Default: false
78
+ #
79
+ # randomize_hosts [true|false]
80
+ # Shuffle connections on initialization and reload.
81
+ # Default: false
82
+ #
83
+ # sniffer_timeout [Integer]
84
+ # Timeout for reloading connections in seconds.
85
+ # Default: 1
86
+ #
87
+ # retry_on_failure [true|false|Integer]
88
+ # Retry X times when request fails before raising and exception.
89
+ # Default: false
90
+ #
91
+ # retry_on_status [Array<Number>]
92
+ # Retry when specific status codes are returned.
93
+ #
94
+ # reload_on_failure [true|false]
95
+ # Reload connections after failure.
96
+ # Default: false
97
+ #
98
+ # request_timeout [Integer]
99
+ # The request timeout to be passed to transport in options.
100
+ #
101
+ # adapter [Symbol]
102
+ # A specific adapter for Faraday (e.g. `:patron`)
103
+ #
104
+ # transport_options [Hash]
105
+ # Options to be passed to the `Faraday::Connection` constructor.
106
+ #
107
+ # transport_class [Constant]
108
+ # A specific transport class to use, will be initialized by
109
+ # the client and passed hosts and all arguments.
110
+ #
111
+ # transport [Object]
112
+ # A specific transport instance.
113
+ #
114
+ # serializer_class [Constant]
115
+ # A specific serializer class to use, will be initialized by
116
+ # the transport and passed the transport instance.
117
+ #
118
+ # selector
119
+ # An instance of a connection selector strategy.
120
+ #
121
+ # send_get_body_as [String]
122
+ # Specify the HTTP method to use for GET requests with a body.
123
+ # Default: 'GET'
124
+ def initialize(url: "http://localhost:9200",
125
+ index: "semantic_logger",
126
+ date_pattern: "%Y.%m.%d",
127
+ type: "log",
128
+ level: nil,
129
+ formatter: nil,
130
+ filter: nil,
131
+ application: nil,
132
+ environment: nil,
133
+ host: nil,
134
+ data_stream: false,
135
+ **client_args,
136
+ &)
137
+ @url = url
138
+ @index = index
139
+ @date_pattern = date_pattern
140
+ @type = type
141
+ @client_args = client_args.dup
142
+ @client_args[:url] = url if url && !client_args[:hosts]
143
+ @client_args[:logger] = logger
144
+ @data_stream = data_stream
145
+
146
+ super(level: level, formatter: formatter, filter: filter, application: application, environment: environment, host: host, metrics: false, &)
147
+ reopen
148
+ end
149
+
150
+ def reopen
151
+ @client = client_class.new(@client_args)
152
+ end
153
+
154
+ # Log to the index for today
155
+ def log(log)
156
+ bulk_payload = formatter.call(log, self)
157
+ bulk_write([bulk_index(log), bulk_payload])
158
+ true
159
+ end
160
+
161
+ def batch(logs)
162
+ messages = []
163
+ logs.each do |log|
164
+ messages << bulk_index(log) << formatter.call(log, self)
165
+ end
166
+
167
+ bulk_write(messages)
168
+ true
169
+ end
170
+
171
+ private
172
+
173
+ # The backing client class, e.g. ::Elasticsearch::Client or ::OpenSearch::Client.
174
+ def client_class
175
+ raise NotImplementedError, "#{self.class} must implement #client_class"
176
+ end
177
+
178
+ def bulk_write(messages)
179
+ bulk_result =
180
+ if @data_stream
181
+ @client.bulk(index: index, body: messages)
182
+ else
183
+ @client.bulk(body: messages)
184
+ end
185
+
186
+ return unless bulk_result["errors"]
187
+
188
+ failed = bulk_result["items"].reject { |x| x["status"] == 201 }
189
+ logger.error("#{self.class.name}: Write failed. Messages discarded. : #{failed}")
190
+ end
191
+
192
+ def bulk_index(log)
193
+ expanded_index_name = log.time.strftime("#{index}-#{date_pattern}")
194
+ return {"create" => {}} if @data_stream
195
+
196
+ bulk_index = {"index" => {"_index" => expanded_index_name}}
197
+ bulk_index["index"].merge!({"_type" => type}) if version_supports_type?
198
+ bulk_index
199
+ end
200
+
201
+ def default_formatter
202
+ time_key = @data_stream ? "@timestamp" : :timestamp
203
+ SemanticLogger::Formatters::Raw.new(time_format: :iso_8601, time_key: time_key)
204
+ end
205
+
206
+ # Modern Elasticsearch (>= 7) and OpenSearch no longer support document `_type`.
207
+ def version_supports_type?
208
+ false
209
+ end
210
+ end
211
+ end
212
+ end
@@ -55,10 +55,10 @@ module SemanticLogger
55
55
  type: "log",
56
56
  url: "http://localhost:9200",
57
57
  **http_args,
58
- &block)
58
+ &)
59
59
  @index = index
60
60
  @type = type
61
- super(url: url, **http_args, &block)
61
+ super(url: url, **http_args, &)
62
62
 
63
63
  @request_path = "#{@path.end_with?('/') ? @path : "#{@path}/"}#{@index}-%Y.%m.%d"
64
64
  @logging_path = "#{@request_path}/#{type}"
@@ -7,7 +7,7 @@ module SemanticLogger
7
7
  module Appender
8
8
  class File < SemanticLogger::Subscriber
9
9
  attr_accessor :file_name, :retry_count, :append, :exclusive_lock, :encoding,
10
- :reopen_period, :reopen_count, :reopen_size
10
+ :reopen_period, :reopen_count, :reopen_size, :permissions
11
11
  attr_reader :log_count, :log_size, :current_file_name, :reopen_at
12
12
 
13
13
  # Create an appender to log to a named file.
@@ -73,6 +73,13 @@ module SemanticLogger
73
73
  # Encoding to use when writing to the file.
74
74
  # Default: Encoding::BINARY
75
75
  #
76
+ # :permissions [Integer]
77
+ # Octal file permissions to apply to the log file.
78
+ # Log files frequently contain sensitive information, so restrict access
79
+ # by supplying for example `0o640` (owner read/write, group read).
80
+ # Applied both when the file is created and to an existing log file.
81
+ # Default: nil (use the process umask, the standard Ruby behavior)
82
+ #
76
83
  # :retry_count [Integer]
77
84
  # Number of times to attempt to re-open the file name when an error occurs trying to
78
85
  # write to the file.
@@ -123,7 +130,7 @@ module SemanticLogger
123
130
  # logger = SemanticLogger["test"]
124
131
  # logger.info "Hello World"
125
132
  def initialize(file_name, retry_count: 1, append: true, reopen_period: nil, reopen_count: 0, reopen_size: 0,
126
- encoding: Encoding::BINARY, exclusive_lock: false, **args, &block)
133
+ encoding: Encoding::BINARY, exclusive_lock: false, permissions: nil, **args, &)
127
134
  if !file_name.is_a?(String) || file_name.empty?
128
135
  raise(ArgumentError, "SemanticLogging::Appender::File file_name must be a non-empty string")
129
136
  end
@@ -137,11 +144,12 @@ module SemanticLogger
137
144
  @reopen_size = reopen_size
138
145
  @encoding = encoding
139
146
  @exclusive_lock = exclusive_lock
147
+ @permissions = permissions
140
148
  @log_count = 0
141
149
  @log_size = 0
142
150
  @reopen_at = nil
143
151
 
144
- super(**args, &block)
152
+ super(**args, &)
145
153
  end
146
154
 
147
155
  # After forking an active process call #reopen to re-open
@@ -170,7 +178,15 @@ module SemanticLogger
170
178
 
171
179
  options = ::File::WRONLY | ::File::CREAT
172
180
  options |= ::File::APPEND if append
173
- @file = ::File.open(current_file_name, options)
181
+ @file =
182
+ if permissions
183
+ ::File.open(current_file_name, options, permissions)
184
+ else
185
+ ::File.open(current_file_name, options)
186
+ end
187
+ # File.open only applies the permissions when creating the file, so also
188
+ # enforce them on an already existing log file.
189
+ @file.chmod(permissions) if permissions
174
190
  # Force all log entries to write immediately without buffering
175
191
  # Allows multiple processes to write to the same log file simultaneously
176
192
  @file.sync = true
@@ -88,13 +88,13 @@ module SemanticLogger
88
88
  gelf_options: {},
89
89
  level_map: LevelMap.new,
90
90
  **args,
91
- &block)
91
+ &)
92
92
  @url = url
93
93
  @max_size = max_size
94
94
  @gelf_options = gelf_options
95
95
  @level_map = level_map.is_a?(LevelMap) ? level_map : LevelMap.new(level_map)
96
96
 
97
- super(**args, &block)
97
+ super(**args, &)
98
98
  reopen
99
99
  end
100
100
 
@@ -16,6 +16,16 @@ require "openssl"
16
16
  # appender: :http,
17
17
  # url: 'http://localhost:8088/path'
18
18
  # )
19
+ #
20
+ # Batching:
21
+ # By default each log message is posted individually. To post multiple log
22
+ # messages in a single request as a JSON array (for example to the Filebeat
23
+ # http_endpoint input), enable batching when adding the appender:
24
+ # SemanticLogger.add_appender(
25
+ # appender: :http,
26
+ # url: 'http://localhost:8088/path',
27
+ # batch: true
28
+ # )
19
29
  module SemanticLogger
20
30
  module Appender
21
31
  class Http < SemanticLogger::Subscriber
@@ -106,7 +116,7 @@ module SemanticLogger
106
116
  read_timeout: 1.0,
107
117
  continue_timeout: 1.0,
108
118
  **args,
109
- &block)
119
+ &)
110
120
  @url = url
111
121
  @proxy_url = proxy_url
112
122
  @ssl_options = ssl
@@ -151,7 +161,7 @@ module SemanticLogger
151
161
 
152
162
  @http = nil
153
163
 
154
- super(**args, &block)
164
+ super(**args, &)
155
165
  reopen
156
166
  end
157
167
 
@@ -191,6 +201,21 @@ module SemanticLogger
191
201
  post(message)
192
202
  end
193
203
 
204
+ # Forward a batch of log messages to the HTTP Server in a single request.
205
+ # Only used when the appender is created with `batch: true`.
206
+ def batch(logs)
207
+ message = formatter.batch(logs, self)
208
+ logger.trace(message)
209
+ post(message)
210
+ end
211
+
212
+ # The HTTP appender supports batching, but only when explicitly requested
213
+ # via `batch: true`, so that the default behaviour (one request per log
214
+ # message) is preserved.
215
+ def batch_by_default?
216
+ false
217
+ end
218
+
194
219
  private
195
220
 
196
221
  # Use JSON Formatter by default
@@ -37,13 +37,13 @@ module SemanticLogger
37
37
  #
38
38
  # logger = SemanticLogger['test']
39
39
  # logger.info 'Hello World'
40
- def initialize(io, **args, &block)
40
+ def initialize(io, **args, &)
41
41
  @io = io
42
42
  unless @io.respond_to?(:write)
43
43
  raise(ArgumentError, "SemanticLogging::Appender::IO io is not a valid IO instance: #{io.inspect}")
44
44
  end
45
45
 
46
- super(**args, &block)
46
+ super(**args, &)
47
47
  end
48
48
 
49
49
  def log(log)
@@ -60,8 +60,12 @@ module SemanticLogger
60
60
  @io.flush if @io.respond_to?(:flush)
61
61
  end
62
62
 
63
- def console_output?
64
- [$stderr, $stdout].include?(@io)
63
+ def console_stream
64
+ if @io.equal?($stdout)
65
+ :stdout
66
+ elsif @io.equal?($stderr)
67
+ :stderr
68
+ end
65
69
  end
66
70
  end
67
71
  end
@@ -127,7 +127,7 @@ module SemanticLogger
127
127
  ssl_ca_cert: nil, ssl_client_cert: nil, ssl_client_cert_key: nil, ssl_ca_certs_from_system: false,
128
128
  topic: "log_messages", partition: nil, partition_key: nil, key: nil,
129
129
  delivery_threshold: 100, delivery_interval: 10, required_acks: 1,
130
- metrics: true, **args, &block)
130
+ metrics: true, **args, &)
131
131
  @seed_brokers = seed_brokers
132
132
  @client_id = client_id
133
133
  @connect_timeout = connect_timeout
@@ -144,7 +144,7 @@ module SemanticLogger
144
144
  @delivery_interval = delivery_interval
145
145
  @required_acks = required_acks
146
146
 
147
- super(metrics: metrics, **args, &block)
147
+ super(metrics: metrics, **args, &)
148
148
  reopen
149
149
  end
150
150
 
@@ -39,14 +39,12 @@ module SemanticLogger
39
39
  header: {"Content-Type" => "application/json"},
40
40
  path: INGESTION_PATH,
41
41
  **args,
42
- &block)
43
-
44
- super(url: "#{url}/#{path}", formatter: formatter, header: header, **args, &block)
42
+ &)
43
+ super(url: "#{url}/#{path}", formatter: formatter, header: header, **args, &)
45
44
  end
46
45
 
47
46
  def log(log)
48
47
  message = formatter.call(log, self)
49
- puts message
50
48
  logger.trace(message)
51
49
  post(message)
52
50
  end
@@ -36,11 +36,8 @@ module SemanticLogger
36
36
  # Example:
37
37
  # require 'semantic_logger'
38
38
  #
39
- # client = Mongo::MongoClient.new
40
- # database = client['test']
41
- #
42
39
  # appender = SemanticLogger::Appender::MongoDB.new(
43
- # db: database,
40
+ # uri: 'mongodb://127.0.0.1:27017/test',
44
41
  # collection_size: 1024**3 # 1.gigabyte
45
42
  # )
46
43
  # SemanticLogger.add_appender(appender: appender)
@@ -110,7 +107,7 @@ module SemanticLogger
110
107
  collection_size: 1024**3,
111
108
  collection_max: nil,
112
109
  **args,
113
- &block)
110
+ &)
114
111
  @client = Mongo::Client.new(uri, logger: logger)
115
112
  @collection_name = collection_name
116
113
  @options = {
@@ -125,7 +122,7 @@ module SemanticLogger
125
122
  # Create the collection and necessary indexes
126
123
  create_indexes
127
124
 
128
- super(**args, &block)
125
+ super(**args, &)
129
126
  end
130
127
 
131
128
  # After forking an active process call #reopen to re-open
@@ -58,8 +58,8 @@ module SemanticLogger
58
58
  def log(log)
59
59
  begin
60
60
  message = formatter.call(log, self) # Generate the structured log
61
- json_message = message.to_json # Convert the log to JSON
62
- level = log.level.to_s.upcase # Determine the log level
61
+ json_message = Utils.to_json(message) # Convert the log to JSON, repairing any non UTF-8 data
62
+ level = log.level.to_s.upcase # Determine the log level
63
63
  self.class.log_newrelic(json_message, level)
64
64
  rescue JSON::GeneratorError => e
65
65
  warn("Failed to serialize log message to JSON: #{e.message}")