Module: NRSER::Log

Extended by:
SingleForwardable
Includes:
Mixin
Defined in:
lib/nrser/log/formatters/color.rb,
lib/nrser/log/formatters/mixin.rb,
lib/nrser/log.rb

Overview

Unified logging support via SemanticLogger.

Defined Under Namespace

Modules: Appender, Formatters, Mixin Classes: Logger

Utility Class Methods collapse

Setup Class Methods collapse

Class Method Summary collapse

Methods included from Mixin

included, #logger, #logger=

Class Method Details

.[](subject) ⇒ Object

Class Methods



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/nrser/log.rb', line 91

def self.[] subject
  # key = logger_type_and_name_from subject
  # 
  # if @__loggers__.key? key
  #   ref = @__loggers__[key]
  # 
  #   if ref.weakref_alive?
  #     return
  # 
  instance = NRSER::Log::Logger.new subject
end

.appenderSemanticLogger::Subscriber | nil

The current “main” appender (destination), if any.

This is just to simplify things in simple cases, you can always still add multiple appenders.

Returns:

  • (SemanticLogger::Subscriber | nil)


433
434
435
# File 'lib/nrser/log.rb', line 433

def self.appender
  @appender
end

.body(*tokens) ⇒ Object



494
495
496
# File 'lib/nrser/log.rb', line 494

def self.body *tokens
  formatter.body( *tokens ) if body?
end

.body=(tokens) ⇒ Object



499
500
501
502
503
# File 'lib/nrser/log.rb', line 499

def self.body= tokens
  if body?
    formatter.body = tokens
  end
end

.body?Boolean

Returns:

  • (Boolean)


489
490
491
# File 'lib/nrser/log.rb', line 489

def self.body?
  formatter && formatter.respond_to?( :body )
end

.formatternil, SemanticLogger::Formatters::Default

Short-cut for ‘.appender.formatter`.

Returns:

  • (nil)

    If there is no appender.

  • (SemanticLogger::Formatters::Default)

    If there is an appender, the appender’s ‘#formatter` attribute.



446
447
448
# File 'lib/nrser/log.rb', line 446

def self.formatter
  appender.formatter if appender
end

.header(*tokens) ⇒ nil, HeaderTokens

Calls ‘.formatter.header` if there is a header?.

Parameters:

  • *tokens (Array<Symbol>)

    Optional list of token symbols to set as the header format.

    When empty, the method works as a getter, returning the current header format tokens.

Returns:

  • (nil)

    If there is no appender or it’s formatter doesn’t have a header.

  • (HeaderTokens)

    The current header tokens.

See Also:



477
478
479
# File 'lib/nrser/log.rb', line 477

def self.header *tokens
  formatter.header( *tokens ) if header?
end

.header=(tokens) ⇒ Object



482
483
484
485
486
# File 'lib/nrser/log.rb', line 482

def self.header= tokens
  if header?
    formatter.header = tokens
  end
end

.header?Boolean

Is there a header formatter?

Returns:

  • (Boolean)

    ‘true` if there is an formatter and it responds to ’:header’.

    If it returns ‘false`, it means there is no appender attached or it’s formatter does not mix in NRSER::Log::Formatters::Mixin.

    In this case you can’t read or write to the header, so header= won’t do anything.



462
463
464
# File 'lib/nrser/log.rb', line 462

def self.header?
  formatter && formatter.respond_to?( :header )
end

.level:trace | :debug | :info | :warn | :error | :fatal

Global / default log level, which we always normalize to a symbol.

Returns:

  • (:trace | :debug | :info | :warn | :error | :fatal)


219
220
221
# File 'lib/nrser/log.rb', line 219

def self.level
  level_sym_for SemanticLogger.default_level
end

.level=(level) ⇒ :trace | :debug | :info | :warn | :error | :fatal

Set the global default log level.

Returns:

  • (:trace | :debug | :info | :warn | :error | :fatal)

    Log level symbol.

Raises:

  • When ‘level` is invalid.



