Log4r - include class name in log output - ruby

I'd like to include the name of the class that invokes the logger in the log output, such as:
[MyClass] here is the message
I've seen the option of using the Contexts but I don't want to have to do something like this throughout my app when I log stuff (keep in mind, I want the class name in every log message):
NDC.push('class:' + self.class.name)
logger.debug 'hello'
I'd like to just call:
logger.debug 'hello'
Any suggestions?

Using the contexts is preferable, but you can use your own formatter (see Log4r formatters)
logger = Logger.new 'test'
outputter = Outputter.stdout
outputter.formatter = PatternFormatter.new(:pattern => "%l - kittens - %m")
logger.outputters = outputter
logger.info 'adorable' # => INFO - kittens - adorable
Or, actually, because you want it to reference self.class my advice would actually to create a Logging module that works like so:
module Logging
def logger
return #logger if #logger
#logger = Logger.new 'test'
outputter = Outputter.stdout
outputter.formatter = PatternFormatter.new(:pattern => "%l - #{self.class} - %m")
#logger.outputters = outputter
#logger
end
end
class HasLogging
include Logging
def test
logger.info 'adorable'
end
end
test = HasLogging.new
test.test # => INFO - HasLogging - adorable
Probably not exactly like that, but you get the idea.

Related

Ruby - Set different log levels for different targets

Wondering how to set different log levels for different targets. Below is my Ruby code that writes a line to both Console and File.
# https://stackoverflow.com/a/6407200
class MultiIO
def initialize(*targets)
#targets = targets
end
def write(*args)
#targets.each do |t|
t.write(*args)
end
end
def close
#targets.each(&:close)
end
end
module Logging
def self.logger(logname, programname, debug = false)
log_file = File.open(logname, "a")
log_file.sync = true
zlogger = Logger.new MultiIO.new(log_file, STDOUT)
zlogger.level = Logger::INFO
zlogger.progname = programname
zlogger.formatter = proc do |serverity, datetime, progname, msg|
"#{datetime.strftime('%Y-%m-%d %I:%M:%S %p %:::z %Z')} - #{serverity} - [#{progname}] | #{msg}\n"
end
zlogger
end
end
I can set the level to Debug if a special env variable is found,
$logger.level = Logger::DEBUG if ENV['enable_debug_logs'] == 'true'
But, not sure how to always write Debug lines to the log file and only Info lines to console.
Does anyone know? Any help is greatly appreciated!
Since the log level is a property of the logger, not of the IO, it sounds to me like what you really need to do is define a MultiLogger rather than a MultiIO. Something along the lines of:
class MultiLogger
attr_reader :default_level, :default_progname, :default_formatter
def initialize(**args)
#default_level = args[:default_level]
#default_progname = args[:default_progname]
#default_formatter = args[:default_formatter]
#loggers = []
Array(args[:loggers]).each { |logger| add_logger(logger) }
end
def add_logger(logger)
logger.level = default_level if default_level
logger.progname = default_progname if default_progname
logger.formatter = default_formatter if default_formatter
#loggers << logger
end
def close
#loggers.map(&:close)
end
Logger::Severity.constants.each do |level|
define_method(level.downcase) do |*args|
#loggers.each { |logger| logger.send(__method__, args) }
end
# These methods are a bit weird in the context of a "multi-logger" with varying levels,
# since they are now returning an `Array`; never a falsey value.
# You may want to define them differently, e.g. `#loggers.all? {...}`, or use a non-predicate method name here.
define_method("#{level.downcase}?".to_sym) do
#loggers.map(&__method__)
end
end
end
# Usage:
log_file = File.open(logname, "a")
log_file.sync = true
file_logger = Logger.new(log_file)
console_logger = Logger.new(STDOUT)
console_logger.level = Logger::INFO
multi_logger = MultiLogger.new(
default_progname: programname,
default_formatter: proc do |severity, datetime, progname, msg|
"#{datetime.strftime('%Y-%m-%d %I:%M:%S %p %:::z %Z')} - #{severity} - [#{progname}] | #{msg}\n"
end,
loggers: [file_logger, console_logger]
)

How to define a common module logging shared among others module

