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.
- checksums.yaml +4 -4
- data/README.md +51 -81
- data/Rakefile +39 -3
- data/docs/api.md +480 -0
- data/docs/appenders.md +990 -0
- data/docs/config.md +605 -0
- data/docs/index.md +197 -0
- data/docs/log.md +108 -0
- data/docs/metrics.md +168 -0
- data/docs/operations.md +400 -0
- data/docs/rails.md +816 -0
- data/docs/security.md +119 -0
- data/docs/testing.md +285 -0
- data/docs/upgrading.md +409 -0
- data/lib/semantic_logger/appender/async.rb +86 -173
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +8 -6
- data/lib/semantic_logger/appender/elasticsearch.rb +8 -187
- data/lib/semantic_logger/appender/elasticsearch_base.rb +212 -0
- data/lib/semantic_logger/appender/elasticsearch_http.rb +16 -9
- data/lib/semantic_logger/appender/file.rb +25 -4
- data/lib/semantic_logger/appender/graylog.rb +2 -2
- data/lib/semantic_logger/appender/http.rb +27 -2
- data/lib/semantic_logger/appender/io.rb +8 -4
- data/lib/semantic_logger/appender/kafka.rb +2 -2
- data/lib/semantic_logger/appender/loki.rb +2 -4
- data/lib/semantic_logger/appender/mongodb.rb +3 -6
- data/lib/semantic_logger/appender/new_relic.rb +6 -0
- data/lib/semantic_logger/appender/new_relic_logs.rb +4 -3
- data/lib/semantic_logger/appender/open_telemetry.rb +10 -7
- data/lib/semantic_logger/appender/opensearch.rb +36 -0
- data/lib/semantic_logger/appender/rabbitmq.rb +5 -4
- data/lib/semantic_logger/appender/sentry.rb +5 -0
- data/lib/semantic_logger/appender/sentry_ruby.rb +2 -2
- data/lib/semantic_logger/appender/splunk.rb +2 -2
- data/lib/semantic_logger/appender/splunk_http.rb +3 -3
- data/lib/semantic_logger/appender/syslog.rb +11 -7
- data/lib/semantic_logger/appender/tcp.rb +4 -3
- data/lib/semantic_logger/appender/udp.rb +2 -2
- data/lib/semantic_logger/appender/wrapper.rb +5 -4
- data/lib/semantic_logger/appender.rb +36 -22
- data/lib/semantic_logger/appenders.rb +27 -5
- data/lib/semantic_logger/base.rb +100 -23
- data/lib/semantic_logger/concerns/compatibility.rb +2 -2
- data/lib/semantic_logger/core_ext/process.rb +34 -0
- data/lib/semantic_logger/formatters/base.rb +46 -7
- data/lib/semantic_logger/formatters/color.rb +9 -3
- data/lib/semantic_logger/formatters/default.rb +11 -4
- data/lib/semantic_logger/formatters/ecs.rb +151 -0
- data/lib/semantic_logger/formatters/fluentd.rb +15 -4
- data/lib/semantic_logger/formatters/json.rb +6 -1
- data/lib/semantic_logger/formatters/logfmt.rb +6 -4
- data/lib/semantic_logger/formatters/loki.rb +4 -4
- data/lib/semantic_logger/formatters/open_telemetry.rb +15 -5
- data/lib/semantic_logger/formatters/pattern.rb +237 -0
- data/lib/semantic_logger/formatters/raw.rb +2 -2
- data/lib/semantic_logger/formatters/signalfx.rb +2 -2
- data/lib/semantic_logger/formatters/syslog.rb +16 -4
- data/lib/semantic_logger/formatters/syslog_cee.rb +3 -2
- data/lib/semantic_logger/formatters.rb +2 -0
- data/lib/semantic_logger/log.rb +18 -4
- data/lib/semantic_logger/logger.rb +2 -2
- data/lib/semantic_logger/metric/new_relic.rb +2 -2
- data/lib/semantic_logger/metric/signalfx.rb +6 -4
- data/lib/semantic_logger/metric/statsd.rb +2 -2
- data/lib/semantic_logger/processor.rb +21 -0
- data/lib/semantic_logger/queue_processor.rb +372 -0
- data/lib/semantic_logger/reporters/minitest.rb +8 -8
- data/lib/semantic_logger/semantic_logger.rb +103 -11
- data/lib/semantic_logger/subscriber.rb +15 -2
- data/lib/semantic_logger/sync_processor.rb +25 -3
- data/lib/semantic_logger/test/capture_log_events.rb +3 -3
- data/lib/semantic_logger/test/minitest.rb +8 -4
- data/lib/semantic_logger/test/rspec.rb +249 -0
- data/lib/semantic_logger/utils.rb +83 -4
- data/lib/semantic_logger/version.rb +1 -1
- data/lib/semantic_logger.rb +9 -0
- metadata +28 -8
- 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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
&
|
|
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(
|
|
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
|
-
|
|
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 "
|
|
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 <
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
179
|
-
|
|
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
|
|
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
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
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:
|
|
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
|
-
|
|
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}
|
|
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, &
|
|
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, &
|
|
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 =
|
|
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
|
-
&
|
|
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, &
|
|
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
|
-
&
|
|
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, &
|
|
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
|