243
244
245
# File 'lib/nrser/log.rb', line 243

def self.level= level
  SemanticLogger.default_level = level_sym_for level
end

.level_from_ENV(prefix:) ⇒ Object



248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/nrser/log.rb', line 248

def self.level_from_ENV prefix:
  if NRSER.truthy? ENV["#{ prefix }_TRACE"]
    return :trace
  elsif NRSER.truthy? ENV["#{ prefix }_DEBUG"]
    return :debug
  end
  
  level = ENV["#{ prefix }_LOG_LEVEL"]
  
  unless level.nil? || level == ''
    return level
  end
  
  nil
end

.level_indexFixnum

Note:

There is some funkiness around level indexes that I think/hope I wrote down somewhere, so use with some caution/research.

Integer level index. Forwards to SemanticLogger.default_level_index.

Returns:

  • (Fixnum)


232
233
234
# File 'lib/nrser/log.rb', line 232

def self.level_index
  SemanticLogger.default_level_index
end

.level_sym_for(level) ⇒ :trace | :debug | :info | :warn | :error | :fatal

Normalize a level name or number to a symbol, raising if it’s not valid.

Relies on Semantic Logger’s “internal” SemanticLogger.level_to_index method.

Parameters:

  • Representation (Symbol | String | Integer)

    of a level in one of the following formats:

    1. Symbol - verified as member of SemanticLogger::LEVELS and returned.

    2. String - accepts string representations of the level symbols, case insensitive.

    3. Integer - interpreted as a Ruby StdLib Logger / Rails Logger level, which are different than Semantic Logger’s!

Returns:

  • (:trace | :debug | :info | :warn | :error | :fatal)

    Log level symbol.

