Module: NRSER::Log

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

Overview

Unified logging support via SemanticLogger.

Defined Under Namespace

Modules: Appender, Formatters, Mixin, Types Classes: Logger, Plugin

Utility Class Methods collapse

Setup Class Methods collapse

Class Method Summary collapse

Methods included from Mixin

included, #logger, #logger=

Class Method Details

.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)


536
537
538
# File 'lib/nrser/log.rb', line 536

def self.appender
  @appender
end

.body(*tokens) ⇒ Object



597
598
599
# File 'lib/nrser/log.rb', line 597

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

.body=(tokens) ⇒ Object



602
603
604
605
606
# File 'lib/nrser/log.rb', line 602

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

.body?Boolean

Returns:

  • (Boolean)


592
593
594
# File 'lib/nrser/log.rb', line 592

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

.env_var_prefixnil, ...

Get the current ENV var prefix to use looking for config (when values are not explictly provided).

Returns:

  • (nil)

    A prefix has not been sucessfully set, and we couldn’t figure one out from application.

  • (false)

    Looking for config in ENV has been explicitly disabled.

  • (String)

    The ENV var name prefix we’ll use looking for logging config.



252
253
254
255
256
257
258
# File 'lib/nrser/log.rb', line 252

def self.env_var_prefix
  return @env_var_prefix unless @env_var_prefix.nil?

  return application.to_s.env_varize unless application.nil?
  
  return nil
end

.env_var_prefix=(prefix) ⇒ nil, String | false

Set the env_var_prefix.

Parameters:

  • prefix (String | false)

    Value to set the prefix to. ‘false` disables looking up config in the ENV.

    If anything other than ‘false` or a String that satisfies Sys::Env.var_name? is passed a warning will be logged, the value will be ignored, and `nil` will be returned.

Returns:

  • (nil)

    ‘prefix` was a bad value and the internal variable was not set. A warning was also logged.

  • (String | false)

    The value was set to ‘prefix`.



278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/nrser/log.rb', line 278

def self.env_var_prefix= prefix
  unless prefix == false || NRSER::Sys::Env.var_name?( prefix )
    logger.warn \
      "Bad NRSER::Log.env_var_prefix; must `false` or a String matching" +
      "#{ NRSER::Sys::Env::VAR_NAME_RE }. Ignoring...",
      value: prefix
    
    return nil
  end

  @env_var_prefix = prefix
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.



549
550
551
# File 'lib/nrser/log.rb', line 549

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:



580
581
582
# File 'lib/nrser/log.rb', line 580

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

.header=(tokens) ⇒ Object



585
586
587
588
589
# File 'lib/nrser/log.rb', line 585

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.



565
566
567
# File 'lib/nrser/log.rb', line 565

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)


210
211
212
# File 'lib/nrser/log.rb', line 210

def self.level
  level_sym_for SemanticLogger.default_level
end

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

Set the global default log level.

Parameters:

  • level (Symbol | String | Integer)

    Representation 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.



234
235
236
# File 'lib/nrser/log.rb', line 234

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

.level_from_env(prefix: self.env_var_prefix) ⇒ nil, ...

Try to find a log level in the ENV.

Parameters:

  • prefix (String | false | nil) (defaults to: self.env_var_prefix)

    The prefix to look under.

Returns:

  • (nil)

    We either didn’t look or we didn’t find.

  • (Symbol)

    One of SemanticLogger::LEVELS, returned because we found a NRSER.truthy? ‘<prefix>_DEBUG` or `<prefix>_TRACE`.

  • (String)

    Value found at ‘<prefix>_LOG_LEVEL`. **Note that this may not be a valid level - this method does NOT check!**.



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/nrser/log.rb', line 308

def self.level_from_env prefix: self.env_var_prefix
  # Bail with a `nil` if we can't figure out a prefix (it's `nil`) or
  # looking in the ENV has been disabled (it's `false`).
  return nil unless 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)


223
224
225
# File 'lib/nrser/log.rb', line 223

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:

  • level (Symbol | String | Integer)

    Representation 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:



197
198
199
200
201
202
203
# File 'lib/nrser/log.rb', line 197

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_for(subject) ⇒ Object

