semantic_logger 5.0.0 → 5.2.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +20 -10
  3. data/Rakefile +40 -0
  4. data/docs/api.md +506 -0
  5. data/docs/appenders.md +1074 -0
  6. data/docs/config.md +707 -0
  7. data/docs/index.md +223 -0
  8. data/docs/log.md +116 -0
  9. data/docs/metrics.md +171 -0
  10. data/docs/operations.md +446 -0
  11. data/docs/rails.md +890 -0
  12. data/docs/security.md +286 -0
  13. data/docs/testing.md +287 -0
  14. data/docs/upgrading.md +697 -0
  15. data/lib/semantic_logger/appender/async.rb +18 -5
  16. data/lib/semantic_logger/appender/bugsnag.rb +1 -1
  17. data/lib/semantic_logger/appender/cloudwatch_logs.rb +126 -29
  18. data/lib/semantic_logger/appender/elasticsearch.rb +2 -5
  19. data/lib/semantic_logger/appender/elasticsearch_base.rb +48 -23
  20. data/lib/semantic_logger/appender/elasticsearch_http.rb +15 -8
  21. data/lib/semantic_logger/appender/file.rb +141 -28
  22. data/lib/semantic_logger/appender/graylog.rb +22 -6
  23. data/lib/semantic_logger/appender/honeybadger.rb +1 -1
  24. data/lib/semantic_logger/appender/honeybadger_insights.rb +1 -1
  25. data/lib/semantic_logger/appender/http.rb +70 -11
  26. data/lib/semantic_logger/appender/io.rb +1 -1
  27. data/lib/semantic_logger/appender/kafka.rb +26 -12
  28. data/lib/semantic_logger/appender/loki.rb +13 -3
  29. data/lib/semantic_logger/appender/mongodb.rb +20 -1
  30. data/lib/semantic_logger/appender/new_relic.rb +7 -1
  31. data/lib/semantic_logger/appender/new_relic_logs.rb +11 -12
  32. data/lib/semantic_logger/appender/open_telemetry.rb +4 -3
  33. data/lib/semantic_logger/appender/opensearch.rb +2 -1
  34. data/lib/semantic_logger/appender/rabbitmq.rb +10 -4
  35. data/lib/semantic_logger/appender/sentry.rb +6 -1
  36. data/lib/semantic_logger/appender/sentry_ruby.rb +91 -17
  37. data/lib/semantic_logger/appender/splunk.rb +10 -1
  38. data/lib/semantic_logger/appender/syslog.rb +66 -25
  39. data/lib/semantic_logger/appender/tcp.rb +10 -4
  40. data/lib/semantic_logger/appender/udp.rb +11 -1
  41. data/lib/semantic_logger/appender/wrapper.rb +3 -2
  42. data/lib/semantic_logger/appender.rb +6 -3
  43. data/lib/semantic_logger/appenders.rb +11 -2
  44. data/lib/semantic_logger/base.rb +82 -32
  45. data/lib/semantic_logger/exception_details.rb +50 -0
  46. data/lib/semantic_logger/formatters/base.rb +52 -6
  47. data/lib/semantic_logger/formatters/color.rb +22 -3
  48. data/lib/semantic_logger/formatters/default.rb +28 -6
  49. data/lib/semantic_logger/formatters/logfmt.rb +155 -21
  50. data/lib/semantic_logger/formatters/loki.rb +26 -22
  51. data/lib/semantic_logger/formatters/one_line.rb +67 -3
  52. data/lib/semantic_logger/formatters/open_telemetry.rb +16 -17
  53. data/lib/semantic_logger/formatters/pattern.rb +11 -5
  54. data/lib/semantic_logger/formatters/raw.rb +4 -5
  55. data/lib/semantic_logger/formatters/syslog.rb +110 -17
  56. data/lib/semantic_logger/formatters/syslog_cee.rb +29 -15
  57. data/lib/semantic_logger/levels.rb +10 -1
  58. data/lib/semantic_logger/log.rb +183 -37
  59. data/lib/semantic_logger/loggable.rb +26 -3
  60. data/lib/semantic_logger/metric/new_relic.rb +1 -1
  61. data/lib/semantic_logger/metric/signalfx.rb +12 -8
  62. data/lib/semantic_logger/metric/statsd.rb +15 -7
  63. data/lib/semantic_logger/queue_processor/request.rb +55 -0
  64. data/lib/semantic_logger/queue_processor/throttled_counter.rb +46 -0
  65. data/lib/semantic_logger/queue_processor.rb +170 -68
  66. data/lib/semantic_logger/reporters/minitest.rb +4 -4
  67. data/lib/semantic_logger/semantic_logger.rb +15 -2
  68. data/lib/semantic_logger/subscriber.rb +96 -10
  69. data/lib/semantic_logger/sync_processor.rb +11 -0
  70. data/lib/semantic_logger/test/capture_log_events.rb +9 -6
  71. data/lib/semantic_logger/utils.rb +244 -16
  72. data/lib/semantic_logger/version.rb +1 -1
  73. data/lib/semantic_logger.rb +1 -1
  74. metadata +33 -6
  75. data/lib/semantic_logger/core_ext/thread.rb +0 -30
