RubySmart::SimpleLogger

GitHub Documentation

Gem Version License

Coverage Status Tests

A simple, multifunctional logging library for Ruby (and Rails). It features a fast, customizable logging with multi-device support (e.g. log to STDOUT AND file). Special (PRE-defined) scenes can be used for interactive CLI and better logging visibility.


Installation

Add this line to your application's Gemfile:

gem 'ruby_smart-simple_logger'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install ruby_smart-simple_logger

Enhancements

  • PRE-defined scenes to fastly create a simple, structured CLI output. (see Scenes)
  • Better log-visibility with masked output through scenes
  • awesome_print gem compatibility for a prettified object debug
  • Multi-device support (write to logfile & to STDOUT & to ... & to ...)
  • 'klass_logger' instances for easier access (see klass_logger)

Examples

require 'simple_logger'

logger = ::SimpleLogger.new

logger.debug @my_custom_object, "test title"
# =========================================== [test title] ===========================================
#    {
#      :a => "hash",
#      :with => {
#        :example => :data
#      }
#    }
# ====================================================================================================
logger = ::SimpleLogger.new(:memory)
logger.debug "some debug"
logger.info "some info"

# uses a new SUB-severity of INFO to handle with error / success logs
logger.success "success called"

logger.logs
# => [
#     [:debug,   2022-07-07 10:58:35 +0200, "some debug"],
#     [:info,    2022-07-07 10:58:35 +0200, "some info"],
#     [:success, 2022-07-07 10:58:35 +0200, "success called"]
#    ]
require 'simple_logger'

logger = ::SimpleLogger.new

logger.theme "Jobs to do"
logger.job_result "A custom job", true
logger.job_result "A other job", "failed", false
logger.job_result "Just a job", "unknown", :yellow

# # Jobs to do
# -------------------------------------------------------------------------------------
# - A custom job                                                     => true
# - A other job                                                      => failed
# - Just a job                                                       => unknown

Usage

You can either create your own logger instance, by calling new on the ::SimpleLogger class or using the logger as a klass_logger.

Instance Usage

require 'simple_logger'

# providing no 'builtin' parameter will auto-detect the best logging solution
# - for CLI / windowed programs it'll send the logs to STDOUT.
# - for rails applications it'll forward all logs to the Rails logger.
# - otherwise it'll store the logs in memory
logger = ::SimpleLogger.new
logger.debug "some debug"
logger.error "that failed ..."

# ----------------------------------------------------------------------------------------------------------------------

alternative_logger = ::SimpleLogger.new :memory
alternative_logger.debug "some debug, just in memory logs"
alternative_logger.error "that failed, also in memory logs..."

# access the logs as array
alternative_logger.logs
# => [[:debug, 2022-07-06 14:49:40 +0200, "some debug, just in memory logs"], [:error, 2022-07-06 14:49:40 +0200, "that failed, also in memory logs..."]]

# access the logs as grouped hash
alternative_logger.logs_to_h
#=> {:debug=>["some debug, just in memory logs"], :error=>["that failed, also in memory logs..."]}

# ----------------------------------------------------------------------------------------------------------------------

multi_logger = ::SimpleLogger.new :memory, :stdout, clr: true, format: :default
multi_logger.debug "some debug, sent to stdout & memory"
# > D, [2023-02-15T10:22:45.030758 #13397]   DEBUG -- : some debug, sent to stdout & memory

# access the logs as array
multi_logger.logs
# => [[:debug, 2023-02-15T10:22:45 +0200, "some debug, sent to stdout & memory"]]

You can also create a new instance from every klass_logger Object (see below).

klass_logger Usage

Instead of creating a new instance you can also create klass_logger on every module by simply extending the ::SimpleLogger::KlassLogger module.

module MyCustomLogger
  extend ::RubySmart::SimpleLogger::KlassLogger

  self.klass_logger_opts = {builtin: :stdout, clr: true}
