Class: Mimi::Logger

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
Core::Module
Defined in:
lib/mimi/logger.rb,
lib/mimi/logger/version.rb,
lib/mimi/logger/instance.rb,
lib/mimi/logger/null_logger.rb

Overview

Mimi::Logger is a preconfigured logger which outputs log messages to STDOUT.

Defined Under Namespace

Modules: Instance Classes: NullLogger

Constant Summary collapse

CONTEXT_ID_SIZE =

bytes

8
CONTEXT_ID_THREAD_VARIABLE =
'mimi_logger_context_id'
VERSION =
'1.0.1'.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Logger

Creates a new Logger instance

Examples:

logger = Mimi::Logger.new
logger.info 'I am a banana!' # outputs "I, I am a banana!" to the STDOUT

logger = Mimi::Logger.new(level: :debug)
logger.debug 'blabla' # => "D, blabla"

Parameters:

  • io (IO)

    An IO object to output log messages to, defaults to STDOUT

  • opts (Hash)
  • [String,Symbol,Integer] (Hash)

    a customizable set of options

Raises:

  • (ArgumentError)


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/mimi/logger.rb', line 68

def initialize(*args)
  io = args.shift if args.first.is_a?(IO) || args.first.is_a?(StringIO)
  io ||= STDOUT
  opts = args.shift if args.first.is_a?(Hash)
  opts ||= {}
  raise ArgumentError, '(io, opts) are expected as parameters' unless args.empty?

  # module configured?
  self.class.configure() if self.class.options.empty?

  @options = self.class.options.deep_merge(opts)
  @logger_instance = ::Logger.new(io)
  @logger_instance.level = self.class.level_from_any(options[:logger_level])
  io.sync = true if io.respond_to?(:sync=)
  @logger_instance.formatter = self.class.formatter(options)
end

Instance Attribute Details

#logger_instanceObject (readonly)

Returns the value of attribute logger_instance.



21
22
23
# File 'lib/mimi/logger.rb', line 21

def logger_instance
  @logger_instance
end

#optionsObject (readonly)

Returns the value of attribute options.



21
22
23
# File 'lib/mimi/logger.rb', line 21

def options
  @options
end

Class Method Details

.context_idString

Returns current context ID if set, otherwise generates and sets a new context ID and returns it.

Context ID is local to the current thread. It identifies a group of instructions happening within same logical context, as a single operation. For example, processing an incoming request may be seen as a single context.

Context ID is logged with every message.

Returns:

  • (String)

    a hex encoded context ID



266
267
268
# File 'lib/mimi/logger.rb', line 266

def self.context_id
  Thread.current[CONTEXT_ID_THREAD_VARIABLE] || new_context!
end

.context_id=(id) ⇒ String

Sets the new context ID to the given value

Parameters:

  • id (String)

    a new context ID

Returns:

  • (String)


275
276
277
# File 'lib/mimi/logger.rb', line 275

def self.context_id=(id)
  Thread.current[CONTEXT_ID_THREAD_VARIABLE] = id
end

.formatter(local_options) ⇒ Proc

Returns formatter Proc object depending on configured format

Returns:

  • (Proc)


167
168
169
170
171
172
173
174
175
176
# File 'lib/mimi/logger.rb', line 167

def self.formatter(local_options)
  case local_options[:logger_format].to_s
  when 'json'
    formatter_json(local_options)
  when 'string'
    formatter_string(local_options)
  else
    raise "Invalid format specified for Mimi::Logger: '#{local_options[:logger_format]}'"
  end
end

.formatter_json(local_options) ⇒ Proc

Returns formatter for ‘json’ format

Parameters:

  • options (Hash)

    logger options

Returns:

  • (Proc)


184
185
186
187
188
189
190
191
# File 'lib/mimi/logger.rb', line 184

def self.formatter_json(local_options)
  proc do |severity, _datetime, _progname, message|
    h = formatter_message_args_to_hash(message)
    h[:c] = context_id if local_options[:logger_context]
    h[:s] = severity.to_s[0]
    JSON.dump(h) + "\n"
  end
end

.formatter_message_args_to_hash(message_args) ⇒ Hash

Converts logger methods arguments passed in various forms to a message hash.

Arguments to a logger may be passed in 6 different ways: This helper method converts all possible variations into one Hash, where key m: refers to the message and the rest are optional parameters passed in a Hash argument.

Examples:

logger.info('String')
logger.info('String', param1: '..and a Hash')
logger.info(m: 'Just a Hash', param1: 'with optional data')

# and the same 3 ways in a block form
logger.info { ... }

Returns:

  • (Hash)


228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/mimi/logger.rb', line 228

def self.formatter_message_args_to_hash(message_args)
  message_args = message_args.is_a?(String) || message_args.is_a?(Hash) ? [message_args] : message_args
  if !message_args.is_a?(Array) || message_args.size > 2
    raise ArgumentError, "Mimi::Logger arguments expected to be Array of up to 2 elements: #{message_args.inspect}"
  end

  h = {}
  arg1 = message_args.shift
  arg2 = message_args.shift

  if arg1.is_a?(String)
    if arg2 && !arg2.is_a?(Hash)
      raise ArgumentError, 'Mimi::Logger arguments are expected to be one of (<String>, <Hash>, [<String>, <Hash>])'
    end
    h = arg2.dup || {}
    h[:m] = arg1
  elsif arg1.is_a?(Hash)
    if arg2
      raise ArgumentError, 'Mimi::Logger arguments are expected to be one of (<String>, <Hash>, [<String>, <Hash>])'
    end
    h = arg1.dup
  else
    raise ArgumentError, 'Mimi::Logger arguments are expected to be one of (<String>, <Hash>, [<String>, <Hash>])'
  end
  h
