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.
- checksums.yaml +4 -4
- data/README.md +42 -81
- data/Rakefile +10 -3
- data/lib/semantic_logger/appender/async.rb +86 -173
- data/lib/semantic_logger/appender/cloudwatch_logs.rb +4 -4
- data/lib/semantic_logger/appender/elasticsearch.rb +6 -182
- data/lib/semantic_logger/appender/elasticsearch_base.rb +212 -0
- data/lib/semantic_logger/appender/elasticsearch_http.rb +2 -2
- data/lib/semantic_logger/appender/file.rb +20 -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_logs.rb +2 -2
- data/lib/semantic_logger/appender/open_telemetry.rb +8 -6
- data/lib/semantic_logger/appender/opensearch.rb +35 -0
- data/lib/semantic_logger/appender/rabbitmq.rb +3 -3
- 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 +5 -4
- data/lib/semantic_logger/appender/tcp.rb +2 -2
- data/lib/semantic_logger/appender/udp.rb +2 -2
- data/lib/semantic_logger/appender/wrapper.rb +4 -4
- data/lib/semantic_logger/appender.rb +30 -19
- data/lib/semantic_logger/appenders.rb +26 -5
- data/lib/semantic_logger/base.rb +100 -21
- 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 +6 -3
- data/lib/semantic_logger/formatters/default.rb +6 -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 +2 -2
- 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 +235 -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 +14 -3
- data/lib/semantic_logger/formatters/syslog_cee.rb +1 -1
- 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 +2 -2
- data/lib/semantic_logger/metric/statsd.rb +2 -2
- data/lib/semantic_logger/processor.rb +21 -0
- data/lib/semantic_logger/queue_processor.rb +369 -0
- data/lib/semantic_logger/reporters/minitest.rb +4 -4
- 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 +2 -2
- 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 +17 -8
- 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 "
|
|
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 <
|
|
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
|
|
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
|
|
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)
|
|
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
|
-
&
|
|
58
|
+
&)
|
|
59
59
|
@index = index
|
|
60
60
|
@type = type
|
|
61
|
-
super(url: url, **http_args, &
|
|
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, &
|
|
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, &
|
|
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 =
|
|
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
|
-
&
|
|
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
|
|
@@ -37,13 +37,13 @@ module SemanticLogger
|
|
|
37
37
|
#
|
|
38
38
|
# logger = SemanticLogger['test']
|
|
39
39
|
# logger.info 'Hello World'
|
|
40
|
-
def initialize(io, **args, &
|
|
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, &
|
|
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
|
|
64
|
-
|
|
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, &
|
|
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, &
|
|
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
|
-
&
|
|
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
|
-
#
|
|
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
|
-
&
|
|
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, &
|
|
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 =
|
|
62
|
-
level = log.level.to_s.upcase
|
|
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}")
|