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.
5 Answers
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.

- 12,003
- 9
- 51
- 107

- 1,901
- 13
- 18
-
4Shouldn't the first line of MyLoggerMiddleware#call(env) be: env['rack.errors'] = @logger ? – Lawrence I. Siden Feb 12 '10 at 19:49
-
Also, I don't want to log every request, just warnings and error messages. But I'd like it to be configurable so that I can set the level of logging, as in "debug", "info", "warnings", "errors", ... BTW - My app is not a Rails app. There is no config.ru file. It's a simple Sinatra app. I was hoping to use an existing standard, but can't figure out what that is. Maybe I have to take the CommonLogger you showed me and wing it on my own? – Lawrence I. Siden Feb 12 '10 at 19:55
-
`config.ru` is a configuration file for Rack, not for Rails. Both Sinatra and Rails are Rack-based, so you can use `config.ru` in Sinatra applications as well. – jafrog Sep 30 '12 at 11:29
-
The file link first posted here has changed. It is currently https://github.com/rack/rack/blob/master/lib/rack/common_logger.rb – Douglas G. Allen Feb 02 '16 at 14:17
-
2This doesn't work for me. It has no effect on Sinatra or Rack logging. – Andy Jones Mar 03 '16 at 14:55
-
Where is the Logger class (used in Logger.new) defined? – iphone007 Dec 13 '16 at 21:38
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

- 296,393
- 112
- 651
- 745
-
i try your solution, and it works. i wonder why it have to be in config.ru? – Chamnap May 10 '11 at 04:13
-
a file things I'm not 100% on there, would you mind commenting on what/why some of the lines are there please? – Ian Vaughan Jun 02 '13 at 18:27
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.

- 4,544
- 4
- 25
- 22
-
1That works, but I'd really like to find out how to use Rack::CommonLogger to send messages formatted with timestamps. – Lawrence I. Siden Feb 10 '10 at 21:53
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

- 4,660
- 6
- 30
- 40
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

- 1
- 3