@@ -21,13 +21,14 @@ module SemanticLogger
21
21
  # Methods forwarded to the queue processor that owns the thread and queue.
22
22
  # Tuning options (max_queue_size, batch_size, non_blocking, ...) are set once at
23
23
  # construction via SemanticLogger.add_appender, so only the values that callers actually
24
- # read back are re-exposed here. lag_* backs the public SemanticLogger.lag_* API.
24
+ # read back are re-exposed here. lag_* and non_blocking= back the public
25
+ # SemanticLogger.lag_* and SemanticLogger.non_blocking= API on the main Processor.
25
26
  def_delegators :@processor,
26
27
  :log, :flush, :close, :thread, :active?, :queue, :max_queue_size,
27
- :capped?, :non_blocking?, :batch?,
28
+ :capped?, :non_blocking?, :non_blocking=, :batch?,
28
29
  :lag_check_interval, :lag_check_interval=,
29
30
  :lag_threshold_s, :lag_threshold_s=,
30
- :processed_count, :dropped_count
31
+ :processed_count, :dropped_count, :failed_count
31
32
 
32
33
  # Appender proxy to allow an existing appender to run asynchronously in a separate thread.
33
34
  #
@@ -73,7 +74,8 @@ module SemanticLogger
73
74
  #
74
75
  # dropped_message_report_seconds: [Integer]
75
76
  # When non_blocking is enabled, log the count of dropped messages to the internal logger
76
- # at most once every this number of seconds.
77
+ # at most once every this number of seconds. Also limits how often messages discarded
78
+ # because their content could not be written are reported, after the first.
77
79
  # Default: 30
78
80
  #
79
81
  # async_max_retries: [Integer]
@@ -87,6 +89,14 @@ module SemanticLogger
87
89
  @processor = QueueProcessor.start(appender: appender, **args)
88
90
  end
89
91
 
92
+ # Returns [String] the class and object address of this proxy, whether it batches, and the
93
+ # wrapped appender's own #inspect, which shows only what that appender says is safe, but none of
94
+ # the instance variables of this proxy or its queue processor. See Subscriber#inspect.
95
+ def inspect
96
+ address = Kernel.instance_method(:to_s).bind_call(self).delete_suffix(">")
97
+ "#{address} batch=#{batch?} appender=#{appender.inspect}>"
98
+ end
99
+
90
100
  # Re-open appender after a fork.
91
101
  def reopen
92
102
  appender.reopen if appender.respond_to?(:reopen)
@@ -104,6 +114,8 @@ module SemanticLogger
104
114
  # processed: [Integer] Cumulative number of log messages written since startup.