end

MyCustomLogger.debug "some debug"
MyCustomLogger.error "that failed ...", "It's an error title for better visibility"
MyCustomLogger.theme "My Theme"

# log directly to a customized logfile - created through the builtin module name
MyCustomLogger.klass_logger_opts = {builtin: MyApp::Tasks::SpecialTask}
MyCustomLogger.info "Very nice here"
# => creates a logfile @ log/my_app/tasks/special_task.log

This is already done for the SimpleLogger (or Debugger) module - so you can directly access the methods:

require 'simple_logger'

SimpleLogger.debug "some debug"
SimpleLogger.error "that failed ..."

# resetting options
SimpleLogger.klass_logger_opts = {builtin: :memory, stdout: false}

SimpleLogger.debug "some other debug in memory only ..."
SimpleLogger.logs

# create new logger from current SimpleLogger module
# this will also use the current 'klass_logger_opts', if no other opts are provided ...
other_logger = SimpleLogger.new
other_logger.debug "some other debug in memory only ..."

# create new logger, but don't use 'klass_logger_opts' - instead pipe to the rails logger
other_logger2 = SimpleLogger.new :rails, :stdout
other_logger2.info "directly logs to the rails logger"
other_logger2.info "and also to STDOUT"

Alternatives with already build 'Debugger':

require 'ruby_smart-debugger'

Debugger.debug "some debug"
Debugger.error "that failed ..."

Builtins

While you can just build a new logger (or use the klass_logger) without any arguments, you can also create a new one with builtins.

nil Builtin

A nil builtin will auto-detect the best logging solution for you:

  • For CLI or windowed programs it'll just send the logs to STDOUT.
  • For Debugging (e.g. IDE-related debugging gems) it'll send the logs to the Debugger.
  • For rails-applications it'll send to the current Rails.logger instance.
  • Otherwise it'll store logs temporary in memory (accessible through the #logs method)

Example:

logger = ::SimpleLogger.new
logger.debug "some debug"

stdout / stderr Builtin

A :stdout / :stderr builtin will send to STDOUT / STDERR and uses a colored output by default.

Example:

logger = ::SimpleLogger.new(:stdout)

# creates a nice debug output (by default)
# ============================================== [Debug] =============================================
# "some debug"
# ====================================================================================================
logger.debug "some debug"

debugger Builtin

