mimi-logger 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.md +83 -1
- data/lib/mimi/logger/version.rb +1 -1
- data/lib/mimi/logger.rb +163 -7
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1dac6f3cbaeef0582579cb7f695e92896676d9267c286e7d82d95d7d72312219
|
4
|
+
data.tar.gz: 3e9866931dff24f400dca04da3e1e41d59139f33860c621d2e7136656f80f87d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c02fa6f4e89c807b5b6284aec1e304939cf3aec08820c73db08380b81a0b0f071e85e99c55810bf8e0b584a5497f227748311dd44b7d4e9b087b649970a2b2a
|
7
|
+
data.tar.gz: 62cdc14d5fcbfe555e2fe45af17db5c004d051767445802dbb35fa3525825f4de514afad51da9fd700f110ae4c1ad70680344da47641b3bba50250a7c2dfdf9e
|
data/README.md
CHANGED
@@ -25,11 +25,93 @@ Or install it yourself as:
|
|
25
25
|
```ruby
|
26
26
|
require 'mimi/logger'
|
27
27
|
|
28
|
-
logger = Mimi::Logger.new
|
28
|
+
logger = Mimi::Logger.new format: :string
|
29
29
|
|
30
30
|
logger.info 'I am a banana!' # outputs "I, I am a banana!" to STDOUT
|
31
31
|
```
|
32
32
|
|
33
|
+
### Logging format
|
34
|
+
|
35
|
+
Since v0.2.0 the default format for logging is JSON:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
require 'mimi/logger'
|
39
|
+
|
40
|
+
logger = Mimi::Logger.new
|
41
|
+
|
42
|
+
logger.info 'I am a banana' # => '{"s":"I","m":"I am a banana","c":"60eecc2e764fe2f6"}'
|
43
|
+
```
|
44
|
+
|
45
|
+
The following properties of a serialized JSON object are reserved:
|
46
|
+
|
47
|
+
name | description
|
48
|
+
-----|------------
|
49
|
+
s | severity, one of D,I,W,E,F
|
50
|
+
m | message
|
51
|
+
c | context ID, 8 bytes hex encoded
|
52
|
+
|
53
|
+
Additional properties may be provided by the caller and they will be included in the logged JSON:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'mimi/logger'
|
57
|
+
|
58
|
+
logger = Mimi::Logger.new
|
59
|
+
|
60
|
+
t_start = Time.now
|
61
|
+
... # work, work
|
62
|
+
logger.info 'Jobs done', t: Time.now - t_start
|
63
|
+
# => '{"s":"I","m":"Jobs done","c":"60eecc2e764fe2f6", "t": 13.794034499}'
|
64
|
+
```
|
65
|
+
|
66
|
+
### Logging context
|
67
|
+
|
68
|
+
Logging context refers to a set of instructions that are somehow logically grouped. For example,
|
69
|
+
the whole logic processing an incoming web request can be seen as operating within a single context
|
70
|
+
of this particular web request. In a distributed or a multithreaded application it would be beneficial
|
71
|
+
to identify logged messages produced within same context, otherwise the messages related to different
|
72
|
+
web requests will be interleaved and understanding the flow, the cause and effect would be very difficult.
|
73
|
+
|
74
|
+
To solve this problem, Mimi::Logger allows for setting an arbitrary (or random) context ID, which is local
|
75
|
+
to the current thread, and which is included in every logged message.
|
76
|
+
|
77
|
+
A new context may be initiated by `.new_context_id!`:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
require 'mimi/logger'
|
81
|
+
|
82
|
+
logger = Mimi::Logger.new
|
83
|
+
|
84
|
+
logger.info 'I am a banana!'
|
85
|
+
logger.new_context_id!
|
86
|
+
logger.info 'I am a banana!' # this is not the same banana, it's from a different context
|
87
|
+
```
|
88
|
+
|
89
|
+
Or it can be set to an explicit value:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
require 'mimi/logger'
|
93
|
+
|
94
|
+
logger = Mimi::Logger.new
|
95
|
+
|
96
|
+
logger.info 'I am a banana!'
|
97
|
+
logger.context_id = 'different-context!'
|
98
|
+
logger.info 'I am a banana!' # this is not the same banana, it's from a 'different-context!'
|
99
|
+
|
100
|
+
```
|
101
|
+
|
102
|
+
#### Context ID in a multithreaded application
|
103
|
+
|
104
|
+
The context ID is local to a current thread, so it's safe to start or assign a different context ID
|
105
|
+
in other threads of the application.
|
106
|
+
|
107
|
+
#### Context ID in a distributed application
|
108
|
+
|
109
|
+
If you have a distributed multicomponent application (e.g. microservice architecture), context ID
|
110
|
+
may help to track requests between multiple parties. In order to achieve it, you need to generate a
|
111
|
+
new context ID in the beginning of your processing and pass it along with all the requests/messages to
|
112
|
+
other components of the system. Upon receiving such a request, another application sets its local context ID
|
113
|
+
to the received value and continues.
|
114
|
+
|
33
115
|
## Contributing
|
34
116
|
|
35
117
|
Bug reports and pull requests are welcome on GitHub at https://github.com/kukushkin/mimi-logger. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
data/lib/mimi/logger/version.rb
CHANGED
data/lib/mimi/logger.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'forwardable'
|
2
4
|
require 'logger'
|
5
|
+
require 'json'
|
3
6
|
require 'mimi/core'
|
4
7
|
|
5
8
|
module Mimi
|
@@ -7,17 +10,21 @@ module Mimi
|
|
7
10
|
# Mimi::Logger is a preconfigured logger which outputs log messages to STDOUT.
|
8
11
|
#
|
9
12
|
class Logger
|
13
|
+
CONTEXT_ID_SIZE = 8 # bytes
|
14
|
+
CONTEXT_ID_THREAD_VARIABLE = 'mimi_logger_context_id'
|
15
|
+
|
10
16
|
extend Forwardable
|
11
17
|
include Mimi::Core::Module
|
12
18
|
|
13
19
|
attr_reader :logger_instance
|
14
20
|
delegate [
|
15
|
-
:debug, :info, :warn, :error, :fatal, :unknown,
|
16
21
|
:debug?, :info?, :warn?, :error?, :fatal?,
|
17
22
|
:<<, :add, :log
|
18
23
|
] => :logger_instance
|
19
24
|
|
20
25
|
default_options(
|
26
|
+
format: 'json', # 'string' or 'json'
|
27
|
+
log_context: true,
|
21
28
|
level: 'info',
|
22
29
|
cr_character: '↲' # alternative CR: ↵ ↲ ⏎
|
23
30
|
)
|
@@ -36,7 +43,7 @@ module Mimi
|
|
36
43
|
# logger.debug 'blabla' # => "D, blabla"
|
37
44
|
#
|
38
45
|
def initialize(*args)
|
39
|
-
io = args.shift if args.first.is_a?(IO)
|
46
|
+
io = args.shift if args.first.is_a?(IO) || args.first.is_a?(StringIO)
|
40
47
|
io ||= STDOUT
|
41
48
|
opts = args.shift if args.first.is_a?(Hash)
|
42
49
|
opts ||= {}
|
@@ -51,7 +58,7 @@ module Mimi
|
|
51
58
|
|
52
59
|
# Returns the current log level
|
53
60
|
#
|
54
|
-
# @return [
|
61
|
+
# @return [Integer]
|
55
62
|
#
|
56
63
|
def level
|
57
64
|
logger_instance.level
|
@@ -60,18 +67,41 @@ module Mimi
|
|
60
67
|
# Sets the log level.
|
61
68
|
# Allows setting the log level from a String or a Symbol, in addition to the standard ::Logger::INFO etc.
|
62
69
|
#
|
63
|
-
# @param [String,Symbol,
|
70
|
+
# @param [String,Symbol,Integer] value
|
64
71
|
#
|
65
72
|
def level=(value)
|
66
73
|
logger_instance.level = self.class.level_from_any(value)
|
67
74
|
end
|
68
75
|
|
76
|
+
# Logs a new message at the corresponding logging level
|
77
|
+
#
|
78
|
+
#
|
79
|
+
#
|
80
|
+
def debug(*args, &block)
|
81
|
+
logger_instance.debug(args, &block)
|
82
|
+
end
|
83
|
+
def info(*args, &block)
|
84
|
+
logger_instance.info(args, &block)
|
85
|
+
end
|
86
|
+
def warn(*args, &block)
|
87
|
+
logger_instance.warn(args, &block)
|
88
|
+
end
|
89
|
+
def error(*args, &block)
|
90
|
+
logger_instance.error(args, &block)
|
91
|
+
end
|
92
|
+
def fatal(*args, &block)
|
93
|
+
logger_instance.fatal(args, &block)
|
94
|
+
end
|
95
|
+
def unknown(*args, &block)
|
96
|
+
logger_instance.unknown(args, &block)
|
97
|
+
end
|
98
|
+
|
69
99
|
# Returns the log level inferred from value
|
70
100
|
#
|
71
|
-
# @param value [String,Symbol,
|
101
|
+
# @param value [String,Symbol,Integer]
|
72
102
|
#
|
73
103
|
def self.level_from_any(value)
|
74
|
-
return value if value.is_a?(
|
104
|
+
return value if value.is_a?(Integer)
|
75
105
|
case value.to_s.downcase.to_sym
|
76
106
|
when :debug
|
77
107
|
::Logger::DEBUG
|
@@ -90,12 +120,138 @@ module Mimi
|
|
90
120
|
end
|
91
121
|
end
|
92
122
|
|
123
|
+
# Returns formatter Proc object depending on configured format
|
124
|
+
#
|
125
|
+
# @return [Proc]
|
126
|
+
#
|
93
127
|
def self.formatter
|
128
|
+
case module_options[:format].to_s
|
129
|
+
when 'json'
|
130
|
+
formatter_json
|
131
|
+
when 'string'
|
132
|
+
formatter_string
|
133
|
+
else
|
134
|
+
raise "Invalid format specified for Mimi::Logger: '#{module_options[:format]}'"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# Returns formatter for 'json' format
|
139
|
+
#
|
140
|
+
# @return [Proc]
|
141
|
+
#
|
142
|
+
def self.formatter_json
|
94
143
|
proc do |severity, _datetime, _progname, message|
|
95
|
-
|
144
|
+
h = formatter_message_args_to_hash(message)
|
145
|
+
h[:c] = context_id if module_options[:log_context]
|
146
|
+
h[:s] = severity.to_s[0]
|
147
|
+
h.to_json
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns formatter for 'string' format
|
152
|
+
#
|
153
|
+
# @return [Proc]
|
154
|
+
#
|
155
|
+
def self.formatter_string
|
156
|
+
proc do |severity, _datetime, _progname, message|
|
157
|
+
h = formatter_message_args_to_hash(message)
|
158
|
+
parts = []
|
159
|
+
parts << severity.to_s[0]
|
160
|
+
parts << context_id if module_options[:log_context]
|
161
|
+
parts << h[:m].to_s.tr("\n", module_options[:cr_character])
|
162
|
+
parts << '...' unless h.except(:m).empty?
|
163
|
+
parts.join(', ')
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Converts logger methods arguments passed in various forms to a message hash.
|
168
|
+
#
|
169
|
+
# Arguments to a logger may be passed in 6 different ways:
|
170
|
+
# @example
|
171
|
+
# logger.info('String')
|
172
|
+
# logger.info('String', param1: '..and a Hash')
|
173
|
+
# logger.info(m: 'Just a Hash', param1: 'with optional data')
|
174
|
+
#
|
175
|
+
# # and the same 3 ways in a block form
|
176
|
+
# logger.info { ... }
|
177
|
+
#
|
178
|
+
# This helper method converts all possible variations into one Hash, where key m: refers to the
|
179
|
+
# message and the rest are optional parameters passed in a Hash argument.
|
180
|
+
#
|
181
|
+
# @return [Hash]
|
182
|
+
#
|
183
|
+
def self.formatter_message_args_to_hash(message_args)
|
184
|
+
message_args = message_args.is_a?(String) || message_args.is_a?(Hash) ? [message_args] : message_args
|
185
|
+
if !message_args.is_a?(Array) || message_args.size > 2
|
186
|
+
raise ArgumentError, "Mimi::Logger arguments expected to be Array of up to 2 elements: #{message_args.inspect}"
|
96
187
|
end
|
188
|
+
|
189
|
+
h = {}
|
190
|
+
arg1 = message_args.shift
|
191
|
+
arg2 = message_args.shift
|
192
|
+
|
193
|
+
if arg1.is_a?(String)
|
194
|
+
if arg2 && !arg2.is_a?(Hash)
|
195
|
+
raise ArgumentError, 'Mimi::Logger arguments are expected to be one of (<String>, <Hash>, [<String>, <Hash>])'
|
196
|
+
end
|
197
|
+
h = arg2.dup || {}
|
198
|
+
h[:m] = arg1
|
199
|
+
elsif arg1.is_a?(Hash)
|
200
|
+
if arg2
|
201
|
+
raise ArgumentError, 'Mimi::Logger arguments are expected to be one of (<String>, <Hash>, [<String>, <Hash>])'
|
202
|
+
end
|
203
|
+
h = arg1.dup
|
204
|
+
else
|
205
|
+
raise ArgumentError, 'Mimi::Logger arguments are expected to be one of (<String>, <Hash>, [<String>, <Hash>])'
|
206
|
+
end
|
207
|
+
h
|
208
|
+
end
|
209
|
+
|
210
|
+
# Returns current context ID if set, otherwise generates and sets a new context ID
|
211
|
+
# and returns it.
|
212
|
+
#
|
213
|
+
# Context ID is local to the current thread. It identifies a group of instructions
|
214
|
+
# happening within same logical context, as a single operation. For example, processing
|
215
|
+
# an incoming request may be seen as a single context.
|
216
|
+
#
|
217
|
+
# Context ID is logged with every message
|
218
|
+
#
|
219
|
+
# @return [String] a hex encoded context ID
|
220
|
+
#
|
221
|
+
def self.context_id
|
222
|
+
Thread.current[CONTEXT_ID_THREAD_VARIABLE] || new_context_id!
|
97
223
|
end
|
98
224
|
|
225
|
+
def context_id
|
226
|
+
self.class.context_id
|
227
|
+
end
|
228
|
+
|
229
|
+
# Sets the new context ID to the given value
|
230
|
+
#
|
231
|
+
# @param id [String]
|
232
|
+
#
|
233
|
+
def self.context_id=(id)
|
234
|
+
Thread.current[CONTEXT_ID_THREAD_VARIABLE] = id
|
235
|
+
end
|
236
|
+
|
237
|
+
def context_id=(id)
|
238
|
+
self.class.context_id = id
|
239
|
+
end
|
240
|
+
|
241
|
+
# Generates a new random context ID and sets it as current
|
242
|
+
#
|
243
|
+
# @return [String] a new context ID
|
244
|
+
#
|
245
|
+
def self.new_context_id!
|
246
|
+
self.context_id = SecureRandom.hex(CONTEXT_ID_SIZE)
|
247
|
+
end
|
248
|
+
|
249
|
+
def new_context_id!
|
250
|
+
self.class.new_context_id!
|
251
|
+
end
|
252
|
+
|
253
|
+
# private-ish
|
254
|
+
|
99
255
|
def self.module_manifest
|
100
256
|
{
|
101
257
|
log_level: {
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mimi-logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Kukushkin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-04-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mimi-core
|
@@ -123,7 +123,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
123
|
version: '0'
|
124
124
|
requirements: []
|
125
125
|
rubyforge_project:
|
126
|
-
rubygems_version: 2.
|
126
|
+
rubygems_version: 2.7.6
|
127
127
|
signing_key:
|
128
128
|
specification_version: 4
|
129
129
|
summary: A pre-configured logger for microservice applications
|