105
115
  # dropped: [Integer] Cumulative number of log messages dropped because the queue
106
116
  # was full (only possible when non_blocking is enabled).
117
+ # failed: [Integer] Cumulative number of log messages discarded because their
118
+ # content could not be written (see Subscriber#message_error?).
107
119
  def stats
108
120
  {
109
121
  name: name,
@@ -113,7 +125,8 @@ module SemanticLogger
113
125
  capped: capped?,
114
126
  max_queue_size: capped? ? max_queue_size : nil,
115
127
  processed: processed_count,
116
- dropped: dropped_count
128
+ dropped: dropped_count,
129
+ failed: failed_count
117
130
  }
118
131
  end
119
132
  end
@@ -56,7 +56,7 @@ module SemanticLogger
56
56
 
57
57
  # Send error messages as Runtime exceptions
58
58
  exception = extract_exception(log)
59
- hash = formatter.call(log, self)
59
+ hash = format_log(log)
60
60
  bugsnag_notify(exception, hash, log_level(log.level))
61
61
  true
62
62
  end
@@ -2,7 +2,8 @@ begin
2
2
  require "aws-sdk-cloudwatchlogs"
3
3
  rescue LoadError
4
4
  raise LoadError,
5
- 'Gem aws-sdk-cloudwatchlogs is required for logging to CloudWatch Logs. Please add the gem "aws-sdk-cloudwatchlogs" to your Gemfile.'
5
+ "Gem aws-sdk-cloudwatchlogs is required for logging to CloudWatch Logs. " \
6
+ 'Please add the gem "aws-sdk-cloudwatchlogs" to your Gemfile.'
6
7
  end
7
8
 
8
9
  require "concurrent"
@@ -20,7 +21,15 @@ require "concurrent"
20
21
  module SemanticLogger
21
22
  module Appender
22
23
  class CloudwatchLogs < SemanticLogger::Subscriber
23
- attr_reader :client_kwargs, :group, :create_group, :create_stream, :force_flush_interval_seconds, :max_buffered_events,
24
+ # PutLogEvents limits, from the AWS documentation: a batch holds at most MAX_BATCH_EVENTS
25
+ # events, and at most MAX_BATCH_BYTES counting each event's message in UTF-8 plus
26
+ # EVENT_OVERHEAD_BYTES, in chronological order.
27
+ MAX_BATCH_BYTES = 1_048_576
28
+ MAX_BATCH_EVENTS = 10_000
29
+ EVENT_OVERHEAD_BYTES = 26
30
+
31
+ attr_reader :client_kwargs, :group, :create_group, :create_stream,
32
+ :force_flush_interval_seconds, :max_buffered_events,
24
33
  :task, :client, :buffered_logs
25
34
 
26
35
  # Create CloudWatch Logs Appender
@@ -78,14 +87,19 @@ module SemanticLogger
78
87
  end
79
88
 
80
89
  # Method called to log an event
90
+ #
91
+ # Formats the log message now, so that one that cannot be formatted, or that CloudWatch Logs
92
+ # would reject, raises here for that message alone, and the buffer only holds log events
93
+ # ready to send.
81
94
  def log(log)
82
- buffered_logs << log
83
-
95
+ buffered_logs << log_event(log)
84
96
  put_log_events if buffered_logs.size >= max_buffered_events
97
+ true
85
98
  end
86
99
 
100
+ # Sends the buffered log messages on the calling thread, rather than waiting for the timer.
87
101
  def flush
88
- task.execute while buffered_logs.size.positive?
102
+ put_log_events while buffered_logs.size.positive?
89
103
  end
90
104
 
91
105
  def close
@@ -108,40 +122,123 @@ module SemanticLogger
108
122
  SemanticLogger::Formatters::Json.new
109
123
  end
110
124
 
125
+ # Only the log group and stream: the client options can hold AWS credentials. See
126
+ # Subscriber#inspect.
127
+ def inspect_details
128
+ "group=#{group.inspect} stream=#{stream.inspect}"
129
+ end
130
+
111
131
  private
