Home » Ruby » Logging in Sinatra?

Logging in Sinatra?

Posted by: admin November 30, 2017 Leave a comment

Questions:

I’m having trouble figuring out how to log messages with Sinatra. I’m not looking to log requests, but rather custom messages at certain points in my app. For example, when fetching a URL I would like to log "Fetching #{url}".

Here’s what I’d like:

  • The ability to specify log levels (ex: logger.info("Fetching #{url}"))
  • In development and testing environments, the messages would be written to the console.
  • In production, only write out messages matching the current log level.

I’m guessing this can easily be done in config.ru, but I’m not 100% sure which setting I want to enable, and if I have to manually create a Logger object myself (and furthermore, which class of Logger to use: Logger, Rack::Logger, or Rack::CommonLogger).

(I know there are similar questions on StackOverflow, but none seem to directly answer my question. If you can point me to an existing question, I will mark this one as a duplicate).

Answers:

Sinatra 1.3 will ship with such a logger object, exactly usable as above. You can use edge Sinatra as described in “The Bleeding Edge“. Won’t be that long until we’ll release 1.3, I guess.

To use it with Sinatra 1.2, do something like this:

require 'sinatra'
use Rack::Logger

helpers do
  def logger
    request.logger
  end
end

Questions:
Answers:

I personally log in Sinatra via:

require 'sinatra'
require 'sequel'
require 'logger'
class MyApp < Sinatra::Application
  configure :production do
    set :haml, { :ugly=>true }
    set :clean_trace, true

    Dir.mkdir('logs') unless File.exist?('logs')

    $logger = Logger.new('logs/common.log','weekly')
    $logger.level = Logger::WARN

    # Spit stdout and stderr to a file during production
    # in case something goes wrong
    $stdout.reopen("logs/output.log", "w")
    $stdout.sync = true
    $stderr.reopen($stdout)
  end

  configure :development do
    $logger = Logger.new(STDOUT)
  end
end

# Log all DB commands that take more than 0.2s
DB = Sequel.postgres 'mydb', user:'dbuser', password:'dbpass', host:'localhost'
DB << "SET CLIENT_ENCODING TO 'UTF8';"
DB.loggers << $logger if $logger
DB.log_warn_duration = 0.2

Questions:
Answers:

Here’s another solution:

module MySinatraAppLogger
  extend ActiveSupport::Concern

  class << self
    def logger_instance
      @logger_instance ||= ::Logger.new(log_file).tap do |logger|
        ::Logger.class_eval { alias :write :'<<' }
        logger.level = ::Logger::INFO
      end
    end

    def log_file
      @log_file ||= File.new("#{MySinatraApp.settings.root}/log/#{MySinatraApp.settings.environment}.log", 'a+').tap do |log_file|
        log_file.sync = true
      end
    end
  end

  included do
    configure do
      enable :logging
      use Rack::CommonLogger, MySinatraAppLogger.logger_instance
    end

    before { env["rack.errors"] = MySinatraAppLogger.log_file }
  end

  def logger
    MySinatraAppLogger.logger_instance
  end
end

class MySinatraApp < Sinatra::Base
  include MySinatraAppLogger
  get '/' do
    logger.info params.inspect
  end
end

Of course, you can do it without ActiveSupport::Concern by putting the configure and before blocks straight into MySinatraApp, but what I like about this approach is that it’s very clean–all logging configuration is totally abstracted out of the main app class.

It’s also very easy to spot where you can change it. For instance, the SO asked about making it log to console in development. It’s pretty obvious here that all you need to do is a little if-then logic in the log_file method.

Questions:
Answers:

If you are using something like unicorn logging or other middleware that tails IO streams, you can easily set up a logger to STDOUT or STDERR

# unicorn.rb
stderr_path "#{app_root}/shared/log/unicorn.stderr.log"
stdout_path "#{app_root}/shared/log/unicorn.stdout.log"

# sinatra_app.rb
set :logger, Logger.new(STDOUT) # STDOUT & STDERR is captured by unicorn
logger.info('some info') # also accessible as App.settings.logger

this allows you to intercept messages at application scope, rather than just having access to logger as request helper