Raises:

  • When ‘level` is invalid.

See Also:



206
207
208
209
210
211
212
# File 'lib/nrser/log.rb', line 206

def self.level_sym_for level
  if SemanticLogger::LEVELS.include? level
    level
  else
    SemanticLogger.index_to_level SemanticLogger.level_to_index( level )
  end
end

.logger_type_and_name_from(subject) ⇒ return_type

TODO:

Document logger_name_and_type method.

Returns @todo Document return value.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/nrser/log.rb', line 116

def self.logger_type_and_name_from subject
  case subject
  when String
    [:string, subject]
    
  when Module
    [:const, subject.safe_name]
    
  when Array
    # NOTE  Prob bad to use {NRSER::Types} here since logging gets
    #       required so early, and we want it to *work* then... seems like
    #       it would introduce lots of dependency mess. So just use plain
    #       ol' Ruby.
    unless  subject.length == 2 &&
            subject[0].is_a?( Symbol ) &&
            subject[1].is_a?( String )
      raise NRSER::ArgumentError.new \
        "Subject arrays must be [Symbol, String]; received", subject,
        subject: subject,
        details: -> {
          <<~END
            When passing an {Array}, it must be a pair:
            
            1.  `[0]` must be a {Symbol} that is the logger's subject
                *type*.
                
            2.  `[1]` must be a {String} that is the logger's subject
                *name*.
          END
        }
    end
    
    pair = subject.dup
    pair[0] = :const if [:module, :class].include? pair[0]
    pair
    
  when Hash
    unless subject.length == 1
      raise NRSER::ArgumentError.new \
        "Hash subjects must be a single key/value pair",
        subject: subject
    end
    
    pair = subject.first
    
    unless  pair[0].is_a?( Symbol ) &&
            pair[1].is_a?( String )
      raise NRSER::ArgumentError.new \
        "Subject hashes must be a Symbol key and String value",
        subject: subject
    end
    
    pair[0] = :const if [:module, :class].include? pair[0]
    
    pair
    
  else
    raise NRSER::TypeError.new \
      "Expected `subject` to be String, Module, Array or Hash, ",
      "found #{ subject.class }",
      subject: subject
  end
end

.processorSemanticLogger::Subscriber

Shortcut to SemanticLogger::Processor.instance.

Returns:

  • (SemanticLogger::Subscriber)

    You would think this would be a SemanticLogger::Processor, but it’s actually an appender (SemanticLogger::Subscriber is the base class of appenders, sigh…).



416
417
418
# File 'lib/nrser/log.rb', line 416

def self.processor
  SemanticLogger::Processor.instance
end

.setup!(level: nil, dest: nil, sync: nil, say_hi: :debug, application: nil, env_var_prefix: nil, header: nil, body: nil) ⇒ nil

Setup logging.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (nil)


277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/nrser/log.rb', line 277

def self.setup! level: nil,
                dest: nil,
                sync: nil,
                say_hi: :debug,
                application: nil,
                env_var_prefix: nil,
                header: nil,
                body: nil
  
  unless @__mutex__.try_lock
    raise ThreadError, <<~END
      Mutex is already held.
      
      You should pretty generally NOT have multiple threads trying to
      setup logging at once or re-enter {NRSER::Log.setup}!
    END
  end
  
  # Wrap around everything to make sure we release the mutex
  begin
    # Setup main appender if needed
    setup_appender! dest
    
    # Set sync/async processor state
    setup_sync! sync
    
    # If we didn't receive a level, check the ENV
    if level.nil?
      if env_var_prefix.nil? && !application.nil?
        env_var_prefix = application.gsub( /[^a-zA-Z0-0_]+/, '_' ).upcase
      end
      
      level = level_from_ENV prefix: env_var_prefix
    end
    
    # If we ended up with a level, try to set it (will only log a warning
    # if it fails, not raise, which could crash things on boot)
    setup_level! level unless level.nil?
    
    self.application = application unless application.nil?
    
    # Setup formatter header and body tokens, if needed
    setup_formatter_tokens! :header, header
    setup_formatter_tokens! :body, body
    
  ensure
    # Make sure we release the mutex; don't need to hold it for the rest
    @__mutex__.unlock
  end
  
  # Name the main thread so that `process_info` doesn't look so lame
  setup_main_thread_name!
  
  # Possibly say hi
  setup_say_hi say_hi, dest, sync
  
  nil
  
rescue Exception => error
  # Suppress errors in favor of a warning
  
  logger.warn \
    message: "Error setting up logging",
    payload: {
      args: {
        level: level,
        dest: dest,
        env_var_prefix: env_var_prefix,
        say_hi: say_hi,
      },
    },
    exception: error
  
  nil
end

.setup_for_CLI!(dest: $stderr, sync: true, **kwds) ⇒ nil

Call setup! with some default keywords that are nice for CLI apps.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (nil)


359
360
361
362
363
# File 'lib/nrser/log.rb', line 359

def self.setup_for_CLI! dest: $stderr,
                        sync: true,
                        **kwds
  setup! dest: dest, sync: sync, **kwds
end

.setup_for_console!(dest: $stderr, sync: true, header: { delete: :process_info }, **kwds) ⇒ nil

Call setup! with some default keywords that are nice for interactive session (console/REPL) usage.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (nil)


375
376
377
378
379
380
381
382
383
384
# File 'lib/nrser/log.rb', line 375

def self.setup_for_console! dest: $stderr,
                            sync: true,
                            header: { delete: :process_info },
                            **kwds
  setup! \
    dest: dest,
    sync: sync,
    header: header,
    **kwds
end

.setup_for_rspec!(dest: $stderr, sync: true, header: { delete: :process_info }, **kwds) ⇒ nil

Call setup! but provides default for running RSpec tests: sync, write to

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (nil)


393
394
395
396
397
398
399
400
401
402
# File 'lib/nrser/log.rb', line 393

def self.setup_for_rspec! dest: $stderr,
                            sync: true,
                            header: { delete: :process_info },
                            **kwds
  setup! \
    dest: dest,
    sync: sync,
    header: header,
    **kwds
end

.sync?Boolean

Returns:

  • (Boolean)


421
422
423
# File 'lib/nrser/log.rb', line 421

def self.sync?
  processor.is_a? NRSER::Log::Appender::Sync
end