semantic_logger 4.8.2 → 4.9.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 +41 -11
- data/lib/semantic_logger/appender/elasticsearch.rb +20 -4
- data/lib/semantic_logger/appender/file.rb +247 -75
- data/lib/semantic_logger/appender/io.rb +68 -0
- data/lib/semantic_logger/appender.rb +6 -3
- data/lib/semantic_logger/base.rb +2 -1
- data/lib/semantic_logger/formatters/base.rb +1 -0
- data/lib/semantic_logger/formatters/logfmt.rb +26 -8
- data/lib/semantic_logger/processor.rb +1 -1
- data/lib/semantic_logger/semantic_logger.rb +4 -4
- data/lib/semantic_logger/subscriber.rb +10 -0
- data/lib/semantic_logger/sync_processor.rb +1 -1
- data/lib/semantic_logger/test/capture_log_events.rb +34 -0
- data/lib/semantic_logger/version.rb +1 -1
- data/lib/semantic_logger.rb +4 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e586c9e92e30d49d498a13ac996e1ac4f17f51f0da5817354866c64d9d5ddab
|
4
|
+
data.tar.gz: 27daca7b6f2af20d521c004aa64df670a6026b8e845e89f022924375fc85e70e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aef0b209aadb844bfbd27c8fca2119172119037cf73c4b3254d273f66f382114890c81617536b3a29aa6b3d19e65ba855aa48658ea86ca9c6df630561cb3cadf
|
7
|
+
data.tar.gz: cd341bbb661bf9c3ece3c95d63feef49c18af016fe062d3bd03a1127586598d9f3c086137be456113448b5d4d310b5261dbf96bf42213c8ad1093323a7d321e0
|
data/README.md
CHANGED
@@ -9,16 +9,6 @@ Semantic Logger is a feature rich logging framework, and replacement for existin
|
|
9
9
|
|
10
10
|
[Semantic Logger Guide](https://logger.rocketjob.io/)
|
11
11
|
|
12
|
-
## Upgrading to Semantic Logger v4.4
|
13
|
-
|
14
|
-
With some forking frameworks it is necessary to call `reopen` after the fork. With v4.4 the
|
15
|
-
workaround for Ruby 2.5 crashes is no longer needed.
|
16
|
-
I.e. Please remove the following line if being called anywhere:
|
17
|
-
|
18
|
-
~~~ruby
|
19
|
-
SemanticLogger::Processor.instance.instance_variable_set(:@queue, Queue.new)
|
20
|
-
~~~
|
21
|
-
|
22
12
|
## Logging Destinations
|
23
13
|
|
24
14
|
Logging to the following destinations are all supported "out-of-the-box":
|
@@ -71,7 +61,47 @@ and are therefore not automatically included by this gem:
|
|
71
61
|
- Elasticsearch Appender: gem 'elasticsearch'
|
72
62
|
- Kafka Appender: gem 'ruby-kafka'
|
73
63
|
|
74
|
-
##
|
64
|
+
## Upgrading to Semantic Logger v4.9
|
65
|
+
|
66
|
+
These changes should not be noticeable by the majority of users of Semantic Logger, since
|
67
|
+
they are to the internal API. It is possible that advanced users may be using these internal
|
68
|
+
API's directly.
|
69
|
+
|
70
|
+
This does not affect any calls to the public api `SemanticLogger.add_appender`.
|
71
|
+
|
72
|
+
File and IO are now separate appenders. When creating the File appender explicitly, its arguments
|
73
|
+
have changed. For example, when requesting an IO stream, it needs to be changed from:
|
74
|
+
|
75
|
+
~~~ruby
|
76
|
+
SemanticLogger::Appender::File.new(io: $stderr)
|
77
|
+
~~~
|
78
|
+
to:
|
79
|
+
~~~ruby
|
80
|
+
SemanticLogger::Appender::IO.new($stderr)
|
81
|
+
~~~
|
82
|
+
|
83
|
+
Additionally, this needs to be changed from:
|
84
|
+
~~~ruby
|
85
|
+
SemanticLogger::Appender::File.new(file_name: "file.log")
|
86
|
+
~~~
|
87
|
+
to:
|
88
|
+
~~~ruby
|
89
|
+
SemanticLogger::Appender::File.new("file.log")
|
90
|
+
~~~
|
91
|
+
|
92
|
+
Rails Semantic Logger, if used, needs to be upgraded to v4.9 when upgrading to Semantic Logger v4.9.
|
93
|
+
|
94
|
+
## Upgrading to Semantic Logger v4.4
|
95
|
+
|
96
|
+
With some forking frameworks it is necessary to call `reopen` after the fork. With v4.4 the
|
97
|
+
workaround for Ruby 2.5 crashes is no longer needed.
|
98
|
+
I.e. Please remove the following line if being called anywhere:
|
99
|
+
|
100
|
+
~~~ruby
|
101
|
+
SemanticLogger::Processor.instance.instance_variable_set(:@queue, Queue.new)
|
102
|
+
~~~
|
103
|
+
|
104
|
+
## Upgrading to Semantic Logger v4.0
|
75
105
|
|
76
106
|
The following changes need to be made when upgrading to V4:
|
77
107
|
- Ruby V2.3 / JRuby V9.1 is now the minimum runtime version.
|
@@ -135,7 +135,7 @@ module SemanticLogger
|
|
135
135
|
application: nil,
|
136
136
|
environment: nil,
|
137
137
|
host: nil,
|
138
|
-
|
138
|
+
data_stream: false,
|
139
139
|
**elasticsearch_args,
|
140
140
|
&block)
|
141
141
|
|
@@ -146,6 +146,7 @@ module SemanticLogger
|
|
146
146
|
@elasticsearch_args = elasticsearch_args.dup
|
147
147
|
@elasticsearch_args[:url] = url if url && !elasticsearch_args[:hosts]
|
148
148
|
@elasticsearch_args[:logger] = logger
|
149
|
+
@data_stream = data_stream
|
149
150
|
|
150
151
|
super(level: level, formatter: formatter, filter: filter, application: application, environment: environment, host: host, metrics: false, &block)
|
151
152
|
reopen
|
@@ -175,7 +176,12 @@ module SemanticLogger
|
|
175
176
|
private
|
176
177
|
|
177
178
|
def write_to_elasticsearch(messages)
|
178
|
-
bulk_result = @
|
179
|
+
bulk_result = if @data_stream
|
180
|
+
@client.bulk(index: index, body: messages)
|
181
|
+
else
|
182
|
+
@client.bulk(body: messages)
|
183
|
+
end
|
184
|
+
|
179
185
|
return unless bulk_result["errors"]
|
180
186
|
|
181
187
|
failed = bulk_result["items"].reject { |x| x["status"] == 201 }
|
@@ -184,11 +190,21 @@ module SemanticLogger
|
|
184
190
|
|
185
191
|
def bulk_index(log)
|
186
192
|
expanded_index_name = log.time.strftime("#{index}-#{date_pattern}")
|
187
|
-
|
193
|
+
if @data_stream
|
194
|
+
{"create" => {}}
|
195
|
+
else
|
196
|
+
{"index" => {"_index" => expanded_index_name, "_type" => type}}
|
197
|
+
end
|
188
198
|
end
|
189
199
|
|
190
200
|
def default_formatter
|
191
|
-
|
201
|
+
time_key = if @data_stream
|
202
|
+
"@timestamp"
|
203
|
+
else
|
204
|
+
:timestamp
|
205
|
+
end
|
206
|
+
|
207
|
+
SemanticLogger::Formatters::Raw.new(time_format: :iso_8601, time_key: time_key)
|
192
208
|
end
|
193
209
|
end
|
194
210
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "date"
|
1
2
|
# File appender
|
2
3
|
#
|
3
4
|
# Writes log messages to a file or open iostream
|
@@ -5,114 +6,285 @@
|
|
5
6
|
module SemanticLogger
|
6
7
|
module Appender
|
7
8
|
class File < SemanticLogger::Subscriber
|
8
|
-
|
9
|
+
attr_accessor :file_name, :retry_count, :append, :exclusive_lock, :encoding,
|
10
|
+
:reopen_period, :reopen_count, :reopen_size
|
11
|
+
attr_reader :log_count, :log_size, :current_file_name, :reopen_at
|
12
|
+
|
13
|
+
# Create an appender to log to a named file.
|
9
14
|
#
|
10
15
|
# Parameters
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# Or,
|
14
|
-
# :io [IO]
|
15
|
-
# An IO stream to which to write the log messages to.
|
16
|
-
#
|
17
|
-
# :level [:trace | :debug | :info | :warn | :error | :fatal]
|
18
|
-
# Override the log level for this appender.
|
19
|
-
# Default: SemanticLogger.default_level
|
20
|
-
#
|
21
|
-
# :formatter: [Object|Proc]
|
22
|
-
# An instance of a class that implements #call, or a Proc to be used to format
|
23
|
-
# the output from this appender
|
24
|
-
# Default: Use the built-in formatter (See: #call)
|
25
|
-
#
|
26
|
-
# :filter [Regexp|Proc]
|
27
|
-
# RegExp: Only include log messages where the class name matches the supplied
|
28
|
-
# regular expression. All other messages will be ignored.
|
29
|
-
# Proc: Only include log messages where the supplied Proc returns true
|
30
|
-
# The Proc must return true or false.
|
16
|
+
# file_name [String]
|
17
|
+
# Name of the file to write to.
|
31
18
|
#
|
32
|
-
#
|
33
|
-
#
|
19
|
+
# File name format directives:
|
20
|
+
# %p - Process Id
|
21
|
+
# %n - Short hostname (SemanticLogger.host). Everything before the first period in the hostname.
|
22
|
+
# %N - Full hostname (SemanticLogger.host)
|
23
|
+
# %a - Application name (SemanticLogger.application)
|
24
|
+
# %e - Environment name (SemanticLogger.environment)
|
25
|
+
# %D - Current Date. Equivalent to "%Y%m%d"
|
26
|
+
# %T - Current Time. Equivalent to "%H%M%S"
|
27
|
+
# %% - Literal `%` character
|
34
28
|
#
|
35
|
-
#
|
36
|
-
#
|
29
|
+
# Date:
|
30
|
+
# %Y - Year with century
|
31
|
+
# %C - year / 100 (round down. 20 in 2009)
|
32
|
+
# %y - year % 100 (00..99)
|
33
|
+
# %m - Month of the year, zero-padded (01..12)
|
34
|
+
# %d - Day of the month, zero-padded (01..31)
|
35
|
+
# %j - Day of the year (001..366)
|
36
|
+
# %U - Week number of the year. The week starts with Sunday. (00..53)
|
37
|
+
# %W - Week number of the year. The week starts with Monday. (00..53)
|
37
38
|
#
|
38
|
-
#
|
39
|
-
#
|
39
|
+
# Time:
|
40
|
+
# %H - 24 Hour of the day, zero-padded (00..23)
|
41
|
+
# %M - Minute of the hour (00..59)
|
42
|
+
# %S - Second of the minute (00..60)
|
40
43
|
#
|
41
|
-
#
|
42
|
-
#
|
44
|
+
# Examples:
|
45
|
+
# Create a log file name consisting of the short host name, process id, date, and time.
|
46
|
+
# "log/production-%n-%p-%D-%T.log"
|
43
47
|
#
|
44
|
-
#
|
45
|
-
#
|
48
|
+
# :level [:trace | :debug | :info | :warn | :error | :fatal]
|
49
|
+
# Override the log level for this appender.
|
50
|
+
# Default: SemanticLogger.default_level
|
46
51
|
#
|
47
|
-
#
|
52
|
+
# :formatter: [Object|Proc]
|
53
|
+
# An instance of a class that implements #call, or a Proc to be used to format
|
54
|
+
# the output from this appender
|
55
|
+
# Default: Use the built-in formatter (See: #call)
|
48
56
|
#
|
49
|
-
#
|
57
|
+
# :filter [Regexp|Proc]
|
58
|
+
# RegExp: Only include log messages where the class name matches the supplied
|
59
|
+
# regular expression. All other messages will be ignored.
|
60
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
61
|
+
# The Proc must return true or false.
|
50
62
|
#
|
51
|
-
#
|
52
|
-
#
|
63
|
+
# :append [true|false]
|
64
|
+
# Append to the log file if already present?
|
65
|
+
# Default: true
|
53
66
|
#
|
54
|
-
#
|
55
|
-
#
|
67
|
+
# :exclusive_lock [true|false]
|
68
|
+
# Obtain an exclusive lock on the file, for operating systems that support it.
|
69
|
+
# Prevents multiple processes from trying to write to the same log file.
|
70
|
+
# Default: false
|
56
71
|
#
|
57
|
-
#
|
58
|
-
#
|
72
|
+
# :encoding ["UTF-8", "UTF-16", etc.]
|
73
|
+
# Encoding to use when writing to the file.
|
74
|
+
# Default: Encoding::BINARY
|
59
75
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
76
|
+
# :retry_count [Integer]
|
77
|
+
# Number of times to attempt to re-open the file name when an error occurs trying to
|
78
|
+
# write to the file.
|
79
|
+
# Note: Set to 0 to disable retries.
|
80
|
+
# Default: 1
|
81
|
+
#
|
82
|
+
# :reopen_period [String]
|
83
|
+
# Specify a period after which to re-open the log file, specified in minutes, hours, or days.
|
84
|
+
# The format of the duration must start with an Integer or Float number,
|
85
|
+
# followed by the duration specified as:
|
86
|
+
# "m" : minutes
|
87
|
+
# "h" : hours
|
88
|
+
# "d" : days
|
89
|
+
# The time is rounded down to the specified time interval, so that:
|
90
|
+
# - "1h" will re-open every hour at the beginning of the hour.
|
91
|
+
# - "30m" will re-open every 30 minutes at the beginning of the 30th minute.
|
92
|
+
# - "1d" will re-open every day at midnight.
|
93
|
+
# Examples:
|
94
|
+
# "60m" : Every 60 minutes at the beginning of the minute: 10:24:00, 11:24:00, 12:24:00, ...
|
95
|
+
# "1h" : Every hour at the beginning of the hour: 10:00:00, 11:00:00, 12:00:00, ...
|
96
|
+
# "1d" : Every day at the beginning of the day: "20211008 00:00:00", "20211009 00:00:00", ...
|
97
|
+
# Default: nil (Disabled)
|
98
|
+
#
|
99
|
+
# :reopen_count [Integer]
|
100
|
+
# Close and re-open the log file after every `reopen_count` number of logged entries.
|
101
|
+
# Default: 0 (Disabled)
|
102
|
+
#
|
103
|
+
# :reopen_size [Integer]
|
104
|
+
# Approximate number of bytes to write to a log file by this process before closing and re-opening it.
|
105
|
+
# Notes:
|
106
|
+
# - When `append: true` and the file already exists, it reads the size of the current log file
|
107
|
+
# and starts with that size.
|
108
|
+
# - If the current log file size already exceeds the `reopen_size`, its current size is ignored.
|
109
|
+
# - The `reopen_size` is only the amount of bytes written by this process, it excludes data
|
110
|
+
# written by other processes. Use a unique filename to prevent multiple processes from writing to
|
111
|
+
# the same log file at the same time.
|
112
|
+
# Default: 0 (Disabled)
|
113
|
+
#
|
114
|
+
# Example
|
115
|
+
# require "semantic_logger"
|
116
|
+
#
|
117
|
+
# # Enable trace level logging
|
118
|
+
# SemanticLogger.default_level = :info
|
119
|
+
#
|
120
|
+
# # Log to a file
|
121
|
+
# SemanticLogger.add_appender(file_name: "application.log", formatter: :color)
|
122
|
+
#
|
123
|
+
# logger = SemanticLogger["test"]
|
124
|
+
# logger.info "Hello World"
|
125
|
+
def initialize(file_name, retry_count: 1, append: true, reopen_period: nil, reopen_count: 0, reopen_size: 0, encoding: Encoding::BINARY, exclusive_lock: false, **args, &block)
|
126
|
+
if !file_name.is_a?(String) || file_name.empty?
|
127
|
+
raise(ArgumentError, "SemanticLogging::Appender::File file_name must be a non-empty string")
|
75
128
|
end
|
76
129
|
|
130
|
+
@file_name = file_name
|
131
|
+
@retry_count = retry_count
|
132
|
+
@file = nil
|
133
|
+
@append = append
|
134
|
+
@reopen_period = reopen_period
|
135
|
+
@reopen_count = reopen_count
|
136
|
+
@reopen_size = reopen_size
|
137
|
+
@encoding = encoding
|
138
|
+
@exclusive_lock = exclusive_lock
|
139
|
+
@log_count = 0
|
140
|
+
@log_size = 0
|
141
|
+
@reopen_at = nil
|
142
|
+
|
77
143
|
super(**args, &block)
|
78
144
|
end
|
79
145
|
|
80
146
|
# After forking an active process call #reopen to re-open
|
81
|
-
# open the file handles etc to resources
|
82
|
-
#
|
83
|
-
# Note: This method will only work if :file_name was supplied
|
84
|
-
# on the initializer.
|
85
|
-
# If :io was supplied, it will need to be re-opened manually.
|
147
|
+
# open the file handles etc to resources.
|
86
148
|
def reopen
|
87
|
-
|
149
|
+
begin
|
150
|
+
@file&.close
|
151
|
+
rescue StandardError
|
152
|
+
nil
|
153
|
+
end
|
154
|
+
|
155
|
+
self.current_file_name = apply_format_directives(file_name)
|
156
|
+
if ::File.directory?(file_name)
|
157
|
+
raise(ArgumentError, "The supplied log file_name: #{current_file_name} is already a directory.")
|
158
|
+
end
|
159
|
+
|
160
|
+
self.log_count = 0
|
161
|
+
if append && reopen_size && ::File.exist?(current_file_name)
|
162
|
+
self.log_size = ::File.size(current_file_name)
|
163
|
+
self.log_size = 0 if log_size >= reopen_size
|
164
|
+
else
|
165
|
+
self.log_size = 0
|
166
|
+
end
|
88
167
|
|
89
|
-
|
168
|
+
self.reopen_at = reopen_period ? next_reopen_period(reopen_period) : nil
|
169
|
+
|
170
|
+
options = ::File::WRONLY | ::File::CREAT
|
171
|
+
options |= ::File::APPEND if append
|
172
|
+
@file = ::File.open(current_file_name, options)
|
90
173
|
# Force all log entries to write immediately without buffering
|
91
174
|
# Allows multiple processes to write to the same log file simultaneously
|
92
|
-
@
|
93
|
-
@
|
94
|
-
@
|
175
|
+
@file.sync = true
|
176
|
+
@file.set_encoding(encoding) if @file.respond_to?(:set_encoding)
|
177
|
+
@file.flock(::File::LOCK_EX) if exclusive_lock
|
178
|
+
@file
|
95
179
|
end
|
96
180
|
|
97
|
-
#
|
98
|
-
#
|
99
|
-
# Ruby or Rails Loggers
|
181
|
+
# Since only one appender thread will be writing to the file at a time
|
182
|
+
# it is not necessary to protect access to the file with a semaphore.
|
100
183
|
def log(log)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
184
|
+
reopen if time_to_reopen?
|
185
|
+
|
186
|
+
count = 0
|
187
|
+
begin
|
188
|
+
message = formatter.call(log, self) << "\n"
|
189
|
+
@file.write(message)
|
190
|
+
@log_count += 1
|
191
|
+
@log_size += message.size
|
192
|
+
rescue StandardError => e
|
193
|
+
if count < retry_count
|
194
|
+
count += 1
|
195
|
+
reopen
|
196
|
+
retry
|
197
|
+
end
|
198
|
+
raise(e)
|
199
|
+
end
|
105
200
|
true
|
106
201
|
end
|
107
202
|
|
108
203
|
# Flush all pending logs to disk.
|
109
|
-
# Waits for all sent documents to be
|
204
|
+
# Waits for all sent documents to be written to disk
|
110
205
|
def flush
|
111
|
-
@
|
206
|
+
@file&.flush
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
attr_writer :log_count, :log_size, :current_file_name, :reopen_at
|
212
|
+
|
213
|
+
def time_to_reopen?
|
214
|
+
return true unless @file
|
215
|
+
|
216
|
+
(reopen_count.positive? && (log_count >= reopen_count)) ||
|
217
|
+
(reopen_size.positive? && (log_size >= reopen_size)) ||
|
218
|
+
(reopen_at && (Time.now > reopen_at))
|
219
|
+
end
|
220
|
+
|
221
|
+
def apply_format_directives(file_name)
|
222
|
+
return file_name unless file_name.include?("%")
|
223
|
+
|
224
|
+
file_name.gsub(/%(.)/) { format_directive(Regexp.last_match(1)) }
|
225
|
+
end
|
226
|
+
|
227
|
+
def format_directive(directive)
|
228
|
+
case directive
|
229
|
+
when "p"
|
230
|
+
$$
|
231
|
+
when "n"
|
232
|
+
SemanticLogger.host.split(".")[0]
|
233
|
+
when "N"
|
234
|
+
SemanticLogger.host
|
235
|
+
when "a"
|
236
|
+
SemanticLogger.application
|
237
|
+
when "e"
|
238
|
+
SemanticLogger.environment
|
239
|
+
when "D"
|
240
|
+
Date.today.strftime("%Y%m%d")
|
241
|
+
when "Y", "C", "y", "m", "d", "j", "U", "W"
|
242
|
+
Date.today.strftime("%#{directive}")
|
243
|
+
when "T"
|
244
|
+
Time.now.strftime("%H%M%S")
|
245
|
+
when "H", "M", "S"
|
246
|
+
Time.now.strftime("%#{directive}")
|
247
|
+
when "%"
|
248
|
+
"%"
|
249
|
+
else
|
250
|
+
raise(ArgumentError, "Format Directive '#{directive}' in file_name: #{file_name} is not supported.")
|
251
|
+
end
|
112
252
|
end
|
113
253
|
|
114
|
-
def
|
115
|
-
|
254
|
+
def next_reopen_period(period_string)
|
255
|
+
return unless period_string
|
256
|
+
|
257
|
+
duration, period = parse_period(period_string)
|
258
|
+
calculate_reopen_at(duration, period)
|
259
|
+
end
|
260
|
+
|
261
|
+
def parse_period(period_string)
|
262
|
+
match = period_string.to_s.downcase.gsub(/\s+/, "").match(/([\d.]+)([mhd])/)
|
263
|
+
unless match
|
264
|
+
raise(ArgumentError,
|
265
|
+
"Invalid period definition: #{period_string}, must begin with an integer, followed by m,h, or d.")
|
266
|
+
end
|
267
|
+
|
268
|
+
duration = match[1]
|
269
|
+
period = match[2]
|
270
|
+
raise(ArgumentError, "Invalid or missing duration in: #{period_string}, must begin with an integer.") unless duration
|
271
|
+
raise(ArgumentError, "Invalid or missing period in: #{period_string}, must end with m,h, or d.") unless period
|
272
|
+
|
273
|
+
[duration.to_i, period]
|
274
|
+
end
|
275
|
+
|
276
|
+
# Round down the current time based on the period, then add on the duration for that period
|
277
|
+
def calculate_reopen_at(duration, period, time = Time.now)
|
278
|
+
case period
|
279
|
+
when "m"
|
280
|
+
Time.new(time.year, time.month, time.day, time.hour, time.min, 0) + (duration * 60)
|
281
|
+
when "h"
|
282
|
+
Time.new(time.year, time.month, time.day, time.hour, 0, 0) + (duration * 60 * 60)
|
283
|
+
when "d"
|
284
|
+
Time.new(time.year, time.month, time.day, 0, 0, 0) + (duration * 24 * 60 * 60)
|
285
|
+
else
|
286
|
+
raise(ArgumentError, "Invalid or missing period in: #{reopen_period}, must end with m,h, or d.")
|
287
|
+
end
|
116
288
|
end
|
117
289
|
end
|
118
290
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# File appender
|
2
|
+
#
|
3
|
+
# Writes log messages to a file or open iostream
|
4
|
+
#
|
5
|
+
module SemanticLogger
|
6
|
+
module Appender
|
7
|
+
class IO < SemanticLogger::Subscriber
|
8
|
+
# Create a Stream Logger appender instance.
|
9
|
+
#
|
10
|
+
# Parameters
|
11
|
+
# io [IO]
|
12
|
+
# An IO stream to which to write the log messages to.
|
13
|
+
#
|
14
|
+
# :level [:trace | :debug | :info | :warn | :error | :fatal]
|
15
|
+
# Override the log level for this appender.
|
16
|
+
# Default: SemanticLogger.default_level
|
17
|
+
#
|
18
|
+
# :formatter: [Object|Proc]
|
19
|
+
# An instance of a class that implements #call, or a Proc to be used to format
|
20
|
+
# the output from this appender
|
21
|
+
# Default: Use the built-in formatter (See: #call)
|
22
|
+
#
|
23
|
+
# :filter [Regexp|Proc]
|
24
|
+
# RegExp: Only include log messages where the class name matches the supplied
|
25
|
+
# regular expression. All other messages will be ignored.
|
26
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
27
|
+
# The Proc must return true or false.
|
28
|
+
#
|
29
|
+
# Example
|
30
|
+
# require "semantic_logger"
|
31
|
+
#
|
32
|
+
# # Enable trace level logging
|
33
|
+
# SemanticLogger.default_level = :info
|
34
|
+
#
|
35
|
+
# # Log to screen
|
36
|
+
# SemanticLogger.add_appender(io: $stdout, formatter: :color)
|
37
|
+
#
|
38
|
+
# logger = SemanticLogger['test']
|
39
|
+
# logger.info 'Hello World'
|
40
|
+
def initialize(io, **args, &block)
|
41
|
+
@io = io
|
42
|
+
unless @io.respond_to?(:write)
|
43
|
+
raise(ArgumentError, "SemanticLogging::Appender::IO io is not a valid IO instance: #{io.inspect}")
|
44
|
+
end
|
45
|
+
|
46
|
+
super(**args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def log(log)
|
50
|
+
# Since only one appender thread will be writing to the file at a time
|
51
|
+
# it is not necessary to protect access to the file with a semaphore
|
52
|
+
# Allow this logger to filter out log levels lower than it's own
|
53
|
+
@io.write(formatter.call(log, self) << "\n")
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Flush all pending logs to disk.
|
58
|
+
# Waits for all sent documents to be written to disk
|
59
|
+
def flush
|
60
|
+
@io.flush if @io.respond_to?(:flush)
|
61
|
+
end
|
62
|
+
|
63
|
+
def console_output?
|
64
|
+
[$stderr, $stdout].include?(@io)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -9,6 +9,7 @@ module SemanticLogger
|
|
9
9
|
autoload :File, "semantic_logger/appender/file"
|
10
10
|
autoload :Graylog, "semantic_logger/appender/graylog"
|
11
11
|
autoload :Honeybadger, "semantic_logger/appender/honeybadger"
|
12
|
+
autoload :IO, "semantic_logger/appender/io"
|
12
13
|
autoload :Kafka, "semantic_logger/appender/kafka"
|
13
14
|
autoload :Sentry, "semantic_logger/appender/sentry"
|
14
15
|
autoload :Http, "semantic_logger/appender/http"
|
@@ -32,7 +33,7 @@ module SemanticLogger
|
|
32
33
|
appender = build(**args, &block)
|
33
34
|
|
34
35
|
# If appender implements #batch, then it should use the batch proxy by default.
|
35
|
-
batch
|
36
|
+
batch = true if batch.nil? && appender.respond_to?(:batch)
|
36
37
|
|
37
38
|
if batch == true
|
38
39
|
Appender::AsyncBatch.new(
|
@@ -56,8 +57,10 @@ module SemanticLogger
|
|
56
57
|
|
57
58
|
# Returns [Subscriber] instance from the supplied options.
|
58
59
|
def self.build(io: nil, file_name: nil, appender: nil, metric: nil, logger: nil, **args, &block)
|
59
|
-
if
|
60
|
-
SemanticLogger::Appender::File.new(
|
60
|
+
if file_name
|
61
|
+
SemanticLogger::Appender::File.new(file_name, **args, &block)
|
62
|
+
elsif io
|
63
|
+
SemanticLogger::Appender::IO.new(io, **args, &block)
|
61
64
|
elsif logger
|
62
65
|
SemanticLogger::Appender::Wrapper.new(logger: logger, **args, &block)
|
63
66
|
elsif appender
|
data/lib/semantic_logger/base.rb
CHANGED
@@ -188,7 +188,8 @@ module SemanticLogger
|
|
188
188
|
# - For better performance with clean tags, see `SemanticLogger.tagged`.
|
189
189
|
def tagged(*tags, &block)
|
190
190
|
# Allow named tags to be passed into the logger
|
191
|
-
|
191
|
+
# Rails::Rack::Logger passes logs as an array with a single argument
|
192
|
+
if tags.size == 1 && !tags.first.is_a?(Array)
|
192
193
|
tag = tags[0]
|
193
194
|
return yield if tag.nil? || tag == ""
|
194
195
|
|
@@ -2,6 +2,21 @@ require "json"
|
|
2
2
|
|
3
3
|
module SemanticLogger
|
4
4
|
module Formatters
|
5
|
+
# Produces logfmt formatted messages
|
6
|
+
#
|
7
|
+
# The following fields are extracted from the raw log and included in the formatted message:
|
8
|
+
# :timestamp, :level, :name, :message, :duration, :tags, :named_tags
|
9
|
+
#
|
10
|
+
# E.g.
|
11
|
+
# timestamp="2020-07-20T08:32:05.375276Z" level=info name="DefaultTest" base="breakfast" spaces="second breakfast" double_quotes="\"elevensies\"" single_quotes="'lunch'" tag="success"
|
12
|
+
#
|
13
|
+
# All timestamps are ISO8601 formatteed
|
14
|
+
# All user supplied values are escaped and surrounded by double quotes to avoid ambiguious message delimeters
|
15
|
+
# `tags` are treated as keys with boolean values. Tag names are not formatted or validated, ensure you use valid logfmt format for tag names.
|
16
|
+
# `named_tags` are flattened are merged into the top level message field. Any conflicting fields are overridden.
|
17
|
+
# `payload` values take precedence over `tags` and `named_tags`. Any conflicting fields are overridden.
|
18
|
+
#
|
19
|
+
# Futher Reading https://brandur.org/logfmt
|
5
20
|
class Logfmt < Raw
|
6
21
|
def initialize(time_format: :iso_8601, time_key: :timestamp, **args)
|
7
22
|
super(time_format: time_format, time_key: time_key, **args)
|
@@ -16,13 +31,22 @@ module SemanticLogger
|
|
16
31
|
private
|
17
32
|
|
18
33
|
def raw_to_logfmt
|
19
|
-
@parsed = @raw.slice(
|
34
|
+
@parsed = @raw.slice(time_key, :level, :name, :message, :duration).merge(tag: "success")
|
35
|
+
handle_tags
|
20
36
|
handle_payload
|
21
37
|
handle_exception
|
22
38
|
|
23
39
|
flatten_log
|
24
40
|
end
|
25
41
|
|
42
|
+
def handle_tags
|
43
|
+
tags = @raw.fetch(:tags){ [] }
|
44
|
+
.each_with_object({}){ |tag, accum| accum[tag] = true }
|
45
|
+
|
46
|
+
@parsed = @parsed.merge(tags)
|
47
|
+
.merge(@raw.fetch(:named_tags){ {} })
|
48
|
+
end
|
49
|
+
|
26
50
|
def handle_payload
|
27
51
|
return unless @raw.key? :payload
|
28
52
|
|
@@ -38,17 +62,11 @@ module SemanticLogger
|
|
38
62
|
|
39
63
|
def flatten_log
|
40
64
|
flattened = @parsed.map do |key, value|
|
41
|
-
"#{key}=#{
|
65
|
+
"#{key}=#{value.to_json}"
|
42
66
|
end
|
43
67
|
|
44
68
|
flattened.join(" ")
|
45
69
|
end
|
46
|
-
|
47
|
-
def parse_value(value)
|
48
|
-
return value.to_json if value.instance_of? String
|
49
|
-
|
50
|
-
value
|
51
|
-
end
|
52
70
|
end
|
53
71
|
end
|
54
72
|
end
|
@@ -122,7 +122,7 @@ module SemanticLogger
|
|
122
122
|
# Default: SemanticLogger.default_level
|
123
123
|
#
|
124
124
|
# formatter: [Symbol|Object|Proc]
|
125
|
-
# Any of the following symbol values: :default, :color, :json
|
125
|
+
# Any of the following symbol values: :default, :color, :json, :logfmt, etc...
|
126
126
|
# Or,
|
127
127
|
# An instance of a class that implements #call
|
128
128
|
# Or,
|
@@ -164,7 +164,7 @@ module SemanticLogger
|
|
164
164
|
# logger.info "Hello World"
|
165
165
|
# logger.debug("Login time", user: 'Joe', duration: 100, ip_address: '127.0.0.1')
|
166
166
|
def self.add_appender(**args, &block)
|
167
|
-
appender =
|
167
|
+
appender = appenders.add(**args, &block)
|
168
168
|
# Start appender thread if it is not already running
|
169
169
|
Logger.processor.start
|
170
170
|
appender
|
@@ -175,7 +175,7 @@ module SemanticLogger
|
|
175
175
|
def self.remove_appender(appender)
|
176
176
|
return unless appender
|
177
177
|
|
178
|
-
|
178
|
+
appenders.delete(appender)
|
179
179
|
appender.close
|
180
180
|
end
|
181
181
|
|
@@ -189,7 +189,7 @@ module SemanticLogger
|
|
189
189
|
# Use SemanticLogger.add_appender and SemanticLogger.remove_appender
|
190
190
|
# to manipulate the active appenders list
|
191
191
|
def self.appenders
|
192
|
-
Logger.processor.appenders
|
192
|
+
Logger.processor.appenders
|
193
193
|
end
|
194
194
|
|
195
195
|
# Flush all queued log entries disk, database, etc.
|
@@ -22,6 +22,11 @@ module SemanticLogger
|
|
22
22
|
# NOOP
|
23
23
|
end
|
24
24
|
|
25
|
+
# Method called to log an event
|
26
|
+
def log(log)
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
25
30
|
# Returns [SemanticLogger::Formatters::Default] default formatter for this subscriber.
|
26
31
|
def default_formatter
|
27
32
|
SemanticLogger::Formatters::Default.new
|
@@ -68,6 +73,11 @@ module SemanticLogger
|
|
68
73
|
super(log) && (log.metric_only? ? metrics? : true)
|
69
74
|
end
|
70
75
|
|
76
|
+
# Whether this appender is logging to stdout or stderror
|
77
|
+
def console_output?
|
78
|
+
false
|
79
|
+
end
|
80
|
+
|
71
81
|
private
|
72
82
|
|
73
83
|
# Initializer for Abstract Class SemanticLogger::Subscriber
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module SemanticLogger
|
2
|
+
module Test
|
3
|
+
# Logging class to captures all logging events in memory.
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
#
|
7
|
+
# class UserTest < ActiveSupport::TestCase
|
8
|
+
# describe User do
|
9
|
+
# let(:capture_logger) { SemanticLogger::Test::CaptureLogEvents.new }
|
10
|
+
# let(:user) { User.new }
|
11
|
+
#
|
12
|
+
# it "logs message" do
|
13
|
+
# user.stub(:logger, capture_logger) do
|
14
|
+
# user.enable!
|
15
|
+
# end
|
16
|
+
# assert_equal "Hello World", capture_logger.events.last.message
|
17
|
+
# assert_equal :info, capture_logger.events.last.level
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
class CaptureLogEvents < SemanticLogger::Subscriber
|
22
|
+
attr_accessor :events
|
23
|
+
|
24
|
+
# By default collect all log levels, and collect metric only log events.
|
25
|
+
def initialize(level: :trace, metrics: true)
|
26
|
+
super(level: level, metrics: true)
|
27
|
+
end
|
28
|
+
|
29
|
+
def log(log)
|
30
|
+
(@events ||= []) << log
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/semantic_logger.rb
CHANGED
@@ -32,6 +32,10 @@ module SemanticLogger
|
|
32
32
|
autoload :Minitest, "semantic_logger/reporters/minitest"
|
33
33
|
end
|
34
34
|
|
35
|
+
module Test
|
36
|
+
autoload :CaptureLogEvents, "semantic_logger/test/capture_log_events"
|
37
|
+
end
|
38
|
+
|
35
39
|
if defined?(JRuby)
|
36
40
|
module JRuby
|
37
41
|
autoload :GarbageCollectionLogger, "semantic_logger/jruby/garbage_collection_logger"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: semantic_logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Reid Morrison
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -45,6 +45,7 @@ files:
|
|
45
45
|
- lib/semantic_logger/appender/graylog.rb
|
46
46
|
- lib/semantic_logger/appender/honeybadger.rb
|
47
47
|
- lib/semantic_logger/appender/http.rb
|
48
|
+
- lib/semantic_logger/appender/io.rb
|
48
49
|
- lib/semantic_logger/appender/kafka.rb
|
49
50
|
- lib/semantic_logger/appender/mongodb.rb
|
50
51
|
- lib/semantic_logger/appender/new_relic.rb
|
@@ -87,6 +88,7 @@ files:
|
|
87
88
|
- lib/semantic_logger/subscriber.rb
|
88
89
|
- lib/semantic_logger/sync.rb
|
89
90
|
- lib/semantic_logger/sync_processor.rb
|
91
|
+
- lib/semantic_logger/test/capture_log_events.rb
|
90
92
|
- lib/semantic_logger/utils.rb
|
91
93
|
- lib/semantic_logger/version.rb
|
92
94
|
homepage: https://logger.rocketjob.io
|
@@ -108,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
110
|
- !ruby/object:Gem::Version
|
109
111
|
version: '0'
|
110
112
|
requirements: []
|
111
|
-
rubygems_version: 3.
|
113
|
+
rubygems_version: 3.0.9
|
112
114
|
signing_key:
|
113
115
|
specification_version: 4
|
114
116
|
summary: Feature rich logging framework, and replacement for existing Ruby & Rails
|