semantic_logger 4.18.0 → 5.1.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -81
  3. data/Rakefile +39 -3
  4. data/docs/api.md +480 -0
  5. data/docs/appenders.md +990 -0
  6. data/docs/config.md +605 -0
  7. data/docs/index.md +197 -0
  8. data/docs/log.md +108 -0
  9. data/docs/metrics.md +168 -0
  10. data/docs/operations.md +400 -0
  11. data/docs/rails.md +816 -0
  12. data/docs/security.md +119 -0
  13. data/docs/testing.md +285 -0
  14. data/docs/upgrading.md +409 -0
  15. data/lib/semantic_logger/appender/async.rb +86 -173
  16. data/lib/semantic_logger/appender/cloudwatch_logs.rb +8 -6
  17. data/lib/semantic_logger/appender/elasticsearch.rb +8 -187
  18. data/lib/semantic_logger/appender/elasticsearch_base.rb +212 -0
  19. data/lib/semantic_logger/appender/elasticsearch_http.rb +16 -9
  20. data/lib/semantic_logger/appender/file.rb +25 -4
  21. data/lib/semantic_logger/appender/graylog.rb +2 -2
  22. data/lib/semantic_logger/appender/http.rb +27 -2
  23. data/lib/semantic_logger/appender/io.rb +8 -4
  24. data/lib/semantic_logger/appender/kafka.rb +2 -2
  25. data/lib/semantic_logger/appender/loki.rb +2 -4
  26. data/lib/semantic_logger/appender/mongodb.rb +3 -6
  27. data/lib/semantic_logger/appender/new_relic.rb +6 -0
  28. data/lib/semantic_logger/appender/new_relic_logs.rb +4 -3
  29. data/lib/semantic_logger/appender/open_telemetry.rb +10 -7
  30. data/lib/semantic_logger/appender/opensearch.rb +36 -0
  31. data/lib/semantic_logger/appender/rabbitmq.rb +5 -4
  32. data/lib/semantic_logger/appender/sentry.rb +5 -0
  33. data/lib/semantic_logger/appender/sentry_ruby.rb +2 -2
  34. data/lib/semantic_logger/appender/splunk.rb +2 -2
  35. data/lib/semantic_logger/appender/splunk_http.rb +3 -3
  36. data/lib/semantic_logger/appender/syslog.rb +11 -7
  37. data/lib/semantic_logger/appender/tcp.rb +4 -3
  38. data/lib/semantic_logger/appender/udp.rb +2 -2
  39. data/lib/semantic_logger/appender/wrapper.rb +5 -4
  40. data/lib/semantic_logger/appender.rb +36 -22
  41. data/lib/semantic_logger/appenders.rb +27 -5
  42. data/lib/semantic_logger/base.rb +100 -23
  43. data/lib/semantic_logger/concerns/compatibility.rb +2 -2
  44. data/lib/semantic_logger/core_ext/process.rb +34 -0
  45. data/lib/semantic_logger/formatters/base.rb +46 -7
  46. data/lib/semantic_logger/formatters/color.rb +9 -3
  47. data/lib/semantic_logger/formatters/default.rb +11 -4
  48. data/lib/semantic_logger/formatters/ecs.rb +151 -0
  49. data/lib/semantic_logger/formatters/fluentd.rb +15 -4
  50. data/lib/semantic_logger/formatters/json.rb +6 -1
  51. data/lib/semantic_logger/formatters/logfmt.rb +6 -4
  52. data/lib/semantic_logger/formatters/loki.rb +4 -4
  53. data/lib/semantic_logger/formatters/open_telemetry.rb +15 -5
  54. data/lib/semantic_logger/formatters/pattern.rb +237 -0
  55. data/lib/semantic_logger/formatters/raw.rb +2 -2
  56. data/lib/semantic_logger/formatters/signalfx.rb +2 -2
  57. data/lib/semantic_logger/formatters/syslog.rb +16 -4
  58. data/lib/semantic_logger/formatters/syslog_cee.rb +3 -2
  59. data/lib/semantic_logger/formatters.rb +2 -0
  60. data/lib/semantic_logger/log.rb +18 -4
  61. data/lib/semantic_logger/logger.rb +2 -2
  62. data/lib/semantic_logger/metric/new_relic.rb +2 -2
  63. data/lib/semantic_logger/metric/signalfx.rb +6 -4
  64. data/lib/semantic_logger/metric/statsd.rb +2 -2
  65. data/lib/semantic_logger/processor.rb +21 -0
  66. data/lib/semantic_logger/queue_processor.rb +372 -0
  67. data/lib/semantic_logger/reporters/minitest.rb +8 -8
  68. data/lib/semantic_logger/semantic_logger.rb +103 -11
  69. data/lib/semantic_logger/subscriber.rb +15 -2
  70. data/lib/semantic_logger/sync_processor.rb +25 -3
  71. data/lib/semantic_logger/test/capture_log_events.rb +3 -3
  72. data/lib/semantic_logger/test/minitest.rb +8 -4
  73. data/lib/semantic_logger/test/rspec.rb +249 -0
  74. data/lib/semantic_logger/utils.rb +83 -4
  75. data/lib/semantic_logger/version.rb +1 -1
  76. data/lib/semantic_logger.rb +9 -0
  77. metadata +28 -8
  78. data/lib/semantic_logger/appender/async_batch.rb +0 -93