end

.formatter_string(local_options) ⇒ Proc

Returns formatter for ‘string’ format

Parameters:

  • options (Hash)

    logger options

Returns:

  • (Proc)


199
200
201
202
203
204
205
206
207
208
209
# File 'lib/mimi/logger.rb', line 199

def self.formatter_string(local_options)
  proc do |severity, _datetime, _progname, message|
    h = formatter_message_args_to_hash(message)
    parts = []
    parts << severity.to_s[0]
    parts << context_id if local_options[:logger_context]
    parts << h[:m].to_s.tr("\n", local_options[:logger_cr_character])
    parts << '...' unless h.except(:m).empty?
    parts.join(', ') + "\n"
  end
end

.level_from_any(value) ⇒ Object

Returns the log level inferred from value

Parameters:

  • value (String, Symbol, Integer)


142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/mimi/logger.rb', line 142

def self.level_from_any(value)
  return value if value.is_a?(Integer)
  case value.to_s.downcase.to_sym
  when :debug
    ::Logger::DEBUG
  when :info
    ::Logger::INFO
  when :warn
    ::Logger::WARN
  when :error
    ::Logger::ERROR
  when :fatal
    ::Logger::FATAL
  when :unknown
    ::Logger::UNKNOWN
  else
    raise ArgumentError, "Invalid value for the log level: '#{value}'"
  end
end

.manifestObject

Mimi::logger module manifest



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/mimi/logger.rb', line 28

def self.manifest
  {
    logger_format: {
      desc: 'String or JSON',
      default: 'json',
      hidden: true
    },
    logger_context: {
      desc: 'Logger will log context',
      type: :boolean,
      default: true,
      hidden: true
    },
    logger_level: {
      desc: 'Logger severity level threshold',
      default: 'info',
      type: ['debug', 'info', 'warn', 'error', 'fatal']
    },
    logger_cr_character: {
      desc: 'Logger replaces new line with alternative CR character',
      default: '', # alternative CR: ↵ ↲ ⏎
      type: :string,
      hidden: true
    }
  }
end

.new_context!String

Starts a new logging context, generates a new random context ID and sets it as current

Returns:

  • (String)

    a new context ID



283
284
285
# File 'lib/mimi/logger.rb', line 283

def self.new_context!
  self.context_id = SecureRandom.hex(CONTEXT_ID_SIZE)
end

.with_new_context(&_block) ⇒ Object

Executes a given block in a new context and restores the context afterwards

Examples:

logger.context_id # => "5d11f7c483dcfb2a"
logger.with_new_context do
  logger.context_id # => "e211ef95633a04b5"
end
logger.context_id # => "5d11f7c483dcfb2a"


313
314
315
316
317
318
# File 'lib/mimi/logger.rb', line 313

def self.with_new_context(&_block)
  with_preserved_context do
    new_context!
    yield
  end
end

.with_preserved_context(&_block) ⇒ Object

Executes a given block and ensures the context is restored afterwards

Examples:

logger.context_id # => "5d11f7c483dcfb2a"
logger.with_preserved_context do
  logger.context_id = "temporary-context"
  logger.context_id # => "temporary-context"
end
logger.context_id # => "5d11f7c483dcfb2a"


297
298
299
300
301
302
# File 'lib/mimi/logger.rb', line 297

def self.with_preserved_context(&_block)
  preserved_context_id = context_id
  yield
ensure
  self.context_id = preserved_context_id
end

Instance Method Details

#debug(*args, &block) ⇒ Object

Logs a new message at the corresponding logging level



104
105
106
# File 'lib/mimi/logger.rb', line 104

def debug(*args, &block)
  logger_instance.debug(args, &block)
end

#error(*args, &block) ⇒ Object

Logs a new message at the corresponding logging level



122
123
124
# File 'lib/mimi/logger.rb', line 122

def error(*args, &block)
  logger_instance.error(args, &block)
end

#fatal(*args, &block) ⇒ Object

Logs a new message at the corresponding logging level



128
129
130
# File 'lib/mimi/logger.rb', line 128

def fatal(*args, &block)
  logger_instance.fatal(args, &block)
end

#info(*args, &block) ⇒ Object

Logs a new message at the corresponding logging level



110
111
112
# File 'lib/mimi/logger.rb', line 110

def info(*args, &block)
  logger_instance.info(args, &block)
end

#levelInteger

Returns the current log level

Returns:

  • (Integer)


89
90
91
# File 'lib/mimi/logger.rb', line 89

def level
  logger_instance.level
end

#level=(value) ⇒ Object

Sets the log level. Allows setting the log level from a String or a Symbol, in addition to the standard ::Logger::INFO etc.

Parameters:

  • value (String, Symbol, Integer)


98
99
100
# File 'lib/mimi/logger.rb', line 98

def level=(value)
  logger_instance.level = self.class.level_from_any(value)
end

#unknown(*args, &block) ⇒ Object

Logs a new message at the corresponding logging level



134
135
136
# File 'lib/mimi/logger.rb', line 134

def unknown(*args, &block)
  logger_instance.unknown(args, &block)
end

#warn(*args, &block) ⇒ Object

Logs a new message at the corresponding logging level



116
117
118
# File 'lib/mimi/logger.rb', line 116

def warn(*args, &block)
  logger_instance.warn(args, &block)
end