RubySmart::SimpleLogger
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] =============================================[0m
# [0;33m"some debug"[0m
# [1;34m====================================================================================================[0m
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] =============================================[0m
# [0;33m"some debug"[0m
# [1;34m====================================================================================================[0m
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(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
- nl
- model (rails only)
Docs
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.