@@ -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,8 @@ 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
+ attr_reader :client_kwargs, :group, :create_group, :create_stream,
25
+ :force_flush_interval_seconds, :max_buffered_events,
24
26
  :task, :client, :buffered_logs
25
27
 
26
28
  # Create CloudWatch Logs Appender
@@ -54,7 +56,7 @@ module SemanticLogger
54
56
  # Note that currently CloudWatch Logs has 10000 hard limit.
55
57
  # Default: 4000
56
58
  def initialize(
57
- *args,
59
+ *,
58
60
  group:,
59
61
  client_kwargs: {},
60
62
  stream: nil,
@@ -62,8 +64,8 @@ module SemanticLogger
62
64
  create_stream: true,
63
65
  force_flush_interval_seconds: 5,
64
66
  max_buffered_events: 4_000,
65
- **kwargs,
66
- &block
67
+ **,
68
+ &
67
69
  )
68
70
  @group = group
69
71
  @client_kwargs = client_kwargs
@@ -73,7 +75,7 @@ module SemanticLogger
73
75
  @force_flush_interval_seconds = force_flush_interval_seconds
74
76
  @max_buffered_events = max_buffered_events
75
77
 
76
- super(*args, **kwargs, &block)
78
+ super(*, **, &)
77
79
  reopen
78
80
  end
79
81
 
@@ -2,10 +2,11 @@ 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
- require "date"
9
+ require "semantic_logger/appender/elasticsearch_base"
9
10
 
10
11
  # Forward all log messages to Elasticsearch.
11
12
  #
@@ -17,194 +18,14 @@ require "date"
17
18
  # )
18
19
  module SemanticLogger
19
20
  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
21
+ class Elasticsearch < ElasticsearchBase
22
+ # Backwards compatible accessor for the client arguments.
23
+ alias elasticsearch_args client_args
175
24
 
176
25
  private
177
26
 
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)
204
- end
205
-
206
- def version_supports_type?
207
- Gem::Version.new(::Elasticsearch::VERSION) < Gem::Version.new(7)
27
+ def client_class
28
+ ::Elasticsearch::Client
208
29
  end
209
30
  end
210
31
  end