Class Methods



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

def self.logger_for 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

.logger_type_and_name_from(subject) ⇒ Object




107
108
109
110
111
112
113
114
115
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
# File 'lib/nrser/log.rb', line 107

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…).



519
520
521
# File 'lib/nrser/log.rb', line 519

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:

  • env_var_prefix (String | false | nil) (defaults to: nil)

    Prefix to ENV var names to look for logging setup config under, like ‘<prefix>_LOG_LEVEL`, `<prefix>_DEBUG` and `<prefix>_TRACE`.

    If ‘nil` (the default), we’ll try to use ‘application` to guess a prefix.

    You can disable any ENV lookups by passing ‘false`.

  • sync (Boolean) (defaults to: nil)

    Enables or disables synchronous logging, where the log message is processed entirely in the logging thread before the log call returns.

    SemanticLogger offloads log formating and writing to a separate thread in it’s standard configuration, an approach presumably aimed at performance, but one that can be problematic when logging mutable values that may change between the log call and the log serialization.

    See

    SemanticLogger and Mutable Values</a>.
    

Returns:

  • (nil)


359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# File 'lib/nrser/log.rb', line 359

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 so that any logging *in here* hopefully
    # has somewhere to go
    setup_appender! dest

    # Set the application, which we may use for the ENV var prefix
    self.application = application unless application.nil?

    # Set the ENV var prefix, if we received one
    self.env_var_prefix = env_var_prefix unless env_var_prefix.nil?
    
    # Set sync/async processor state
    setup_sync! sync
    
    # If we didn't receive a level, check the ENV
    if level.nil?
      level = level_from_env
    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?
    
    # 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:

  • env_var_prefix (String | false | nil)

    Prefix to ENV var names to look for logging setup config under, like ‘<prefix>_LOG_LEVEL`, `<prefix>_DEBUG` and `<prefix>_TRACE`.

    If ‘nil` (the default), we’ll try to use ‘application` to guess a prefix.

    You can disable any ENV lookups by passing ‘false`.

  • sync (Boolean) (defaults to: true)

    Enables or disables synchronous logging, where the log message is processed entirely in the logging thread before the log call returns.

    SemanticLogger offloads log formating and writing to a separate thread in it’s standard configuration, an approach presumably aimed at performance, but one that can be problematic when logging mutable values that may change between the log call and the log serialization.

    See

    SemanticLogger and Mutable Values</a>.
    

Returns:

  • (nil)


442
443
444
445
446
# File 'lib/nrser/log.rb', line 442

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

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

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

Parameters:

  • add_main_logger (Boolean) (defaults to: true)

    Define a ‘logger` method at the top level (global) that gets a logger for the main object.

Returns:

  • (nil)


466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/nrser/log.rb', line 466

def self.setup_for_console! add_main_logger: true,
                            application: $0,
                            dest: $stderr,
                            header: { delete: :process_info },
                            sync: true,
                            **kwds
  setup! \
    dest: dest,
    sync: sync,
    header: header,
    application: application,
    **kwds
  
  if add_main_logger
    TOPLEVEL_BINDING.eval <<~END
      def logger
        NRSER::Log[self]
      end
    END
  end

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:

  • env_var_prefix (String | false | nil)

    Prefix to ENV var names to look for logging setup config under, like ‘<prefix>_LOG_LEVEL`, `<prefix>_DEBUG` and `<prefix>_TRACE`.

    If ‘nil` (the default), we’ll try to use ‘application` to guess a prefix.

    You can disable any ENV lookups by passing ‘false`.

  • sync (Boolean) (defaults to: true)

    Enables or disables synchronous logging, where the log message is processed entirely in the logging thread before the log call returns.

    SemanticLogger offloads log formating and writing to a separate thread in it’s standard configuration, an approach presumably aimed at performance, but one that can be problematic when logging mutable values that may change between the log call and the log serialization.

    See

    SemanticLogger and Mutable Values</a>.
    

Returns:

  • (nil)


496
497
498
499
500
501
502
503
504
505
# File 'lib/nrser/log.rb', line 496

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)


524
525
526
# File 'lib/nrser/log.rb', line 524

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