112
132
 
133
+ # Returns [Hash] the PutLogEvents log event for the log message.
134
+ def log_event(log)
135
+ {timestamp: (log.time.to_f * 1000).floor, message: event_message(format_log(log))}
136
+ end
137
+
138
+ # Returns [String] the formatted log message in UTF-8, cut short to the most that a batch can
139
+ # hold.
140
+ #
141
+ # Raises FormatError for a message that CloudWatch Logs would reject, together with every
142
+ # other message in the same request: one that is not a String, or is empty.
143
+ def event_message(message)
144
+ unless message.is_a?(String)
145
+ raise(FormatError, "Could not format the log message (the formatter returned a #{message.class})")
146
+ end
147
+
148
+ message = Utils.encode_utf8_string(message)
149
+ raise(FormatError, "Could not format the log message (it is empty)") if message.empty?
150
+
151
+ limit = MAX_BATCH_BYTES - EVENT_OVERHEAD_BYTES
152
+ message.bytesize <= limit ? message : message.byteslice(0, limit).scrub("")
153
+ end
154
+
155
+ # Sends the buffered log events, in as many requests as the PutLogEvents limits need.
156
+ #
157
+ # A request that fails is reported to the internal logger rather than raised: this usually
158
+ # runs on the timer thread, which discards errors, and the log events were already taken off
159
+ # the buffer, so AWS rejecting a batch, for example one over the size limit, lost every
160
+ # message in it without a trace.
113
161
  def put_log_events
114
- logs = buffered_logs.shift(max_buffered_events)
162
+ events = buffered_logs.shift(max_buffered_events)
163
+ return if events.none?
115
164
 
116
- return if logs.none?
165
+ batches(events).each { |batch| put_batch(batch) }
166
+ end
117
167
 
168
+ # Returns [Array<Array<Hash>>] the log events in chronological order, as PutLogEvents requires,
169
+ # split into batches that it accepts.
170
+ def batches(events)
171
+ batches = [[]]
172
+ bytes = 0
173
+ # Keeps the order of log events with the same timestamp.
174
+ events.each_with_index.sort_by { |event, index| [event[:timestamp], index] }.each do |event, _index|
175
+ size = event[:message].bytesize + EVENT_OVERHEAD_BYTES
176
+ if batches.last.size >= MAX_BATCH_EVENTS || bytes + size > MAX_BATCH_BYTES
177
+ batches << []
178
+ bytes = 0
179
+ end
180
+ batches.last << event
181
+ bytes += size
182
+ end
183
+ batches.reject(&:empty?)
184
+ end
185
+
186
+ # Sends one batch, reporting any failure to the internal logger, so that the other batches are
187
+ # still sent.
188
+ def put_batch(events)
189
+ response = send_batch(events)
190
+ report_rejected_events(events, response.rejected_log_events_info)
191
+ rescue StandardError => e
192
+ logger.error("Could not send #{events.size} log message(s) to CloudWatch Logs", e)
193
+ end
194
+
195
+ # Returns the PutLogEvents response, after creating the log group or stream when it is missing
196
+ # and this appender may create it.
197
+ def send_batch(events)
198
+ created = []
118
199
  begin
119
- client.put_log_events({
120
- log_group_name: group,
121
- log_stream_name: stream,
122
- log_events: logs.map do |log|
123
- {
124
- timestamp: (log.time.to_f * 1000).floor,
125
- message: formatter.call(log, self)
126
- }
127
- end
128
- })
200
+ client.put_log_events(log_group_name: group, log_stream_name: stream, log_events: events)
129
201
  rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException => e