A :debugger builtin will send to the Debugger (e.g. your IDE's debugging gem)

Example:

logger = ::SimpleLogger.new(:debugger)

# > will be shown within your debugging gem
logger.debug "some debug"

null Builtin

A :null builtin will silently swallow all logging data (so it will not be send).

Example:

logger = ::SimpleLogger.new(:null)

# >
logger.debug "some debug"

rails Builtin

A :rails builtin will always send to the Rails.logger instance.

Example:

logger = ::SimpleLogger.new(:rails)

# sends data to the Rails.logger
logger.debug "some debug"

proc Builtin

A :proc builtin will call the provided proc (through options[:proc]``) everytime a log will be written.

The data will be provided as array ( [severity, time, progname, data] ).

Example:

proc = lambda{|data| puts "---> #{data[0]} | #{data[3]} <---"}

logger = ::SimpleLogger.new(:proc, proc: proc)

# calls the proc with data-array
# => ---> DEBUG | some debug <---
logger.debug "some debug"

memory Builtin

A :memory builtin will always store the logged data within an instance variable and can be accessed through the #logs or #logs_to_h methods.

Example:

logger = ::SimpleLogger.new(:memory)
logger.debug "some debug"
logger.info "some info"

# uses a new SUB-severity of INFO to handle with error / success logs
logger.success "success called"

logger.logs
# => [
#     [:debug,   2022-07-07 10:58:35 +0200, "some debug"],
#     [:info,    2022-07-07 10:58:35 +0200, "some info"],
#     [:success, 2022-07-07 10:58:35 +0200, "success called"]
#    ]

String Builtin

Providing a String will always create and write to a new logfile.

Example:

# creates a new logfile @ log/a_custom/logfile.log
# IMPORTANT: I'll also create a colored, masked output by default and uses the awesome_print pretty debug...
# 
# # Logfile created on 2022-07-07 11:01:27 +0200 by logger.rb/66358
# [1;34m============================================== [Debug] =============================================
# [0;33m"some debug"
# [1;34m====================================================================================================
logger = ::SimpleLogger.new('a_custom/logfile.log')
logger.debug "some debug"


# creates a new logfile @ log/a_custom/other_logfile.log
# Does NOT create a colored output and prevent inspection (e.g. by awesome_print)
# 
# Logfile created on 2022-07-07 11:04:17 +0200 by logger.rb/66358
# ============================================== [Debug] =============================================
# some debug without color, but with mask
# ====================================================================================================
other_logger = ::SimpleLogger.new('a_custom/other_logfile.log', clr: false, inspect: false)
other_logger.debug "some debug without color, but with mask"


# creates a new logfile @ log/a_custom/noformat_logfile.log
# Prevents logs with masks (or other payloads)
# 
# # Logfile created on 2022-07-07 11:34:38 +0200 by logger.rb/66358
# D, [2022-07-07T11:34:39.056395 #23253]   DEBUG -- : some debug without color and mask - uses the default format
noformat_logger = ::SimpleLogger.new('a_custom/noformat_logfile.log', format: :default, payload: false)
noformat_logger.debug "some debug without color and mask - uses the default format"

Module Builtin

Providing a module will also create and write to a new logfile. The path depends on the provided module name.

Example:

# creates a new logfile @ log/users/jobs/import.log
# IMPORTANT: I'll also create a colored, masked output by default and uses the awesome_print pretty debug...
# 
# # Logfile created on 2022-07-07 11:01:27 +0200 by logger.rb/66358
# [1;34m============================================== [Debug] =============================================
# [0;33m"some debug"
# [1;34m====================================================================================================
logger = ::SimpleLogger.new(Users::Jobs::Import)
logger.debug "some debug"

other Builtin

Providing any other Object must respond to #write``.


Formats

The default formatter (if no other was provided through opts[:formatter``) will provide the following PRE-defined formats: Also prints a colored output by default.

default Format

The default format equals the Ruby's Formatter - by default also prints a colored output:

logger = ::SimpleLogger.new(:stdout, format: :default, payload: false)

# D, [2022-07-07T12:22:16.364920 #27527]   DEBUG -- : debug message
logger.debug "debug message"

passthrough Format

The passthrough format is mostly used to just 'passthrough' all args to the device (proc, memory, file, etc.) without formatting anything. This will simply provide an array of args.

Using this format will prevent newlines and does not recognize the :nl / :clr option.

logger = ::SimpleLogger.new(:stdout, format: :passthrough, payload: false)

# ["DEBUG", 2022-07-07 12:25:59 +0200, nil, "debug message"]
logger.debug "debug message"

plain Format

The plain format is only used to forward the provided data, without severity, time, etc. This is the default behaviour of the SimpleLogger - which is used to build scene, masks, ...

Using this format will prevent newlines and does not recognize the :nl / :clr option.

logger = ::SimpleLogger.new(:stdout, format: :plain, payload: false)

# debug message
logger.debug "debug message"

# with payload
payload_logger = ::SimpleLogger.new(:stdout, format: :plain)

# ============================================== [Debug] =============================================
# "debug message"
# ====================================================================================================
payload_logger.debug "debug message"

memory Format

The memory format is only used by the memory-device to store severity, time & data as an array.

Using this format will prevent newlines and does not recognize the :nl / :clr option.

logger = ::SimpleLogger.new(:stdout, format: :memory, payload: false)

# [:debug, 2022-07-07 12:31:19 +0200, "debug message"]
logger.debug "debug message"

datalog Format

The datalog format is used to store every log in a structured data. For datalogs the colored output should also be disabled!

logger = ::SimpleLogger.new(:stdout, format: :datalog, payload: false, clr: false)

# [  DEBUG] [2022-07-07 12:31:43] [#27527] [debug message]
logger.debug "debug message"

Options

Independent of the builtins you can still provide custom options for the logger:

device

Provide a custom device.

logger = ::SimpleLogger.new(device: @my_custom_device)

# same like above
logger = ::SimpleLogger.new(@my_custom_device)

clr

Disable colored output.

logger = ::SimpleLogger.new(clr: false)
logger = ::SimpleLogger.new(:stdout, clr: false)

payload

Disable payloads (from scenes).

logger = ::SimpleLogger.new(payload: false)
logger = ::SimpleLogger.new(:stdout, payload: false)

format

Provide a other format. Possible values: :default, :passthrough, :plain, :memory, :datalog``

logger = ::SimpleLogger.new(format: :default)
logger = ::SimpleLogger.new(:memory, format: :passthrough)

nl

Enable / disable NewLine for formatter.

logger = ::SimpleLogger.new(format: :default, nl: false, payload: false, clr: false)
logger.debug "debug 1"
logger.debug "debug 2"
# D, [2022-07-07T13:42:25.323359 #32139]   DEBUG -- : debug 1D, [2022-07-07T13:42:25.323501 #32139]   DEBUG -- : debug 2

proc (:proc-builtin ONLY)

Provide a callback for the :proc builtin.

logger = ::SimpleLogger.new(:proc, proc: lambda{|data| ... })

stdout (:memory & Module-builtin ONLY)

Enable STDOUT as MultiDevice for the memory builtin.

logger = ::SimpleLogger.new(:memory, stdout: true)

# same as above
logger = ::SimpleLogger.new(
  ::SimpleLogger::Devices::MultiDevice.new.
    register(::SimpleLogger::Devices::MemoryDevice.new, ::SimpleLogger::Formatter.new(format: :memory)).
    register(STDOUT, ::SimpleLogger::Formatter.new(format: :default, nl: true, clr: opts[:clr]))
)

mask

Provide custom mask options.

logger = ::SimpleLogger.new(mask: { char: '#', length: 50, clr: :purple })
logger.debug "debug text"
# ##################### [Debug] ####################
# "debug text"
# ##################################################

level

Change the severity level.

logger = ::SimpleLogger.new(level: :info)
logger.debug "debug text"
# => false

logger.info "info text"
# ============================================== [Info] ==============================================
# info text
# ====================================================================================================

formatter

Provide a custom formatter instance.

logger = ::SimpleLogger.new(formatter: My::Custom::Formatter.new)

inspect

Disable inspect for logger

logger = ::SimpleLogger.new(inspect: false)
logger.debug({a: {custom: 'object'}})
# {:a => { :custom => "object" } }

inspector

Provide a other inspector method for the data-debug.

logger = ::SimpleLogger.new(inspector: :to_s)
logger.debug({ a: 1, b: 2 })
# some debug without inspector
# ============================================== [Debug] =============================================
# {:a=>1, :b=>2}
# ====================================================================================================

defaults

Device default options are still available: shift_age, shift_size, progname, datetime_format, shift_period_suffix, binmode``


Scenes

The following PRE-defined scenes are available. (You can define your own scenes by using the class method .scene``)

debug(data, subject = 'Debug')

# debug method
# severity: debug
# prints: prettified data by using the 'inspect' method
#
# > ================================================= [Debug] ================================================
# > "DEBUGGED DATA" <- analyzed by awesome_print#ai method
# > ==========================================================================================================

info, warn, error, fatal, unknown, success (data, subject = 'name')

# info method (BASE)
# severity: methods name
# prints: enclosed data
#
# > ================================================= [Info] =================================================
# > DATA
# > ==========================================================================================================

header(subject)

# header method
# severity: debug
# prints: prettified subject
#
# > ===========================================================================================================
# > ================================================ <Subject> ================================================
# > ===========================================================================================================
# footer method
# severity: debug
# prints: prettified subject
#
# > ===========================================================================================================
# > ================================================ >Subject< ================================================
# > ===========================================================================================================

topic(subject)

# topic method
# severity: debug
# prints: prettified subject
#
# > --------------------------------------------------------------------------------
# > #----------------------------------- Subject ----------------------------------#

theme(subject)

# theme method
# severity: debug
# prints: prettified, colored subject
#
# > # Subject
# > ----------------------------------------------------------------------

theme_result(result, status = nil)

# theme_result method
# severity: debug
# prints: prettified, colored result
#
# > ----------------------------------------------------------------------
# > -> Result
# >

theme_line

# theme_line method
# severity: debug
# prints: colored line with no text
#
# > ----------------------------------------------------------------------

desc(description)

# desc method
# severity: debug
# prints: colored text
#
# > "description"
# >

job(name)

# job method
# severity: debug
# prints: colored line with job name (on inline formatter it prevents a line-break)
# calls the result method if a block was provided
#
# > - Job name                                                         =>
#     ________________________________________________________________ <- 64 chars

sub_job(name)

# sub_job method
# severity: debug
# prints: line with job name (on inline formatter it prevents a line-break)
# calls the result method if a block was provided
#
# >   * Subjob name                                                    =>
#       ______________________________________________________________ <- 62 chars

result(result, status = nil)

# result method
# severity: debug
# prints: colored result
#
# > Result

job_result(name, result, status = nil)

# job_result method
# severity: debug
# prints: job with combined colored result
#
# > - Job name                                                         => Result

sub_job_result(name, result, status = nil)

# sub_job_result method
# severity: debug
# prints: sub_job with combined colored result
#
# >   * Subjob name                                                    => Result

spec(status)

# spec method
# severity: debug
# prints: colored spec result string - depending on the status (on inline formatter it prevents a line-break)
#
# true      => . (green)
# false     => F (red)
# "other"   => ? (yellow)
#
# > .FFF...??...F....F...F..???....F...??

progress(name, perc)

# progress method
# severity: debug
# prints: colored progress indicator
#
# > - Progress of Step 0                               [  0%] >-------------------------------------------------
# > - Progress of Step 1                               [ 40%] ===================>------------------------------
#
#     ________________________________________________ <- 48 chars
#                                                 50 chars -> __________________________________________________

processed(name, opts)

require 'simple_logger'
l = SimpleLogger.new(:stdout, payload: false)

l.processed("Process Alpha", timer: true) do
  l.info "find files"
  l.info "found 34 files"

  l.processed("extracting ...", timer: true) do
    l.info "10% done"
    l.info "20% done"
    l.info "100% done"

    # returns true to the processed state
    true
  end

  l.processed("transforming ...") do
    l.error "bad memory"
    l.info "rolling back"
    # returns false to the processed state
    false
  end

  # returns nil to the processed state
  nil
end


# ╔ START ❯ Process Alpha
# ╟ find files
# ╟ found 34 files
# ║ ┌ START ❯ extracting ...
# ║ ├ 10% done
# ║ ├ 20% done
# ║ ├ 100% done
# ║ └   END ❯ extracting ... [SUCCESS] (0.000244804)
# ║ ┌ START ❯ transforming ...
# ║ ├ bad memory
# ║ ├ rolling back
# ║ └   END ❯ transforming ... [FAIL]
# ╚   END ❯ Process Alpha (0.001036969)

other useful methods

  • line
  • print
  • nl
  • model (rails only)

Docs

CHANGELOG

Contributing

Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

A copy of the LICENSE can be found @ the docs.

Code of Conduct

Everyone interacting in the project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the CODE OF CONDUCT.