We'll start with the app.rb
script, which is the entrypoint of our application. First, require the logger
library and create an instance of logger in the constructor of the App
class. Just like Rails, our logger uses a log/development.log
file.
The logger won't create a
log
directory for you, so make sure to create it before running the program.
When the application receives a request, i.e. when the call
method is invoked, we'll log a message containing the HTTP request method and the request path.
require 'logger'
require_relative 'config/routes'
class App
attr_reader :logger
def initialize
@logger = Logger.new('log/development.log')
end
def call(env)
logger.info "#{env['REQUEST_METHOD']}: #{env['REQUEST_PATH']}"
headers = { 'Content-Type' => 'text/html' }
response_html = router.build_response(env)
[200, headers, [response_html]]
end
private
def router
Router.instance
end
end
Alright... let's try this out!
Launch the application by running either rackup
or puma
command, and visit the application in the browser. First, visit the home page at /
and then visit the /articles/index
path.
At this point, you'll find a new development.log
file has been created in the log
directory, containing the following logs.
# Logfile created on 2023-07-29 16:19:13 -0700 by logger.rb/v1.5.3
I, [2023-07-29T17:54:54.618107 #40911] INFO -- : GET: /
I, [2023-07-29T17:55:01.643705 #40911] INFO -- : GET: /articles/index
Our logging system is working as expected. Any time someone visits our application, the logger will make an entry in the log file. You can add as much relevant information as you want in the log.
So that was an info
message. What happens when something goes wrong?
To log an error, use the error
method on the logger object. Here's the modified call
method.
def call(env)
# existing code ...
rescue => e
logger.error("Oops, something went wrong. Error: #{e.message}")
# we still have to return a rack-compliant response
[200, headers, ["Error: #{e.message}"]]
end
I've added a rescue clause that will catch all errors and print the error message to the log file. Finally, we'll return a rack-compliant message containing the error.
To test this, open the ArticlesController
class and raise an error from the index
action.
require_relative "application_controller"
class ArticlesController < ApplicationController
def index
raise "Stack Overflow!"
@title = "Write Software, Well"
@tagline = "Learn to program Ruby and build webapps with Rails"
end
end
Now visit the articles/index
path, and you'll see the following error message has been appended to the log file.
I, [2023-07-29T18:05:19.979988 #41201] INFO -- : GET: /articles/index
E, [2023-07-29T18:05:19.980172 #41201] ERROR -- : Oops, something went wrong. Error: Stack Overflow!
Instead of using string interpolation to insert the error message, you can also use the add
method on the logger, which lets you pass a string, an error object, or any object.
A string: used as-is.
An Exception:
message.message
is used.Anything else:
message.inspect
is used.
def call(env)
# existing code ...
rescue => e
logger.add(Logger::ERROR, e)
# we still have to return a rack-compliant response
[200, headers, ["Error: #{e.message}"]]
end
This logs the following error message (note the stack trace):
I, [2023-07-30T13:24:38.959108 #48772] INFO -- : GET: /articles/index
E, [2023-07-30T13:24:38.959283 #48772] ERROR -- : StackOverflow! (RuntimeError)
weby/controllers/articles_controller.rb:5:in `index'
weby/router.rb:30:in `block in get'
weby/router.rb:40:in `build_response'
weby/app.rb:17:in `call'
Congratulations, we've successfully implemented a working logging solution for our application.