130
- if e.message.include?("log group does not exist.") && create_group
131
- client.create_log_group({
132
- log_group_name: group
133
- })
134
- retry
135
- elsif e.message.include?("log stream does not exist.") && create_stream
136
- client.create_log_stream({
137
- log_group_name: group,
138
- log_stream_name: stream
139
- })
140
- retry
141
- end
202
+ resource = missing_resource(e)
203
+ raise if resource.nil? || created.include?(resource)
204
+
205
+ create(resource)
206
+ created << resource
207
+ retry
142
208
  end
143
209
  end
144
210
 
211
+ # Returns [:group | :stream | nil] the missing resource that this appender may create.
212
+ def missing_resource(exception)
213
+ if exception.message.include?("log group does not exist.") && create_group
214
+ :group
215
+ elsif exception.message.include?("log stream does not exist.") && create_stream
216
+ :stream
217
+ end
218
+ end
219
+
220
+ def create(resource)
221
+ if resource == :group
222
+ client.create_log_group(log_group_name: group)
223
+ else
224
+ client.create_log_stream(log_group_name: group, log_stream_name: stream)
225
+ end
226
+ rescue Aws::CloudWatchLogs::Errors::ResourceAlreadyExistsException
227
+ # Created meanwhile, for example by another process logging to the same log group.
228
+ nil
229
+ end
230
+
231
+ # PutLogEvents accepts a request in which it rejects some of the log events: those more than 2
232
+ # hours in the future, older than 14 days, or older than the log group's retention period.
233
+ def report_rejected_events(events, rejected)
234
+ return unless rejected
235
+
236
+ logger.warn(
237
+ "CloudWatch Logs rejected some of #{events.size} log message(s) as too new, too old, or expired",
238
+ rejected.to_h
239
+ )
240
+ end
241
+
145
242
  def stream
146
243
  @stream || host
147
244
  end
@@ -2,7 +2,8 @@ begin
2
2
  require "elasticsearch"
3
3
  rescue LoadError
4
4
  raise LoadError,
5
- 'Gem elasticsearch is required for logging to Elasticsearch. Please add the gem "elasticsearch" to your Gemfile.'
5
+ "Gem elasticsearch is required for logging to Elasticsearch. " \
6
+ 'Please add the gem "elasticsearch" to your Gemfile.'
6
7
  end
7
8
 
8
9
  require "semantic_logger/appender/elasticsearch_base"
@@ -26,10 +27,6 @@ module SemanticLogger
26
27
  def client_class
27
28
  ::Elasticsearch::Client
28
29
  end
29
-
30
- def version_supports_type?
31
- Gem::Version.new(::Elasticsearch::VERSION) < Gem::Version.new(7)
32
- end
33
30
  end
34
31
  end
35
32
  end
@@ -6,14 +6,13 @@ module SemanticLogger
6
6
  #
7
7
  # Implements the shared bulk-indexing pipeline used by both the
