Skip to content

FundingCircle/loga

Repository files navigation

Loga

Gem Version CI Build status Code Quality Test Coverage Dependency Status

Description

Loga provides consistent logging across frameworks and environments.

Includes:

  • One tagged logger for all environments
  • Human readable logs for development
  • Structured logs for production (GELF)
  • One Rack logger for all Rack based applications

TOC

Installation

Add this line to your application's Gemfile:

gem 'loga'

Rails

Let Loga know what your application name is and Loga will do the rest.

# config/application.rb
class MyApp::Application < Rails::Application
  config.loga = { service_name: 'my_app' }
end

Loga hooks into the Rails logger initialization process and defines its own logger for all environments.

The logger configuration is adjusted based on the environment:

Production Test Development Others
Output STDOUT log/test.log STDOUT STDOUT
Format gelf simple simple simple

You can customize the configuration to your liking:

# config/application.rb
class MyApp::Application < Rails::Application
  config.loga = {
    device:       File.open("log/application.log", 'a'),
    format:       :gelf,
    service_name: 'my_app',
  }
end

Loga leverages existing Rails configuration options:

  • config.filter_parameters
  • config.log_level
  • config.log_tags

Use these options to customize Loga instead of the Loga options hash.

Inside your application use Rails.logger instead of Loga.logger, even though they are equivalent, to prevent lock-in.

Reduced logs

When the format set to gelf requests logs are reduced to a single log entry, which could include an exception.

This is made possible by silencing these loggers:

  • Rack::Request::Logger
  • ActionDispatch::DebugExceptions
  • ActionController::LogSubscriber
  • ActionView::LogSubscriber
  • ActionMailer::LogSubscriber

Request log tags

To provide consistency between Rails and other Rack frameworks, tags (e.i config.log_tags) are computed with a Loga::Rack::Request as opposed to a ActionDispatch::Request.

Sinatra

With Sinatra Loga needs to be configured manually:

# config.ru
require 'sinatra/base'
require 'loga'

Loga.configure(
  filter_parameters: [:password],
  format: :gelf,
  service_name: 'my_app',
  tags: [:uuid],
)

class MyApp < Sinatra::Base
  set :logging, false # suppress Sinatra request logger

  get '/' do
    Loga.logger.info('Everything is Awesome!')
    'Hello World!'
  end
end

use Loga::Rack::RequestId
use Loga::Rack::Logger

run MyApp

You can now use Loga.logger or assign it to your existing logger. The above configuration also inserts two middleware:

  • Loga::Rack::RequestId makes the request id available to the request logger
  • Loga::Rack::Logger logs requests

You can easily switch between formats by using the LOGA_FORMAT environment variable. The format key in the options takes precedence over the environment variable therefore it must be removed.

LOGA_FORMAT=simple rackup

Sidekiq

Loga 2.7 provides an out-of-the-box support for Sidekiq ~> 7.0.

Output Example

GELF Format

Rails request logger: (includes controller/action name):

curl localhost:3000/ok -X GET -H "X-Request-Id: 12345"

{
   "_request.status":     200,
   "_request.method":     "GET",
   "_request.path":       "/ok",
   "_request.params":     {},
   "_request.request_id": "12345",
   "_request.request_ip": "127.0.0.1",
   "_request.user_agent": null,
   "_request.controller": "ApplicationController#ok",
   "_request.duration":   0,
   "_type":               "request",
   "_service.name":       "my_app",
   "_service.version":    "1.0",
   "_tags":               "12345",
   "short_message":       "GET /ok 200 in 0ms",
   "timestamp":           1450150205.123,
   "host":                "example.com",
   "level":               6,
   "version":             "1.1"
}

Sinatra request output is identical to Rails but without the _request.controller key.

Logger output:

# Rails.logger
# or
# Loga.logger

Loga.configure(service_name: 'my_app', format: :gelf)

Loga.logger.info('I love Loga')
Loga.logger.tagged(%w(USER_123 CRM)) do
  Loga.logger.info('I love Loga with tags')
end
{
  "_service.name":    "my_app",
  "_service.version": "v1.0.0",
  "_tags":            "",
  "host":              "example.com",
  "level":            6,
  "short_message":    "I love Loga",
  "timestamp":        1479402679.663,
  "version":          "1.1"
}
{
  "_service.name":    "my_app",
  "_service.version": "v1.0.0",
  "_tags":            "USER_123 CRM",
  "host":              "example.com",
  "level":            6,
  "short_message":    "I love Loga",
  "timestamp":        1479402706.102,
  "version":          "1.1"
}

Simple Format

Request logger:

curl localhost:3000/ok -X GET -H "X-Request-Id: 12345"

Rails

I, [2016-11-15T16:05:03.614081+00:00 #1][12345] Started GET "/ok" for ::1 at 2016-11-15 16:05:03 +0000
I, [2016-11-15T16:05:03.620176+00:00 #1][12345] Processing by ApplicationController#ok as HTML
I, [2016-11-15T16:05:03.624807+00:00 #1][12345]   Rendering text template
I, [2016-11-15T16:05:03.624952+00:00 #1][12345]   Rendered text template (0.0ms)
I, [2016-11-15T16:05:03.625137+00:00 #1][12345] Completed 200 OK in 5ms (Views: 4.7ms)

Sinatra

I, [2016-11-15T16:10:08.645521+00:00 #1][12345] GET /ok 200 in 0ms type=request data={:request=>{"status"=>200, "method"=>"GET", "path"=>"/ok", "params"=>{}, "request_id"=>"12345", "request_ip"=>"127.0.0.1", "user_agent"=>nil, "duration"=>0}}

Logger output:

Loga.configure(service_name: 'my_app', format: :simple)

Loga.logger.info('I love Loga')
Loga.logger.tagged(%w(USER_123 CRM)) do
  Loga.logger.info('I love Loga with tags')
end
I, [2016-11-17T17:07:46.714215+00:00 #595] I love Loga
I, [2016-11-17T17:07:46.725624+00:00 #595][USER_123 CRM] I love Loga with tags

Road map

Consult the milestones.

Contributing

Loga is in active development, feedback and contributions are welcomed.

Running tests

This project uses appraisal to run tests against different versions of dependencies (e.g. Rails, Sinatra).

Install Loga dependencies with bundle install and then appraisals with bundle exec appraisal install.

Run all tests with bundle exec appraisal rspec.

You can run tests for one appraisal with bundle exec appraisal appraisal-name rspec. Refer to the Appraisals file for a complete lists of appraisals.

Prefix test command with RACK_ENV to switch between environments for Rack based tests RACK_ENV=production bundle exec appraisal rspec.

Experiment Guard support introduced to ease running tests locally bundle exec guard.

CI results are the source of truth.

Credits

License

Copyright (c) 2015 Funding Circle. All rights reserved.

Distributed under the BSD 3-Clause License.