I am developing a script with a big main function, which I have split in several modules.
What I need is to have access to the log functionality from all of them, this means that the log file has to be opened only once, and the access be shared.
This is what I have:
require 'module_1'
require 'module_2'
require 'module_3'
module Main
Module_1.Function_startup()
Module_2.Function_configuration()
Module_3.Function_self_test()
end
Here is the dirty module for the logger I need available in all the other modules.
Ideally I would like to call it as "logger.error", where "logger" returns the instance of the logger, and "error" is the function call on rlogger as rlogger.error.
require 'logger'
module Logging
#rlogger = nil
def init_logger
if #rlogger.nil?
puts "initializing logger"
file_path = File.join(File.dirname(__FILE__), 'checker.log')
open_mode = File::TRUNC # or File::APPEND
file = File.open(file_path, File::WRONLY | open_mode)
#rlogger = Logger.new(file)
#rlogger.datetime_format = "%Y-%m-%d %H:%M:%S"
#rlogger.formatter = proc do |severity, datetime, progname, msg|
con_msg = ""
if msg.match("ERROR:")
con_msg = msg.color(:red)
elsif msg.match("OK!")
con_msg = msg.color(:green)
else
con_msg = msg
end
puts ">>>#{con_msg}"
# notice that the colors introduce extra non-printable characters
# which are not nice in the log file.
"#{datetime}: #{msg}\n"
end
# Here is the first log entry
#rlogger.info('Initialize') {"#{Time.new.strftime("%H-%M-%S")}: Checker v#{#version}"}
end
end
# returns the logger
def logger
if #rlogger.nil?
puts "requesting nil rlogger"
end
#rlogger
end
end
end
Just after require you can add this piece of code
$FILE_LOG = Logging.create_log(File.expand_path('LoggingFile.log'), Logger::DEBUG)
Explanation of the above line : It is calling a function in Logging Module, to create File , Level of Logging is debug.
Below is the piece of code for Module
module Logging
def self.create_log(output_location level)
log = Logger.new(output_location, 'weekly').tap do |l|
next unless l
l.level = level
l.progname = File.basename($0)
l.datetime_format = DateTime.iso8601
l.formatter = proc { |severity, datetime, progname, msg| "#{severity}: #{datetime} - (#{progname}) : #{msg}\n" }
end
log.level = level if level < log.level
log
end
def self.log(msg, level)
# Here I am just logging only FATAL and DEBUG, similarly you can add in different level of logs
if level == :FATAL
$FILE_LOG.fatal(msg)
elsif level == :DEBUG
$FILE_LOG.debug(msg)
end
end
end
Then in Every method every Ruby file, we can use this logging as follows
Logging.log("Message",:FATAL)

How do I log correctly inside my Ruby gem?