8
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`).
9
+ # supply the backing client class.
11
10
  #
12
11
  # This class is internal: applications add an appender via
13
12
  # `SemanticLogger.add_appender(appender: :elasticsearch, ...)` or
14
13
  # `appender: :opensearch`, never by referencing this class directly.
15
14
  class ElasticsearchBase < SemanticLogger::Subscriber
16
- attr_accessor :url, :index, :date_pattern, :type, :client, :flush_interval, :timeout_interval, :batch_size,
15
+ attr_accessor :url, :index, :date_pattern, :client, :flush_interval, :timeout_interval, :batch_size,
17
16
  :client_args
18
17
 
19
18
  # Create an Elasticsearch-compatible appender over persistent HTTP(S).
@@ -31,9 +30,9 @@ module SemanticLogger
31
30
  # Default: '%Y.%m.%d'
32
31
  #
33
32
  # 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'
33
+ # Deprecated and ignored. Document `_type` has been unused since
34
+ # Elasticsearch 7 and is ignored by OpenSearch. Passing it logs a
35
+ # deprecation warning; it will be removed in v6.
37
36
  #
38
37
  # level: [:trace | :debug | :info | :warn | :error | :fatal]
39
38
  # Override the log level for this appender.
@@ -124,7 +123,7 @@ module SemanticLogger
124
123
  def initialize(url: "http://localhost:9200",
125
124
  index: "semantic_logger",
126
125
  date_pattern: "%Y.%m.%d",
127
- type: "log",
126
+ type: nil,
128
127
  level: nil,
129
128
  formatter: nil,
130
129
  filter: nil,
@@ -134,40 +133,60 @@ module SemanticLogger
134
133
  data_stream: false,
135
134
  **client_args,
136
135
  &)
136
+ if type
137
+ Kernel.warn(
138
+ "The Elasticsearch/OpenSearch appender `type:` parameter is deprecated and will be removed in v6. " \
139
+ "Document `_type` has been unused since Elasticsearch 7 and is ignored by OpenSearch.",
140
+ category: :deprecated
141
+ )
142
+ end
143
+
137
144
  @url = url
138
145
  @index = index
139
146
  @date_pattern = date_pattern
140
- @type = type
141
147
  @client_args = client_args.dup
142
148
  @client_args[:url] = url if url && !client_args[:hosts]
143
149
  @client_args[:logger] = logger
144
150
  @data_stream = data_stream
145
151
 
146
- super(level: level, formatter: formatter, filter: filter, application: application, environment: environment, host: host, metrics: false, &)
152
+ super(level: level, formatter: formatter, filter: filter, application: application,
153
+ environment: environment, host: host, metrics: false, &)
147
154
  reopen
148
155
  end
149
156
 
157
+ # The url is left to the client gem to parse, since it also takes a comma separated list of
158
+ # urls, an Array of them, or hosts without a scheme.
150
159
  def reopen
151
- @client = client_class.new(@client_args)
160
+ @client = Utils.without_url_in_errors(option: ":url or :hosts") { client_class.new(@client_args) }
152
161
  end
153
162
 
154
163
  # Log to the index for today
155
164
  def log(log)
156
- bulk_payload = formatter.call(log, self)
157
- bulk_write([bulk_index(log), bulk_payload])
165
+ bulk_write([bulk_index(log), bulk_line(log)])
158
166
  true
159
167
  end
160
168
 
161
169
  def batch(logs)
162
170
  messages = []
163
171
  logs.each do |log|
164
- messages << bulk_index(log) << formatter.call(log, self)
172
+ messages << bulk_index(log) << bulk_line(log)
165
173
  end
166
174
 
167
175
  bulk_write(messages)
168
176
  true
169
177
  end
170
178
 
179
+ # The whole batch is formatted into a single bulk request before anything is sent.
180
+ def batch_all_or_nothing?
181
+ true
182
+ end
183
+
184
+ # The url without its credentials, and the index. See Subscriber#inspect.
185
+ def inspect_details
186
+ url_detail = @client_args.key?(:url) ? "url=#{Utils.redacted_url(url).inspect} " : ""
187
+ "#{url_detail}index=#{index.inspect}"
188
+ end
189
+
171
190
  private
172
191
 
173
192
  # The backing client class, e.g. ::Elasticsearch::Client or ::OpenSearch::Client.
@@ -175,12 +194,25 @@ module SemanticLogger
175
194
  raise NotImplementedError, "#{self.class} must implement #client_class"
176
195
  end
177
196
 
197
+ # Returns [String] the log message formatted as a bulk line, serialized by Utils.to_json
198
+ # and handed to the client as a String. The client gem's serializer writes Infinity and NaN
199
+ # as bare words, which is not JSON, and does not repair invalid UTF-8, a structure nested too
200
+ # deeply, or a Struct (written through its #inspect), so such an entry was dropped or its
201
+ # objects' instance variables were written. Serializing is part of formatting, so an error
202
+ # doing so discards the message rather than stalling the appender (see #format_log). A
203
+ # formatter that returns a String is taken to have serialized the line itself.
204
+ def bulk_line(log)
205
+ format_log(log) { |document| document.is_a?(String) ? document : Utils.to_json(document) }
206
+ end
207
+
208
+ # Serializes the action lines; the documents already are (see #bulk_line).
178
209
  def bulk_write(messages)
210
+ body = messages.map { |message| message.is_a?(String) ? message : message.to_json }
179
211
  bulk_result =
180
212
  if @data_stream
181
- @client.bulk(index: index, body: messages)
213
+ @client.bulk(index: index, body: body)
182
214
  else
183
- @client.bulk(body: messages)
215
+ @client.bulk(body: body)
184
216
  end
185
217
 
186
218
  return unless bulk_result["errors"]
@@ -193,20 +225,13 @@ module SemanticLogger
193
225
  expanded_index_name = log.time.strftime("#{index}-#{date_pattern}")
194
226
  return {"create" => {}} if @data_stream
195
227
 
196
- bulk_index = {"index" => {"_index" => expanded_index_name}}
197
- bulk_index["index"].merge!({"_type" => type}) if version_supports_type?
198
- bulk_index
228
+ {"index" => {"_index" => expanded_index_name}}
199
229
  end
200
230
 
201
231
  def default_formatter
202
232
  time_key = @data_stream ? "@timestamp" : :timestamp
203
233
  SemanticLogger::Formatters::Raw.new(time_format: :iso_8601, time_key: time_key)
204
234
  end
205
-
206
- # Modern Elasticsearch (>= 7) and OpenSearch no longer support document `_type`.
207
- def version_supports_type?
208
- false
209
- end
210
235
  end
211
236
  end
212
237
  end
@@ -13,7 +13,7 @@ require "date"
13
13
  module SemanticLogger
14
14
  module Appender
15
15
  class ElasticsearchHttp < SemanticLogger::Appender::Http
16
- attr_accessor :index, :type
16
+ attr_accessor :index
17
17
 
18
18
  # Create Elasticsearch appender over persistent HTTP(S)
19
19
  #
@@ -25,9 +25,9 @@ module SemanticLogger
25
25
  # Default: 'semantic_logger'
26
26
  #
27
27
  # type: [String]
28
- # Document type to associate with logs when they are written.
29
- # Deprecated in Elasticsearch 7.0.0
30
- # Default: 'log'
28
+ # Deprecated and ignored. Document `_type` has been unused since
29
+ # Elasticsearch 7. Passing it logs a deprecation warning; it will be
30
+ # removed in v6.
31
31
  #
32
32
  # level: [:trace | :debug | :info | :warn | :error | :fatal]
33
33
  # Override the log level for this appender.
@@ -52,21 +52,28 @@ module SemanticLogger
52
52
  # Name of this application to appear in log messages.
53
53
  # Default: SemanticLogger.application
54
54
  def initialize(index: "semantic_logger",
55
- type: "log",
55
+ type: nil,
56
56
  url: "http://localhost:9200",
57
57
  **http_args,
58
58
  &)
59
+ if type
60
+ Kernel.warn(
61
+ "The Elasticsearch/OpenSearch appender `type:` parameter is deprecated and will be removed in v6. " \
62
+ "Document `_type` has been unused since Elasticsearch 7 and is ignored by OpenSearch.",
63
+ category: :deprecated
64
+ )
65
+ end
66
+
59
67
  @index = index
60
- @type = type
61
68
  super(url: url, **http_args, &)
62
69
 
63
70
  @request_path = "#{@path.end_with?('/') ? @path : "#{@path}/"}#{@index}-%Y.%m.%d"
64
- @logging_path = "#{@request_path}/#{type}"
71
+ @logging_path = "#{@request_path}/_doc"
65
72
  end
66
73
 
67
74
  # Log to the index for today.
68
75
  def log(log)
69
- post(formatter.call(log, self), log.time.strftime(@logging_path))
76
+ post(format_log(log), log.time.strftime(@logging_path))
70
77
  end
71
78
 
72
79
  # Deletes all log data captured for a day.