Klogger is an opinionated logger for Ruby applications to allow consistent and structured logging.
- Output can be sent to STDOUT or a file. The logger is backed by the standard Ruby
Logger
class. - Ouput can be presented in various formats.
- Output can be highlighted with appropriate colours based on severity (optional).
- Additional destinations can easily be added for shipping log data to other services.
Installation
Add the gem to your Gemfile.
gem "klogger-logger", '~> 1.1'
Usage
This shows some typical usage of the logger along with example output. The Klogger::Logger
instance will output to STDOUT by default but can be redirectly.
Setting up a logger
To begin, you need an instance of your logger. Loggers are thread-safe so you can use a single instance across multiple classes and requests. There are a few options that you can control about a logger. These are documented below.
Klogger.new(name)
Klogger.new(name, destination: $stderr)
Klogger.new(name, destination: 'log/events.log')
Klogger.new(name, destination: StringIO.new)
Klogger.new(name, formatter: :json)
Klogger.new(name, formatter: :simple)
Klogger.new(name, formatter: :go)
Klogger.new(name, highlight: true)
Klogger.new(name, highlight: Rails.env.development?)
Klogger.new(name, tags: { app: 'example-app' })
Logging
When you want to log something, you have the option of 5 severities (debug
, info
, warn
, error
and fatal
). For this example, we'll use info
but it is interchangable with any of the other severities.
logger.info("Hello world!")
logger.info("Sending e-mail", recipient: "adam@example.com", subject: "Hello world!")
logger.info(ip_address: "1.2.3.4", method: "POST", path: "/login")
logger.info { "Hello world!" }
logger.info('Result of 1 + 1') { 1 + 1 }
Logging exceptions
Exceptions happen and when they do, you want to know about them. Klogger provides a helper method to log exceptions. These will automatically be logged with the error
severity.
begin
rescue => e
logger.exception(e)
logger.exception(e, "Something went wrong")
logger.exception(e, "Something went wrong", tags: { user_id: 123 })
end
Groups
Groups allow you to group related log entries together. They do two things:
- They allow you to add tags to all logs which are within the group
- They assign a random ID to the group which is included with all logs within the group
Here's an example of how they work.
logger.group(url: "https://example.com/my-files.zip") do
logger.info("Download starting")
file = download_file('...')
logger.info("Download complete", size: file.size)
end
You'll notice in that example the comment 92b1b62c
. This is the group ID for this block. This is a random ID which is generated when the group is created. It is included with all logs within the group thus allowing you to search for that reference to find all logs related to that group. If groups are nested, you'll have multiple IDs. By default, these group IDs are not shown in your output.
Klogger.new(name, include_group_ids: true)
When executed like this groups will only apply to the logger in question. If you want to group messages from different loggers, you can use the Klogger.group
method where groups and their data will apply to all Klogger loggers.
logger1 = Klogger.new(:logger1)
logger2 = Klogger.new(:logger2)
Klogger.group(ip: '1.2.3.4') do
logger1.info('Hello world!')
Klogger.group(user: 'user_abcdef')
logger2.info('Example')
end
end
group_id = Klogger.global_groups.add(ip: '1.2.3.4')
Klogger.global_groups.pop
Finally, you can use groups without IDs to simply add tags to all logs within the group using the tagged
method.
logger.tagged(name: 'steve') do
logger.info("Download starting")
end
Tagged Loggers
If you wish to apply tags to a series of log entries but you don't wish to use blocks, you can create a "sub" logger which will always include those tags for all messages sent to it.
logger = Klogger.new(:logger)
tagged_logger = logger.create_tagged_logger(tag: 'my-tag')
tagged_logger.info "Hello world!"
Silencing
Sometimes you don't want to log for a little while. You can use the silence
method to temporarily disable logging.
logger.silence!
logger.unsilence!
logger.silence! do
end
Sending log data elsewhere
In many cases you won't want to keep your log data on a local disk or within STDOUT. You can use this additional option to have data dispatched automatically to other services which you decide upon.
class GraylogDestination
def initialize(host, port)
@notifier = GELF::Notifier.new(host, port)
end
def call(logger, payload, group_ids)
message = payload.delete(:message)
@notifer.notify!(facility: "my-app", short_message: message, group_ids: group_ids, **payload)
end
end
logger = Klogger.new(name)
logger.add_destination(GraylogDestination.new('graylog.example.com', 12201))
logger.with_destination(other_destination) do
end