Currently I'm using puts, but I'm sure that's not the correct answer. How do I correctly setup a logger, inside my gem, to output my internal logging instead of puts?
The most flexible approach for users of your gem is to let them provide a logger rather than setting it up inside the gem. At its simplest this could be
class MyGem
class << self
attr_accessor :logger
end
end
You then use MyGem.logger.info "hello" to log messages from your gem (you might want to wrap it in a utility method that tests whether a logger is set at all)
Users of your gem can then control where messages get logged to (a file, syslog, stdout, etc...)
You can keep the logger in your top-level module. Allow user's to set their own logger but provide a reasonable default for those who don't care to deal with logging. For e.g.
module MyGem
class << self
attr_writer :logger
def logger
#logger ||= Logger.new($stdout).tap do |log|
log.progname = self.name
end
end
end
end
Then, anywhere within your gem code you can access the logger. For e.g.
class MyGem::SomeClass
def some_method
# ...
MyGem.logger.info 'some info'
end
end
References:
Using the Ruby Logger
Logger
A little example:
gem 'log4r'
require 'log4r'
class MyClass
def initialize(name)
#log = Log4r::Logger.new(name)
#Add outputter
#~ log.outputters << Log4r::FileOutputter.new('log_file', :filename => 'mini_example.log', :level => Log4r::ALL )
log.outputters << Log4r::StdoutOutputter.new('log_stdout') #, :level => Log4r::WARN )
#~ log.outputters << Log4r::StderrOutputter.new('log_stderr', :level => Log4r::ERROR)
#log.level = Log4r::INFO
#log.info("Creation")
#~ #log.level = Log4r::OFF
end
attr_reader :log
def myfunction(*par)
#log.debug("myfunction is called")
#log.warn("myfunction: No parameter") if par.empty?
end
end
x = MyClass.new('x')
x.myfunction
y = MyClass.new('y')
y.myfunction
y.myfunction(:a)
y.log.level = Log4r::DEBUG
y.myfunction(:a)
During initialization you create a Logger (#log). In your methods you call the logger.
With #log.level= (or MyClass#log.level=) you can influence, which messages are used.
You can use different outputters (in my example I log to STDOUT). You can also mix outputters (e.g. STDOUT with warnings, each data (including DEBUG) to a file...)
I think the easiest approach is to use it this way
Rails.logger.info "hello"

How to add a custom log level to logger in ruby?

I need to add a custom log level like "verbose" or "traffic" to ruby logger, how to do?
Your own logger just need to overwrite the Logger#format_severity method, something like this :
class MyLogger < Logger
SEVS = %w(DEBUG INFO WARN ERROR FATAL VERBOSE TRAFFIC)
def format_severity(severity)
SEVS[severity] || 'ANY'
end
def verbose(progname = nil, &block)
add(5, nil, progname, &block)
end
end
You can simply add to the Logger class:
require 'logger'
class Logger
def self.custom_level(tag)
SEV_LABEL << tag
idx = SEV_LABEL.size - 1
define_method(tag.downcase.gsub(/\W+/, '_').to_sym) do |progname, &block|
add(idx, nil, progname, &block)
end
end
# now add levels like this:
custom_level 'TRAFFIC'
custom_level 'ANOTHER-TAG'
end
# using it:
log = Logger.new($stdout)
log.traffic('testing')
log.another_tag('another message here.')
Log levels are nothing but integer constants defined in logger.rb:
# Logging severity.
module Severity
DEBUG = 0
INFO = 1
WARN = 2
ERROR = 3
FATAL = 4
UNKNOWN = 5
end
You can log messages with any level you like using Logger#add method:
l.add 6, 'asd'
#=> A, [2010-02-17T16:25:47.763708 #14962] ANY -- : asd
If you start needing a bunch of custom stuff, it may be worth checking out log4r, which is a flexible logging library that lets you do a bunch of interesting/useful stuff easily.
This is an old question, but since it comes up so high on google, I figured it'd be useful to have to correct answer. You can actually use the Logging.init method. Here's how you would add a trace log level
require 'logging'
Logging.init %w(trace debug info warn error fatal)
Logging.logger.root.level = :trace
Logging.logger.root.add_appenders Logging.appenders.stdout
Logging.logger['hello'].trace 'TEST'
This is using the 2.0.0 of the logging gem.
You can create your own logger by overloading the Logger class

Use Rack::CommonLogger in Sinatra

I have a small web-server that I wrote with Sinatra. I want to be able to log messages to a log file. I've read through http://www.sinatrarb.com/api/index.html and www.sinatrarb.com/intro.html, and I see that Rack has something called Rack::CommonLogger, but I can't find any examples of how it can be accessed and used to log messages. My app is simple so I wrote it as a top-level DSL, but I can switch to subclassing it from SinatraBase if that's part of what's required.
Rack::CommonLogger won't provide a logger to your main app, it will just logs the request like Apache would do.
Check the code by yourself: https://github.com/rack/rack/blob/master/lib/rack/common_logger.rb
All Rack apps have the call method that get's invoked with the HTTP Request env, if you check the call method of this middleware this is what happens:
def call(env)
began_at = Time.now
status, header, body = #app.call(env)
header = Utils::HeaderHash.new(header)
log(env, status, header, began_at)
[status, header, body]
end
The #app in this case is the main app, the middleware is just registering the time the request began at, then it class your middleware getting the [status, header, body] triple, and then invoke a private log method with those parameters, returning the same triple that your app returned in the first place.
The logger method goes like:
def log(env, status, header, began_at)
now = Time.now
length = extract_content_length(header)
logger = #logger || env['rack.errors']
logger.write FORMAT % [
env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-",
env["REMOTE_USER"] || "-",
now.strftime("%d/%b/%Y %H:%M:%S"),
env["REQUEST_METHOD"],
env["PATH_INFO"],
env["QUERY_STRING"].empty? ? "" : "?"+env["QUERY_STRING"],
env["HTTP_VERSION"],
status.to_s[0..3],
length,
now - began_at ]
end
As you can tell, the log method just grabs some info from the request env, and logs in on a logger that is specified on the constructor call, if there is no logger instance then it goes to the rack.errors logger (it seems there is one by default)
The way to use it (in your config.ru):
logger = Logger.new('log/app.log')
use Rack::CommonLogger, logger
run YourApp
If you want to have a common logger in all your app, you could create a simple logger middleware:
class MyLoggerMiddleware
def initialize(app, logger)
#app, #logger = app, logger
end
def call(env)
env['mylogger'] = #logger
#app.call(env)
end
end
To use it, on your config.ru:
logger = Logger.new('log/app.log')
use Rack::CommonLogger, logger
use MyLoggerMiddleware, logger
run MyApp
Hope this helps.
In your config.ru:
root = ::File.dirname(__FILE__)
logfile = ::File.join(root,'logs','requests.log')
require 'logger'
class ::Logger; alias_method :write, :<<; end
logger = ::Logger.new(logfile,'weekly')
use Rack::CommonLogger, logger
require ::File.join(root,'myapp')
run MySinatraApp.new # Subclassed from Sinatra::Application
I followed what I found on this blog post - excerpted below
require 'rubygems'
require 'sinatra'
disable :run
set :env, :production
set :raise_errors, true
set :views, File.dirname(__FILE__) + '/views'
set :public, File.dirname(__FILE__) + '/public'
set :app_file, __FILE__
log = File.new("log/sinatra.log", "a")
STDOUT.reopen(log)
STDERR.reopen(log)
require 'app'
run Sinatra.application
then use puts or print. It worked for me.
class ErrorLogger
def initialize(file)
#file = ::File.new(file, "a+")
#file.sync = true
end
def puts(msg)
#file.puts
#file.write("-- ERROR -- #{Time.now.strftime("%d %b %Y %H:%M:%S %z")}: ")
#file.puts(msg)
end
end
class App < Sinatra::Base
if production?
error_logger = ErrorLogger.new('log/error.log')
before {
env["rack.errors"] = error_logger
}
end
...
end
Reopening STDOUT and redirecting it to a file is not a good idea if you use Passenger. It causes in my case that Passenger does not start. Read https://github.com/phusion/passenger/wiki/Debugging-application-startup-problems#stdout-redirection for this issue.
This would be the right way instead:
logger = ::File.open('log/sinatra.log', 'a+')
Sinatra::Application.use Rack::CommonLogger, logger

Resources