@@ -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.
10
+ #
11
+ # This class is internal: applications add an appender via
12
+ # `SemanticLogger.add_appender(appender: :elasticsearch, ...)` or
13
+ # `appender: :opensearch`, never by referencing this class directly.
14
+ class ElasticsearchBase < SemanticLogger::Subscriber
15
+ attr_accessor :url, :index, :date_pattern, :client, :flush_interval, :timeout_interval, :batch_size,
16
+ :client_args
17
+
18
+ # Create an Elasticsearch-compatible appender over persistent HTTP(S).
19
+ #
20
+ # Parameters:
21
+ # index: [String]
22
+ # Prefix of the index to store the logs in.
23
+ # The final index appends the date so that indexes are used per day.
24
+ # I.e. The final index will look like 'semantic_logger-YYYY.MM.DD'
25
+ # Default: 'semantic_logger'
26
+ #
27
+ # date_pattern: [String]
28
+ # The time format used to generate the full index name. Useful
29
+ # if you want monthly indexes ('%Y.%m') or weekly ('%Y.%W').
30
+ # Default: '%Y.%m.%d'
31
+ #
32
+ # type: [String]
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.
36
+ #
37
+ # level: [:trace | :debug | :info | :warn | :error | :fatal]
38
+ # Override the log level for this appender.
39
+ # Default: SemanticLogger.default_level
40
+ #
41
+ # formatter: [Object|Proc|Symbol|Hash]
42
+ # An instance of a class that implements #call, or a Proc to be used to format
43
+ # the output from this appender
44
+ # Default: :raw_json (See: #call)
45
+ #
46
+ # filter: [Regexp|Proc]
47
+ # RegExp: Only include log messages where the class name matches the supplied.
48
+ # regular expression. All other messages will be ignored.
49
+ # Proc: Only include log messages where the supplied Proc returns true
50
+ # The Proc must return true or false.
51
+ #
52
+ # host: [String]
53
+ # Name of this host to appear in log messages.
54
+ # Default: SemanticLogger.host
55
+ #
56
+ # application: [String]
57
+ # Name of this application to appear in log messages.
58
+ # Default: SemanticLogger.application
59
+ #
60
+ # Client Parameters (passed through to the backing client):
61
+ # url: [String]
62
+ # Fully qualified address to the service.
63
+ # Default: 'http://localhost:9200'
64
+ #
65
+ # hosts: [String|Hash|Array]
66
+ # Single host passed as a String or Hash, or multiple hosts
67
+ # passed as an Array; `host` or `url` keys are also valid.
68
+ # Note:
69
+ # :url above is ignored when supplying this option.
70
+ #
71
+ # resurrect_after [Float]
72
+ # After how many seconds a dead connection should be tried again.
73
+ #
74
+ # reload_connections [true|false|Integer]
75
+ # Reload connections after X requests.
76
+ # Default: false
77
+ #
78
+ # randomize_hosts [true|false]
79
+ # Shuffle connections on initialization and reload.
80
+ # Default: false
81
+ #
82
+ # sniffer_timeout [Integer]
83
+ # Timeout for reloading connections in seconds.
84
+ # Default: 1
85
+ #
86
+ # retry_on_failure [true|false|Integer]
87
+ # Retry X times when request fails before raising and exception.
88
+ # Default: false
89
+ #
90
+ # retry_on_status [Array<Number>]
91
+ # Retry when specific status codes are returned.
92
+ #
93
+ # reload_on_failure [true|false]
94
+ # Reload connections after failure.
95
+ # Default: false
96
+ #
97
+ # request_timeout [Integer]
98
+ # The request timeout to be passed to transport in options.
99
+ #
100
+ # adapter [Symbol]
101
+ # A specific adapter for Faraday (e.g. `:patron`)
102
+ #
103
+ # transport_options [Hash]
104
+ # Options to be passed to the `Faraday::Connection` constructor.
105
+ #
106
+ # transport_class [Constant]
107
+ # A specific transport class to use, will be initialized by
108
+ # the client and passed hosts and all arguments.
109
+ #
110
+ # transport [Object]
111
+ # A specific transport instance.
112
+ #
113
+ # serializer_class [Constant]
114
+ # A specific serializer class to use, will be initialized by
115
+ # the transport and passed the transport instance.
116
+ #
117
+ # selector
118
+ # An instance of a connection selector strategy.
119
+ #
120
+ # send_get_body_as [String]
121
+ # Specify the HTTP method to use for GET requests with a body.
122
+ # Default: 'GET'
123
+ def initialize(url: "http://localhost:9200",
124
+ index: "semantic_logger",
125
+ date_pattern: "%Y.%m.%d",
126
+ type: nil,
127
+ level: nil,
128
+ formatter: nil,
129
+ filter: nil,
130
+ application: nil,
131
+ environment: nil,
132
+ host: nil,
133
+ data_stream: false,
134
+ **client_args,
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
+
144
+ @url = url
145
+ @index = index
146
+ @date_pattern = date_pattern
147
+ @client_args = client_args.dup
148
+ @client_args[:url] = url if url && !client_args[:hosts]
149
+ @client_args[:logger] = logger
150
+ @data_stream = data_stream
151
+
152
+ super(level: level, formatter: formatter, filter: filter, application: application,
153
+ environment: environment, host: host, metrics: false, &)
154
+ reopen
155
+ end
156
+
157
+ def reopen
158
+ @client = client_class.new(@client_args)
159
+ end
160
+
161
+ # Log to the index for today
162
+ def log(log)
163
+ bulk_payload = formatter.call(log, self)
164
+ bulk_write([bulk_index(log), bulk_payload])
165
+ true
166
+ end
167
+
168
+ def batch(logs)
169
+ messages = []
170
+ logs.each do |log|
171
+ messages << bulk_index(log) << formatter.call(log, self)
172
+ end
173
+
174
+ bulk_write(messages)
175
+ true
176
+ end
177
+
178
+ private
179
+
180
+ # The backing client class, e.g. ::Elasticsearch::Client or ::OpenSearch::Client.
181
+ def client_class
182
+ raise NotImplementedError, "#{self.class} must implement #client_class"
183
+ end
184
+
185
+ def bulk_write(messages)
186
+ bulk_result =
187
+ if @data_stream
188
+ @client.bulk(index: index, body: messages)
189
+ else
190
+ @client.bulk(body: messages)
191
+ end
192
+
193
+ return unless bulk_result["errors"]
194
+
195
+ failed = bulk_result["items"].reject { |x| x["status"] == 201 }
196
+ logger.error("#{self.class.name}: Write failed. Messages discarded. : #{failed}")
197
+ end
198
+
199
+ def bulk_index(log)
200
+ expanded_index_name = log.time.strftime("#{index}-#{date_pattern}")
201
+ return {"create" => {}} if @data_stream
202
+
203
+ {"index" => {"_index" => expanded_index_name}}
204
+ end
205
+
206
+ def default_formatter
207
+ time_key = @data_stream ? "@timestamp" : :timestamp
208
+ SemanticLogger::Formatters::Raw.new(time_format: :iso_8601, time_key: time_key)
209
+ end
210
+ end
211
+ end
212
+ 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,16 +52,23 @@ 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
- &block)
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
- super(url: url, **http_args, &block)
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.
@@ -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,17 @@ 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, &)
153
+
154
+ # Open the file at creation time so that misconfiguration (bad directory,
155
+ # insufficient permissions, ...) raises immediately in SemanticLogger.add_appender,
156
+ # instead of surfacing later on the appender thread via the internal logger.
157
+ reopen
145
158
  end
146
159
 
147
160
  # After forking an active process call #reopen to re-open
@@ -170,7 +183,15 @@ module SemanticLogger
170
183
 
171
184
  options = ::File::WRONLY | ::File::CREAT
172
185
  options |= ::File::APPEND if append
173
- @file = ::File.open(current_file_name, options)
186
+ @file =
187
+ if permissions
188
+ ::File.open(current_file_name, options, permissions)
189
+ else
190
+ ::File.open(current_file_name, options)
191
+ end
192
+ # File.open only applies the permissions when creating the file, so also
193
+ # enforce them on an already existing log file.
194
+ @file.chmod(permissions) if permissions
174
195
  # Force all log entries to write immediately without buffering
175
196
  # Allows multiple processes to write to the same log file simultaneously
176
197
  @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