Reputation: 2705
I have a Sinatra server running in production environment.
Its logger level is set to INFO, but I want to occasionally change the level to DEBUG for a specific deployment when I am debugging.
Is there a way to do this dynamically without killing the Sinatra server and restarting?
Upvotes: 0
Views: 115
Reputation: 27553
Sinatra defers most of its logging to Racks' Rack::CommonLogger
.
This is pluggable, so you can put your own loggers in there.
Sinatra, and thus rack, run in a long thread. The logger is initialized on first run and cannot be changed in a running app, like almost all settings in Sinatra.
So, what you need is a logger has a specific API to switch logs after initializing. Rack::Commonlogger has no such interface to switch after initializing.
This leaves three directions to solve this (well, other than hacking Sinatra or such):
Benefit: Keeps it all in one place.
Downside: Your adding a lot of complexity, albeit in a lib, to your runtime and web-app. Complexity that has little to do with your business-logic.
Other major downside same as below with "Adapter": it must be toggled per running instance.
E.g. Elastic Logstash or, far simpler, one of many syslogd(8) servers, severa of which are already present on your system. If you stick to Ruby, FluentD is an option.
Benefit: Single Responsibility Principle: your web-app should best keep far from writing logs to files etc, but stick to handling web-requests for your business-logic.
Downside: Adds dependency, making testing etc more complex. Can be solved by reducing the coupling with adapters, see below.
E.g. if you already follow adapter patterns or hexagonal architecture, this is a nice place.
By logging to a proxy that can switch logging classes in runtime, you can configure this in Sinatra on boot, then switch it later in e.g. a before{}
hook, your routes etc.
Commonly, Adapter patterns don't have a way to switch adapters in runtime, though, but that can be added. Most likely pattern to do so, is a proxy pattern:
E.g.
class Adapters
def initialize(config_at_boot)
@@map[:logger] = config_at_boot[:logger]
end
def self.set(name, value)
@map[name] = value
end
def self.get(name)
@map[name]
end
end
class Adapters::Log
extend Forwardable
def_delegate(:logger, :log, :info, :debug, :warn, :error)
private
def logger
Adapters.get(
end
end
class Adapters::Log::SilentLogger; end
class Adapters::Log::TestLogger; end
class Adapters::Log::CommonLogger; end
# Use as:
Adapters.initialize(logger: Adapters::Log:::SilentLogger)
logger = Adapters::Log.new
logger.info("I will be logged using the SilentLogger")
Adapters.set(logger: Adapters::Log::TestLogger)
logger.warn("I will be logged using the TestLogger")
# web.rb
configure do
Adapters.new(logger: Adapters::Log::TestLogger.new)
end
# Then, later, e.g in a route
post '/switch_logger' do
Adapters.set(logger: Adapters::Log::CommonLogger)
end
# Or as a filter
before do
Adapters.set(logger: params[:logger]) # This is really insecure, you need additional protections!
end
Benefits: Adapters and proxies are common patterns to keep your business logic separate from "communication with outside". Logging is such communication. Downside: You need to either already use these patterns, or to implement them clean. They will clutter your app with indirections. Esp. Logger in Sinatra, and Rack are proxies themselves already, so you will be adding more proxies; more complexity.
Another major downside is that, by keeping the logging adapters in your app, you will need to change logging adapters in each running version. Whereas a logger outside of your app can be a central service that accepts logs from any amount of running sinatra instances.
Personally, I mix the Adapter pattern with a dedicated logging service. But, YAGNI, only when the need arises to "do more with logs". This is because with Sinatra, I already use Hexagonal Architecture (where Sinatra acts the HTTP adapter, really) and with adapters, so putting the logging in such an adapter only keeps the application consistent.
Upvotes: 1