From 96e9418fd886cbefe5adf9c9acdb74daabcce63c Mon Sep 17 00:00:00 2001 From: =?utf8?q?Franti=C5=A1ek=20Dvo=C5=99=C3=A1k?= Date: Wed, 12 Mar 2014 10:21:45 +0100 Subject: [PATCH] logstasher - initial gem2deb package. --- ruby-logstasher/.gitignore | 5 + ruby-logstasher/.rspec | 2 + ruby-logstasher/.travis.yml | 12 ++ ruby-logstasher/Gemfile | 15 ++ ruby-logstasher/Guardfile | 10 ++ ruby-logstasher/README.md | 104 +++++++++++ ruby-logstasher/Rakefile | 50 ++++++ ruby-logstasher/checksums.yaml.gz | Bin 0 -> 421 bytes ruby-logstasher/debian/changelog | 5 + ruby-logstasher/debian/compat | 1 + ruby-logstasher/debian/control | 19 +++ ruby-logstasher/debian/copyright | 35 ++++ ruby-logstasher/debian/ruby-logstasher.docs | 2 + ruby-logstasher/debian/ruby-test-files.yaml | 5 + ruby-logstasher/debian/rules | 15 ++ ruby-logstasher/debian/source/format | 1 + ruby-logstasher/debian/watch | 2 + ruby-logstasher/lib/logstasher.rb | 107 ++++++++++++ ruby-logstasher/lib/logstasher/device/redis.rb | 64 +++++++ ruby-logstasher/lib/logstasher/log_subscriber.rb | 96 +++++++++++ .../action_controller/metal/instrumentation.rb | 35 ++++ .../lib/logstasher/rails_ext/rack/logger.rb | 24 +++ ruby-logstasher/lib/logstasher/railtie.rb | 14 ++ ruby-logstasher/lib/logstasher/version.rb | 3 + ruby-logstasher/logstasher.gemspec | 26 +++ ruby-logstasher/metadata.yml | 122 +++++++++++++ .../spec/lib/logstasher/device/redis_spec.rb | 79 +++++++++ .../spec/lib/logstasher/log_subscriber_spec.rb | 190 +++++++++++++++++++++ ruby-logstasher/spec/lib/logstasher_spec.rb | 179 +++++++++++++++++++ ruby-logstasher/spec/spec_helper.rb | 50 ++++++ 30 files changed, 1272 insertions(+) create mode 100644 ruby-logstasher/.gitignore create mode 100644 ruby-logstasher/.rspec create mode 100644 ruby-logstasher/.travis.yml create mode 100644 ruby-logstasher/Gemfile create mode 100644 ruby-logstasher/Guardfile create mode 100644 ruby-logstasher/README.md create mode 100644 ruby-logstasher/Rakefile create mode 100644 ruby-logstasher/checksums.yaml.gz create mode 100644 ruby-logstasher/debian/changelog create mode 100644 ruby-logstasher/debian/compat create mode 100644 ruby-logstasher/debian/control create mode 100644 ruby-logstasher/debian/copyright create mode 100644 ruby-logstasher/debian/ruby-logstasher.docs create mode 100644 ruby-logstasher/debian/ruby-test-files.yaml create mode 100755 ruby-logstasher/debian/rules create mode 100644 ruby-logstasher/debian/source/format create mode 100644 ruby-logstasher/debian/watch create mode 100644 ruby-logstasher/lib/logstasher.rb create mode 100644 ruby-logstasher/lib/logstasher/device/redis.rb create mode 100644 ruby-logstasher/lib/logstasher/log_subscriber.rb create mode 100644 ruby-logstasher/lib/logstasher/rails_ext/action_controller/metal/instrumentation.rb create mode 100644 ruby-logstasher/lib/logstasher/rails_ext/rack/logger.rb create mode 100644 ruby-logstasher/lib/logstasher/railtie.rb create mode 100644 ruby-logstasher/lib/logstasher/version.rb create mode 100644 ruby-logstasher/logstasher.gemspec create mode 100644 ruby-logstasher/metadata.yml create mode 100644 ruby-logstasher/spec/lib/logstasher/device/redis_spec.rb create mode 100644 ruby-logstasher/spec/lib/logstasher/log_subscriber_spec.rb create mode 100644 ruby-logstasher/spec/lib/logstasher_spec.rb create mode 100644 ruby-logstasher/spec/spec_helper.rb diff --git a/ruby-logstasher/.gitignore b/ruby-logstasher/.gitignore new file mode 100644 index 0000000..4af26ce --- /dev/null +++ b/ruby-logstasher/.gitignore @@ -0,0 +1,5 @@ +*.gem +.bundle +Gemfile.lock +pkg/* +.idea \ No newline at end of file diff --git a/ruby-logstasher/.rspec b/ruby-logstasher/.rspec new file mode 100644 index 0000000..5f16476 --- /dev/null +++ b/ruby-logstasher/.rspec @@ -0,0 +1,2 @@ +--color +--format progress diff --git a/ruby-logstasher/.travis.yml b/ruby-logstasher/.travis.yml new file mode 100644 index 0000000..b523bb9 --- /dev/null +++ b/ruby-logstasher/.travis.yml @@ -0,0 +1,12 @@ +language: ruby +rvm: + - 1.9.3 + - 2.0.0 + +env: + - "RAILS_VERSION=4.0" + - "RAILS_VERSION=3.2" + - "RAILS_VERSION=3.1" + - "RAILS_VERSION=3.0" + +script: bundle exec rspec diff --git a/ruby-logstasher/Gemfile b/ruby-logstasher/Gemfile new file mode 100644 index 0000000..2ca61bf --- /dev/null +++ b/ruby-logstasher/Gemfile @@ -0,0 +1,15 @@ +source "https://rubygems.org" + +# Specify your gem's dependencies in logstasher.gemspec +gemspec + +group :test do + gem 'growl' + gem 'guard' + gem 'guard-rspec' + gem 'rails', "~> #{ENV["RAILS_VERSION"] || "3.2.0"}" + gem 'rb-fsevent', '~> 0.9' + gem 'rcov', :platforms => :mri_18 + gem 'redis', :require => false + gem 'simplecov', :platforms => :mri_19, :require => false +end diff --git a/ruby-logstasher/Guardfile b/ruby-logstasher/Guardfile new file mode 100644 index 0000000..4dda56e --- /dev/null +++ b/ruby-logstasher/Guardfile @@ -0,0 +1,10 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme +interactor :simple + +guard 'rspec', :version => 2 do + watch(%r{^spec/.+_spec\.rb$}) + watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } + watch('spec/spec_helper.rb') { "spec" } +end + diff --git a/ruby-logstasher/README.md b/ruby-logstasher/README.md new file mode 100644 index 0000000..a302ac1 --- /dev/null +++ b/ruby-logstasher/README.md @@ -0,0 +1,104 @@ +# Logstasher [![Gem Version](https://badge.fury.io/rb/logstasher.png)](http://badge.fury.io/rb/logstasher) [![Build Status](https://secure.travis-ci.org/shadabahmed/logstasher.png)](https://secure.travis-ci.org/shadabahmed/logstasher) +### Awesome Logging for Rails !! + +This gem is heavily inspired from [lograge](https://github.com/roidrage/lograge), but it's focused on one thing and one thing only. That's making your logs awesome like this: + +[![Awesome Logs](http://i.imgur.com/zZXWQNp.png)](http://i.imgur.com/zZXWQNp.png) + +How it's done ? + +By, using these awesome tools: +* [Logstash](http://logstash.net) - Store and index your logs +* [Kibana](http://kibana.org/) - for awesome visualization. This is optional though, and you can use any other visualizer + +To know how to setup these tools - visit my [blog](http://shadabahmed.com/blog/2013/04/30/logstasher-for-awesome-rails-logging) + +## About logstasher + +This gem purely focuses on how to generate logstash compatible logs i.e. *logstash json event format*, without any overhead. Infact, logstasher logs to a separate log file named `logstash_.log`. +The reason for this separation: + * To have a pure json log file + * Prevent any logger messages(e.g. info) getting into our pure json logs + +Before **logstasher** : + +``` +Started GET "/login" for 10.109.10.135 at 2013-04-30 08:59:01 -0400 +Processing by SessionsController#new as HTML + Rendered sessions/new.html.haml within layouts/application (4.3ms) + Rendered shared/_javascript.html.haml (0.6ms) + Rendered shared/_flashes.html.haml (0.2ms) + Rendered shared/_header.html.haml (52.9ms) + Rendered shared/_title.html.haml (0.2ms) + Rendered shared/_footer.html.haml (0.2ms) +Completed 200 OK in 532ms (Views: 62.4ms | ActiveRecord: 0.0ms | ND API: 0.0ms) +``` + +After **logstasher**: + +``` +{"@source":"unknown","@tags":["request"],"@fields":{"method":"GET","path":"/","format":"html","controller":"file_servers" +,"action":"index","status":200,"duration":28.34,"view":25.96,"db":0.88,"ip":"127.0.0.1","route":"file_servers#index", +"parameters":"","ndapi_time":null,"uuid":"e81ecd178ed3b591099f4d489760dfb6","user":"shadab_ahmed@abc.com", +"site":"internal"},"@timestamp":"2013-04-30T13:00:46.354500+00:00"} +``` + +By default, the older format rails request logs are disabled, though you can enable them. + +## Installation + +In your Gemfile: + + gem 'logstasher' + +### Configure your `.rb` e.g. `development.rb` + + # Enable the logstasher logs for the current environment + config.logstasher.enabled = true + + # This line is optional if you do not want to suppress app logs in your .log + config.logstasher.suppress_app_log = false + + # This line is optional, it allows you to set a custom value for the @source field of the log event + config.logstasher.source = 'your.arbitrary.source' + +## Logging params hash + +Logstasher can be configured to log the contents of the params hash. When enabled, the contents of the params hash (minus the ActionController internal params) +will be added to the log as a deep hash. This can cause conflicts within the Elasticsearch mappings though, so should be enabled with care. Conflicts will occur +if different actions (or even different applications logging to the same Elasticsearch cluster) use the same params key, but with a different data type (e.g. a +string vs. a hash). This can lead to lost log entries. Enabling this can also significantly increase the size of the Elasticsearch indexes. + +To enable this, add the following to your `.rb` + + # Enable logging of controller params + config.logstasher.log_controller_parameters = true + +## Adding custom fields to the log + +Since some fields are very specific to your application for e.g. *user_name*, so it is left upto you, to add them. Here's how to add those fields to the logs: + + # Create a file - config/initializers/logstasher.rb + + if LogStasher.enabled + LogStasher.add_custom_fields do |fields| + # This block is run in application_controller context, + # so you have access to all controller methods + fields[:user] = current_user && current_user.mail + fields[:site] = request.path =~ /^\/api/ ? 'api' : 'user' + + # If you are using custom instrumentation, just add it to logstasher custom fields + LogStasher.custom_fields << :myapi_runtime + end + end + +## Versions +All versions require Rails 3.0.x and higher and Ruby 1.9.2+. Tested on Rails 4 and Ruby 2.0 + +## Development + - Run tests - `rake` + - Generate test coverage report - `rake coverage`. Coverage report path - coverage/index.html + +## Copyright + +Copyright (c) 2013 Shadab Ahmed, released under the MIT license diff --git a/ruby-logstasher/Rakefile b/ruby-logstasher/Rakefile new file mode 100644 index 0000000..0cc03a1 --- /dev/null +++ b/ruby-logstasher/Rakefile @@ -0,0 +1,50 @@ +require "bundler/gem_tasks" +require 'rspec/core/rake_task' + +# Add default task. When you type just rake command this would run. Travis CI runs this. Making this run spec +desc 'Default: run specs.' +task :default => :spec + +# Defining spec task for running spec +desc "Run specs" +RSpec::Core::RakeTask.new('spec') do |spec| + # Pattern filr for spec files to run. This is default btw. + spec.pattern = "./spec/**/*_spec.rb" +end + +# Run the rdoc task to generate rdocs for this gem +require 'rdoc/task' +RDoc::Task.new do |rdoc| + require "logstasher/version" + version = LogStasher::VERSION + + rdoc.rdoc_dir = 'rdoc' + rdoc.title = "logstasher #{version}" + rdoc.rdoc_files.include('README*') + rdoc.rdoc_files.include('lib/**/*.rb') +end + +# Code coverage tasks. Different for Ruby 1.8 vs 1.9 +if RUBY_VERSION =~ /^1\.8/ + # Ruby 1.8 uses rcov for code coverage + RSpec::Core::RakeTask.new(:coverage) do |spec| + spec.pattern = 'spec/**/*_spec.rb' + spec.rcov = true + spec.rcov_opts = %w{--exclude pkg\/,spec\/,features\/} + end +else + # Ruby 1.9+ using simplecov. Note: Simplecov config defined in spec_helper + desc "Code coverage detail" + task :coverage do + ENV['COVERAGE'] = "true" + Rake::Task['spec'].execute + end +end + +task :console do + require 'irb' + require 'irb/completion' + require 'logstasher' + ARGV.clear + IRB.start +end diff --git a/ruby-logstasher/checksums.yaml.gz b/ruby-logstasher/checksums.yaml.gz new file mode 100644 index 0000000000000000000000000000000000000000..907fcb191832ac5624660a6e0758a6681f29a7df GIT binary patch literal 421 zcmV;W0b2eaiwFR+tQk`R1BFpdkDD+Mz2{fZy{7^Lmz6jqGO$FNL8K;Ve4|Mf81Noe zkqB0Qd?%asR%uTjd&Y0x$9B8jeET`R_aAHU(uTtxnk;*H-2{RAb?Yzvt^a=Yufe~O z-&^Sffh{2(%ldpGKCZ9hcZ~e#rU`Zj9 znHg2JKzSI5X(4VxtMfZ_ZCZEaQJ+_9j#J%LDe-Cig#GchOC$9aYvepaSKY~<7pvQ( z*5?FD5Z0$zdI|b@0AGy2mcdMMZ74yN$Ec_1$&aUy{BE_}rXopXQbayaz|TzF#$Z71 zhY?IJz3Ymu@!$p$SwWu$(8YvZn*dAg#155zz1Q;2DkSSuS!NYPYlt!GJaT2RlEEkzjTiaG(+ PkZt||?b?!Xcme Wed, 12 Mar 2014 10:19:53 +0100 diff --git a/ruby-logstasher/debian/compat b/ruby-logstasher/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/ruby-logstasher/debian/compat @@ -0,0 +1 @@ +7 diff --git a/ruby-logstasher/debian/control b/ruby-logstasher/debian/control new file mode 100644 index 0000000..20acdb1 --- /dev/null +++ b/ruby-logstasher/debian/control @@ -0,0 +1,19 @@ +Source: ruby-logstasher +Section: ruby +Priority: optional +Maintainer: Debian Ruby Extras Maintainers +Uploaders: <> +Build-Depends: debhelper (>= 7.0.50~), gem2deb (>= 0.6.1~) +Standards-Version: 3.9.4 +#Vcs-Git: git://anonscm.debian.org/pkg-ruby-extras/ruby-logstasher.git +#Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-ruby-extras/ruby-logstasher.git;a=summary +Homepage: https://github.com/shadabahmed/logstasher +XS-Ruby-Versions: all + +Package: ruby-logstasher +Architecture: all +XB-Ruby-Versions: ${ruby:Versions} +Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter +# logstash-event (~> 1.1.0), rspec (>= 0, development), bundler (>= 1.0.0, development), rails (>= 3.0, development) +Description: Awesome rails logs + Awesome rails logs diff --git a/ruby-logstasher/debian/copyright b/ruby-logstasher/debian/copyright new file mode 100644 index 0000000..3889113 --- /dev/null +++ b/ruby-logstasher/debian/copyright @@ -0,0 +1,35 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: logstasher +Source: FIXME + +Files: * +Copyright: + +License: GPL-2+ (FIXME) + +Files: debian/* +Copyright: 2014 <> +License: GPL-2+ (FIXME) +Comment: the Debian packaging is licensed under the same terms as the original package. + +License: GPL-2+ (FIXME) + This program is free software; you can redistribute it + and/or modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later + version. + . + This program is distributed in the hope that it will be + useful, but WITHOUT ANY WARRANTY; without even the implied + warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the GNU General Public License for more + details. + . + You should have received a copy of the GNU General Public + License along with this package; if not, write to the Free + Software Foundation, Inc., 51 Franklin St, Fifth Floor, + Boston, MA 02110-1301 USA + . + On Debian systems, the full text of the GNU General Public + License version 2 can be found in the file + `/usr/share/common-licenses/GPL-2'. diff --git a/ruby-logstasher/debian/ruby-logstasher.docs b/ruby-logstasher/debian/ruby-logstasher.docs new file mode 100644 index 0000000..07b3c9e --- /dev/null +++ b/ruby-logstasher/debian/ruby-logstasher.docs @@ -0,0 +1,2 @@ +# FIXME: READMEs found +# README.md diff --git a/ruby-logstasher/debian/ruby-test-files.yaml b/ruby-logstasher/debian/ruby-test-files.yaml new file mode 100644 index 0000000..e85363c --- /dev/null +++ b/ruby-logstasher/debian/ruby-test-files.yaml @@ -0,0 +1,5 @@ +--- +- spec/lib/logstasher/device/redis_spec.rb +- spec/lib/logstasher/log_subscriber_spec.rb +- spec/lib/logstasher_spec.rb +- spec/spec_helper.rb diff --git a/ruby-logstasher/debian/rules b/ruby-logstasher/debian/rules new file mode 100755 index 0000000..82ddc0c --- /dev/null +++ b/ruby-logstasher/debian/rules @@ -0,0 +1,15 @@ +#!/usr/bin/make -f +#export DH_VERBOSE=1 +# +# Uncomment to ignore all test failures (but the tests will run anyway) +#export DH_RUBY_IGNORE_TESTS=all +# +# Uncomment to ignore some test failures (but the tests will run anyway). +# Valid values: +#export DH_RUBY_IGNORE_TESTS=ruby1.9.1 ruby2.0 require-rubygems +# +# If you need to specify the .gemspec (eg there is more than one) +#export DH_RUBY_GEMSPEC=gem.gemspec + +%: + dh $@ --buildsystem=ruby --with ruby diff --git a/ruby-logstasher/debian/source/format b/ruby-logstasher/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/ruby-logstasher/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/ruby-logstasher/debian/watch b/ruby-logstasher/debian/watch new file mode 100644 index 0000000..2f6962a --- /dev/null +++ b/ruby-logstasher/debian/watch @@ -0,0 +1,2 @@ +version=3 +http://pkg-ruby-extras.alioth.debian.org/cgi-bin/gemwatch/logstasher .*/logstasher-(.*).tar.gz diff --git a/ruby-logstasher/lib/logstasher.rb b/ruby-logstasher/lib/logstasher.rb new file mode 100644 index 0000000..e3887ec --- /dev/null +++ b/ruby-logstasher/lib/logstasher.rb @@ -0,0 +1,107 @@ +require 'logstasher/version' +require 'logstasher/log_subscriber' +require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/core_ext/string/inflections' +require 'active_support/ordered_options' + +module LogStasher + extend self + attr_accessor :logger, :enabled, :log_controller_parameters, :source + # Setting the default to 'unknown' to define the default behaviour + @source = 'unknown' + + def remove_existing_log_subscriptions + ActiveSupport::LogSubscriber.log_subscribers.each do |subscriber| + case subscriber + when ActionView::LogSubscriber + unsubscribe(:action_view, subscriber) + when ActionController::LogSubscriber + unsubscribe(:action_controller, subscriber) + end + end + end + + def unsubscribe(component, subscriber) + events = subscriber.public_methods(false).reject{ |method| method.to_s == 'call' } + events.each do |event| + ActiveSupport::Notifications.notifier.listeners_for("#{event}.#{component}").each do |listener| + if listener.instance_variable_get('@delegate') == subscriber + ActiveSupport::Notifications.unsubscribe listener + end + end + end + end + + def add_default_fields_to_payload(payload, request) + payload[:ip] = request.remote_ip + payload[:route] = "#{request.params[:controller]}##{request.params[:action]}" + self.custom_fields += [:ip, :route] + if self.log_controller_parameters + payload[:parameters] = payload[:params].except(*ActionController::LogSubscriber::INTERNAL_PARAMS) + self.custom_fields += [:parameters] + end + end + + def add_custom_fields(&block) + ActionController::Metal.send(:define_method, :logtasher_add_custom_fields_to_payload, &block) + ActionController::Base.send(:define_method, :logtasher_add_custom_fields_to_payload, &block) + end + + def setup(app) + app.config.action_dispatch.rack_cache[:verbose] = false if app.config.action_dispatch.rack_cache + # Path instrumentation class to insert our hook + require 'logstasher/rails_ext/action_controller/metal/instrumentation' + require 'logstash-event' + self.suppress_app_logs(app) + LogStasher::RequestLogSubscriber.attach_to :action_controller + self.logger = app.config.logstasher.logger || new_logger("#{Rails.root}/log/logstash_#{Rails.env}.log") + self.logger.level = app.config.logstasher.log_level || Logger::WARN + self.source = app.config.logstasher.source unless app.config.logstasher.source.nil? + self.enabled = true + self.log_controller_parameters = !! app.config.logstasher.log_controller_parameters + end + + def suppress_app_logs(app) + if configured_to_suppress_app_logs?(app) + require 'logstasher/rails_ext/rack/logger' + LogStasher.remove_existing_log_subscriptions + end + end + + def configured_to_suppress_app_logs?(app) + # This supports both spellings: "suppress_app_log" and "supress_app_log" + !!(app.config.logstasher.suppress_app_log.nil? ? app.config.logstasher.supress_app_log : app.config.logstasher.suppress_app_log) + end + + def custom_fields + Thread.current[:logstasher_custom_fields] ||= [] + end + + def custom_fields=(val) + Thread.current[:logstasher_custom_fields] = val + end + + def log(severity, msg) + if self.logger && self.logger.send("#{severity}?") + event = LogStash::Event.new('@source' => self.source, '@fields' => {:message => msg, :level => severity}, '@tags' => ['log']) + self.logger.send severity, event.to_json + end + end + + %w( fatal error warn info debug unknown ).each do |severity| + eval <<-EOM, nil, __FILE__, __LINE__ + 1 + def #{severity}(msg) + self.log(:#{severity}, msg) + end + EOM + end + + private + + def new_logger(path) + FileUtils.touch path # prevent autocreate messages in log + Logger.new path + end +end + +require 'logstasher/railtie' if defined?(Rails) diff --git a/ruby-logstasher/lib/logstasher/device/redis.rb b/ruby-logstasher/lib/logstasher/device/redis.rb new file mode 100644 index 0000000..eb6d5ce --- /dev/null +++ b/ruby-logstasher/lib/logstasher/device/redis.rb @@ -0,0 +1,64 @@ +require 'redis' + +module LogStasher + module Device + class Redis + + attr_reader :options, :redis + + def initialize(options = {}) + @options = default_options.merge(options) + validate_options + configure_redis + end + + def data_type + options[:data_type] + end + + def key + options[:key] + end + + def redis_options + unless @redis_options + default_keys = default_options.keys + @redis_options = options.select { |k, v| !default_keys.include?(k) } + end + + @redis_options + end + + def write(log) + case data_type + when 'list' + redis.rpush(key, log) + when 'channel' + redis.publish(key, log) + else + fail "Unknown data type #{data_type}" + end + end + + def close + redis.quit + end + + private + + def configure_redis + @redis = ::Redis.new(redis_options) + end + + def default_options + { key: 'logstash', data_type: 'list' } + end + + def validate_options + unless ['list', 'channel'].include?(options[:data_type]) + fail 'Expected :data_type to be either "list" or "channel"' + end + end + end + end +end diff --git a/ruby-logstasher/lib/logstasher/log_subscriber.rb b/ruby-logstasher/lib/logstasher/log_subscriber.rb new file mode 100644 index 0000000..3227e26 --- /dev/null +++ b/ruby-logstasher/lib/logstasher/log_subscriber.rb @@ -0,0 +1,96 @@ +require 'active_support/core_ext/class/attribute' +require 'active_support/log_subscriber' + +module LogStasher + class RequestLogSubscriber < ActiveSupport::LogSubscriber + def process_action(event) + payload = event.payload + + data = extract_request(payload) + data.merge! extract_status(payload) + data.merge! runtimes(event) + data.merge! location(event) + data.merge! extract_exception(payload) + data.merge! extract_custom_fields(payload) + + tags = ['request'] + tags.push('exception') if payload[:exception] + event = LogStash::Event.new('@source' => LogStasher.source, '@fields' => data, '@tags' => tags) + LogStasher.logger << event.to_json + "\n" + end + + def redirect_to(event) + Thread.current[:logstasher_location] = event.payload[:location] + end + + private + + def extract_request(payload) + { + :method => payload[:method], + :path => extract_path(payload), + :format => extract_format(payload), + :controller => payload[:params]['controller'], + :action => payload[:params]['action'] + } + end + + def extract_path(payload) + payload[:path].split("?").first + end + + def extract_format(payload) + if ::ActionPack::VERSION::MAJOR == 3 && ::ActionPack::VERSION::MINOR == 0 + payload[:formats].first + else + payload[:format] + end + end + + def extract_status(payload) + if payload[:status] + { :status => payload[:status].to_i } + else + { :status => 0 } + end + end + + def runtimes(event) + { + :duration => event.duration, + :view => event.payload[:view_runtime], + :db => event.payload[:db_runtime] + }.inject({}) do |runtimes, (name, runtime)| + runtimes[name] = runtime.to_f.round(2) if runtime + runtimes + end + end + + def location(event) + if location = Thread.current[:logstasher_location] + Thread.current[:logstasher_location] = nil + { :location => location } + else + {} + end + end + + # Monkey patching to enable exception logging + def extract_exception(payload) + if payload[:exception] + exception, message = payload[:exception] + status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception) + message = "#{exception}\n#{message}\n#{($!.backtrace.join("\n"))}" + { :status => status, :error => message } + else + {} + end + end + + def extract_custom_fields(payload) + custom_fields = (!LogStasher.custom_fields.empty? && payload.extract!(*LogStasher.custom_fields)) || {} + LogStasher.custom_fields.clear + custom_fields + end + end +end diff --git a/ruby-logstasher/lib/logstasher/rails_ext/action_controller/metal/instrumentation.rb b/ruby-logstasher/lib/logstasher/rails_ext/action_controller/metal/instrumentation.rb new file mode 100644 index 0000000..dbfd329 --- /dev/null +++ b/ruby-logstasher/lib/logstasher/rails_ext/action_controller/metal/instrumentation.rb @@ -0,0 +1,35 @@ +module ActionController + module Instrumentation + def process_action(*args) + raw_payload = { + :controller => self.class.name, + :action => self.action_name, + :params => request.filtered_parameters, + :format => request.format.try(:ref), + :method => request.method, + :path => (request.fullpath rescue "unknown") + } + + LogStasher.add_default_fields_to_payload(raw_payload, request) + + ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup) + + ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload| + result = super + + if self.respond_to?(:logtasher_add_custom_fields_to_payload) + before_keys = raw_payload.keys.clone + logtasher_add_custom_fields_to_payload(raw_payload) + after_keys = raw_payload.keys + # Store all extra keys added to payload hash in payload itself. This is a thread safe way + LogStasher.custom_fields += after_keys - before_keys + end + + payload[:status] = response.status + append_info_to_payload(payload) + result + end + end + + end +end diff --git a/ruby-logstasher/lib/logstasher/rails_ext/rack/logger.rb b/ruby-logstasher/lib/logstasher/rails_ext/rack/logger.rb new file mode 100644 index 0000000..846ea74 --- /dev/null +++ b/ruby-logstasher/lib/logstasher/rails_ext/rack/logger.rb @@ -0,0 +1,24 @@ +require 'rails/rack/logger' + +module Rails + module Rack + # Overwrites defaults of Rails::Rack::Logger that cause + # unnecessary logging. + # This effectively removes the log lines from the log + # that say: + # Started GET / for 192.168.2.1... + class Logger + # Overwrites Rails 3.2 code that logs new requests + def call_app(*args) + env = args.last + @app.call(env) + ensure + ActiveSupport::LogSubscriber.flush_all! + end + + # Overwrites Rails 3.0/3.1 code that logs new requests + def before_dispatch(env) + end + end + end +end diff --git a/ruby-logstasher/lib/logstasher/railtie.rb b/ruby-logstasher/lib/logstasher/railtie.rb new file mode 100644 index 0000000..911ab2f --- /dev/null +++ b/ruby-logstasher/lib/logstasher/railtie.rb @@ -0,0 +1,14 @@ +require 'rails/railtie' +require 'action_view/log_subscriber' +require 'action_controller/log_subscriber' + +module LogStasher + class Railtie < Rails::Railtie + config.logstasher = ActiveSupport::OrderedOptions.new + config.logstasher.enabled = false + + initializer :logstasher, :before => :load_config_initializers do |app| + LogStasher.setup(app) if app.config.logstasher.enabled + end + end +end diff --git a/ruby-logstasher/lib/logstasher/version.rb b/ruby-logstasher/lib/logstasher/version.rb new file mode 100644 index 0000000..92270ca --- /dev/null +++ b/ruby-logstasher/lib/logstasher/version.rb @@ -0,0 +1,3 @@ +module LogStasher + VERSION = "0.5.0" +end diff --git a/ruby-logstasher/logstasher.gemspec b/ruby-logstasher/logstasher.gemspec new file mode 100644 index 0000000..2f463f2 --- /dev/null +++ b/ruby-logstasher/logstasher.gemspec @@ -0,0 +1,26 @@ +# -*- encoding: utf-8 -*- +$:.push File.expand_path("../lib", __FILE__) +require "logstasher/version" + +Gem::Specification.new do |s| + s.name = "logstasher" + s.version = LogStasher::VERSION + s.authors = ["Shadab Ahmed"] + s.email = ["shadab.ansari@gmail.com"] + s.homepage = "https://github.com/shadabahmed/logstasher" + s.summary = %q{Awesome rails logs} + s.description = %q{Awesome rails logs} + + s.rubyforge_project = "logstasher" + + s.files = `git ls-files`.split("\n") + s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + s.require_paths = ["lib"] + s.add_runtime_dependency "logstash-event", ["~> 1.1.0"] + + # specify any dependencies here; for example: + s.add_development_dependency "rspec" + s.add_development_dependency("bundler", [">= 1.0.0"]) + s.add_development_dependency("rails", [">= 3.0"]) +end diff --git a/ruby-logstasher/metadata.yml b/ruby-logstasher/metadata.yml new file mode 100644 index 0000000..34e73b8 --- /dev/null +++ b/ruby-logstasher/metadata.yml @@ -0,0 +1,122 @@ +--- !ruby/object:Gem::Specification +name: logstasher +version: !ruby/object:Gem::Version + version: 0.5.0 +platform: ruby +authors: +- Shadab Ahmed +autorequire: +bindir: bin +cert_chain: [] +date: 2014-03-07 00:00:00.000000000 Z +dependencies: +- !ruby/object:Gem::Dependency + name: logstash-event + requirement: !ruby/object:Gem::Requirement + requirements: + - - ~> + - !ruby/object:Gem::Version + version: 1.1.0 + type: :runtime + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ~> + - !ruby/object:Gem::Version + version: 1.1.0 +- !ruby/object:Gem::Dependency + name: rspec + requirement: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: '0' + type: :development + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: '0' +- !ruby/object:Gem::Dependency + name: bundler + requirement: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: 1.0.0 + type: :development + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: 1.0.0 +- !ruby/object:Gem::Dependency + name: rails + requirement: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: '3.0' + type: :development + prerelease: false + version_requirements: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: '3.0' +description: Awesome rails logs +email: +- shadab.ansari@gmail.com +executables: [] +extensions: [] +extra_rdoc_files: [] +files: +- .gitignore +- .rspec +- .travis.yml +- Gemfile +- Guardfile +- README.md +- Rakefile +- lib/logstasher.rb +- lib/logstasher/device/redis.rb +- lib/logstasher/log_subscriber.rb +- lib/logstasher/rails_ext/action_controller/metal/instrumentation.rb +- lib/logstasher/rails_ext/rack/logger.rb +- lib/logstasher/railtie.rb +- lib/logstasher/version.rb +- logstasher.gemspec +- spec/lib/logstasher/device/redis_spec.rb +- spec/lib/logstasher/log_subscriber_spec.rb +- spec/lib/logstasher_spec.rb +- spec/spec_helper.rb +homepage: https://github.com/shadabahmed/logstasher +licenses: [] +metadata: {} +post_install_message: +rdoc_options: [] +require_paths: +- lib +required_ruby_version: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: '0' +required_rubygems_version: !ruby/object:Gem::Requirement + requirements: + - - ! '>=' + - !ruby/object:Gem::Version + version: '0' +requirements: [] +rubyforge_project: logstasher +rubygems_version: 2.1.4 +signing_key: +specification_version: 4 +summary: Awesome rails logs +test_files: +- spec/lib/logstasher/device/redis_spec.rb +- spec/lib/logstasher/log_subscriber_spec.rb +- spec/lib/logstasher_spec.rb +- spec/spec_helper.rb diff --git a/ruby-logstasher/spec/lib/logstasher/device/redis_spec.rb b/ruby-logstasher/spec/lib/logstasher/device/redis_spec.rb new file mode 100644 index 0000000..59ca325 --- /dev/null +++ b/ruby-logstasher/spec/lib/logstasher/device/redis_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +require 'logstasher/device/redis' + +describe LogStasher::Device::Redis do + + let(:redis_mock) { double('Redis') } + + let(:default_options) {{ + key: 'logstash', + data_type: 'list' + }} + + it 'has default options' do + device = LogStasher::Device::Redis.new + device.options.should eq(default_options) + end + + it 'creates a redis instance' do + ::Redis.should_receive(:new).with({}) + LogStasher::Device::Redis.new() + end + + it 'assumes unknown options are for redis' do + ::Redis.should_receive(:new).with(hash_including(db: '0')) + device = LogStasher::Device::Redis.new(db: '0') + device.redis_options.should eq(db: '0') + end + + it 'has a key' do + device = LogStasher::Device::Redis.new(key: 'the_key') + device.key.should eq 'the_key' + end + + it 'has a data_type' do + device = LogStasher::Device::Redis.new(data_type: 'channel') + device.data_type.should eq 'channel' + end + + it 'does not allow unsupported data types' do + expect { + device = LogStasher::Device::Redis.new(data_type: 'blargh') + }.to raise_error() + end + + it 'quits the redis connection on #close' do + device = LogStasher::Device::Redis.new + device.redis.should_receive(:quit) + device.close + end + + it 'works as a logger device' do + device = LogStasher::Device::Redis.new + device.should_receive(:write).with('blargh') + logger = Logger.new(device) + logger << 'blargh' + end + + describe '#write' do + it "rpushes logs onto a list" do + device = LogStasher::Device::Redis.new(data_type: 'list') + device.redis.should_receive(:rpush).with('logstash', 'the log') + device.write('the log') + end + + it "rpushes logs onto a custom key" do + device = LogStasher::Device::Redis.new(data_type: 'list', key: 'custom') + device.redis.should_receive(:rpush).with('custom', 'the log') + device.write('the log') + end + + it "publishes logs onto a channel" do + device = LogStasher::Device::Redis.new(data_type: 'channel', key: 'custom') + device.redis.should_receive(:publish).with('custom', 'the log') + device.write('the log') + end + end + +end diff --git a/ruby-logstasher/spec/lib/logstasher/log_subscriber_spec.rb b/ruby-logstasher/spec/lib/logstasher/log_subscriber_spec.rb new file mode 100644 index 0000000..f73fa4f --- /dev/null +++ b/ruby-logstasher/spec/lib/logstasher/log_subscriber_spec.rb @@ -0,0 +1,190 @@ +require 'spec_helper' + +describe LogStasher::RequestLogSubscriber do + let(:log_output) {StringIO.new} + let(:logger) { + logger = Logger.new(log_output) + logger.formatter = ->(_, _, _, msg) { + msg + } + def log_output.json + JSON.parse! self.string + end + logger + } + before do + LogStasher.logger = logger + LogStasher.log_controller_parameters = true + LogStasher.custom_fields = [] + end + after do + LogStasher.log_controller_parameters = false + end + + let(:subscriber) {LogStasher::RequestLogSubscriber.new} + let(:event) { + ActiveSupport::Notifications::Event.new( + 'process_action.action_controller', Time.now, Time.now, 2, { + status: 200, format: 'application/json', method: 'GET', path: '/home?foo=bar', params: { + :controller => 'home', :action => 'index', 'foo' => 'bar' + }.with_indifferent_access, db_runtime: 0.02, view_runtime: 0.01 + } + ) + } + + let(:redirect) { + ActiveSupport::Notifications::Event.new( + 'redirect_to.action_controller', Time.now, Time.now, 1, location: 'http://example.com', status: 302 + ) + } + + describe '.process_action' do + let!(:request_subscriber) { @request_subscriber ||= LogStasher::RequestLogSubscriber.new() } + let(:payload) { {} } + let(:event) { double(:payload => payload) } + let(:logger) { double } + let(:json) { "{\"@source\":\"unknown\",\"@tags\":[\"request\"],\"@fields\":{\"request\":true,\"status\":true,\"runtimes\":true,\"location\":true,\"exception\":true,\"custom\":true},\"@timestamp\":\"timestamp\"}\n" } + before do + LogStasher.stub(:logger => logger) + LogStash::Time.stub(:now => 'timestamp') + end + it 'calls all extractors and outputs the json' do + request_subscriber.should_receive(:extract_request).with(payload).and_return({:request => true}) + request_subscriber.should_receive(:extract_status).with(payload).and_return({:status => true}) + request_subscriber.should_receive(:runtimes).with(event).and_return({:runtimes => true}) + request_subscriber.should_receive(:location).with(event).and_return({:location => true}) + request_subscriber.should_receive(:extract_exception).with(payload).and_return({:exception => true}) + request_subscriber.should_receive(:extract_custom_fields).with(payload).and_return({:custom => true}) + LogStasher.logger.should_receive(:<<).with(json) + request_subscriber.process_action(event) + end + end + + describe 'logstasher output' do + + it "should contain request tag" do + subscriber.process_action(event) + log_output.json['@tags'].should include 'request' + end + + it "should contain HTTP method" do + subscriber.process_action(event) + log_output.json['@fields']['method'].should == 'GET' + end + + it "should include the path in the log output" do + subscriber.process_action(event) + log_output.json['@fields']['path'].should == '/home' + end + + it "should include the format in the log output" do + subscriber.process_action(event) + log_output.json['@fields']['format'].should == 'application/json' + end + + it "should include the status code" do + subscriber.process_action(event) + log_output.json['@fields']['status'].should == 200 + end + + it "should include the controller" do + subscriber.process_action(event) + log_output.json['@fields']['controller'].should == 'home' + end + + it "should include the action" do + subscriber.process_action(event) + log_output.json['@fields']['action'].should == 'index' + end + + it "should include the view rendering time" do + subscriber.process_action(event) + log_output.json['@fields']['view'].should == 0.01 + end + + it "should include the database rendering time" do + subscriber.process_action(event) + log_output.json['@fields']['db'].should == 0.02 + end + + it "should add a valid status when an exception occurred" do + begin + raise AbstractController::ActionNotFound.new('Could not find an action') + # working this in rescue to get access to $! variable + rescue + event.payload[:status] = nil + event.payload[:exception] = ['AbstractController::ActionNotFound', 'Route not found'] + subscriber.process_action(event) + log_output.json['@fields']['status'].should >= 400 + log_output.json['@fields']['error'].should =~ /AbstractController::ActionNotFound.*Route not found.*logstasher\/spec\/lib\/logstasher\/log_subscriber_spec\.rb/m + log_output.json['@tags'].should include 'request' + log_output.json['@tags'].should include 'exception' + end + end + + it "should return an unknown status when no status or exception is found" do + event.payload[:status] = nil + event.payload[:exception] = nil + subscriber.process_action(event) + log_output.json['@fields']['status'].should == 0 + end + + describe "with a redirect" do + before do + Thread.current[:logstasher_location] = "http://www.example.com" + end + + it "should add the location to the log line" do + subscriber.process_action(event) + log_output.json['@fields']['location'].should == 'http://www.example.com' + end + + it "should remove the thread local variable" do + subscriber.process_action(event) + Thread.current[:logstasher_location].should == nil + end + end + + it "should not include a location by default" do + subscriber.process_action(event) + log_output.json['@fields']['location'].should be_nil + end + end + + describe "with append_custom_params block specified" do + let(:request) { double(:remote_ip => '10.0.0.1')} + it "should add default custom data to the output" do + request.stub(:params => event.payload[:params]) + LogStasher.add_default_fields_to_payload(event.payload, request) + subscriber.process_action(event) + log_output.json['@fields']['ip'].should == '10.0.0.1' + log_output.json['@fields']['route'].should == 'home#index' + log_output.json['@fields']['parameters'].should == {'foo' => 'bar'} + end + end + + describe "with append_custom_params block specified" do + before do + LogStasher.stub(:add_custom_fields) do |&block| + @block = block + end + LogStasher.add_custom_fields do |payload| + payload[:user] = 'user' + end + LogStasher.custom_fields += [:user] + end + + it "should add the custom data to the output" do + @block.call(event.payload) + subscriber.process_action(event) + log_output.json['@fields']['user'].should == 'user' + end + end + + describe "when processing a redirect" do + it "should store the location in a thread local variable" do + subscriber.redirect_to(redirect) + Thread.current[:logstasher_location].should == "http://example.com" + end + end +end diff --git a/ruby-logstasher/spec/lib/logstasher_spec.rb b/ruby-logstasher/spec/lib/logstasher_spec.rb new file mode 100644 index 0000000..aee18f2 --- /dev/null +++ b/ruby-logstasher/spec/lib/logstasher_spec.rb @@ -0,0 +1,179 @@ +require 'spec_helper' + +describe LogStasher do + describe "when removing Rails' log subscribers" do + after do + ActionController::LogSubscriber.attach_to :action_controller + ActionView::LogSubscriber.attach_to :action_view + end + + it "should remove subscribers for controller events" do + expect { + LogStasher.remove_existing_log_subscriptions + }.to change { + ActiveSupport::Notifications.notifier.listeners_for('process_action.action_controller') + } + end + + it "should remove subscribers for all events" do + expect { + LogStasher.remove_existing_log_subscriptions + }.to change { + ActiveSupport::Notifications.notifier.listeners_for('render_template.action_view') + } + end + + it "shouldn't remove subscribers that aren't from Rails" do + blk = -> {} + ActiveSupport::Notifications.subscribe("process_action.action_controller", &blk) + LogStasher.remove_existing_log_subscriptions + listeners = ActiveSupport::Notifications.notifier.listeners_for('process_action.action_controller') + listeners.size.should > 0 + end + end + + describe '.appened_default_info_to_payload' do + let(:params) { {'a' => '1', 'b' => 2, 'action' => 'action', 'controller' => 'test'}.with_indifferent_access } + let(:payload) { {:params => params} } + let(:request) { double(:params => params, :remote_ip => '10.0.0.1')} + after do + LogStasher.custom_fields = [] + LogStasher.log_controller_parameters = false + end + it 'appends default parameters to payload' do + LogStasher.log_controller_parameters = true + LogStasher.custom_fields = [] + LogStasher.add_default_fields_to_payload(payload, request) + payload[:ip].should == '10.0.0.1' + payload[:route].should == 'test#action' + payload[:parameters].should == {'a' => '1', 'b' => 2} + LogStasher.custom_fields.should == [:ip, :route, :parameters] + end + + it 'does not include parameters when not configured to' do + LogStasher.custom_fields = [] + LogStasher.add_default_fields_to_payload(payload, request) + payload.should_not have_key(:parameters) + LogStasher.custom_fields.should == [:ip, :route] + end + end + + describe '.append_custom_params' do + let(:block) { ->{} } + it 'defines a method in ActionController::Base' do + ActionController::Base.should_receive(:send).with(:define_method, :logtasher_add_custom_fields_to_payload, &block) + LogStasher.add_custom_fields(&block) + end + end + + shared_examples 'setup' do + let(:logger) { double } + let(:logstasher_config) { double(:logger => logger, :log_level => 'warn', :log_controller_parameters => nil, :source => logstasher_source) } + let(:config) { double(:logstasher => logstasher_config) } + let(:app) { double(:config => config) } + before do + @previous_source = LogStasher.source + config.stub(:action_dispatch => double(:rack_cache => false)) + end + after { LogStasher.source = @previous_source } # Need to restore old source for specs + it 'defines a method in ActionController::Base' do + LogStasher.should_receive(:require).with('logstasher/rails_ext/action_controller/metal/instrumentation') + LogStasher.should_receive(:require).with('logstash-event') + LogStasher.should_receive(:suppress_app_logs).with(app) + LogStasher::RequestLogSubscriber.should_receive(:attach_to).with(:action_controller) + logger.should_receive(:level=).with('warn') + LogStasher.setup(app) + LogStasher.source.should == (logstasher_source || 'unknown') + LogStasher.enabled.should be_true + LogStasher.custom_fields.should == [] + LogStasher.log_controller_parameters.should == false + end + end + + describe '.setup' do + describe 'with source set' do + let(:logstasher_source) { 'foo' } + it_behaves_like 'setup' + end + + describe 'without source set (default behaviour)' do + let(:logstasher_source) { nil } + it_behaves_like 'setup' + end + end + + describe '.suppress_app_logs' do + let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => true))} + let(:app){ double(:config => logstasher_config)} + it 'removes existing subscription if enabled' do + LogStasher.should_receive(:require).with('logstasher/rails_ext/rack/logger') + LogStasher.should_receive(:remove_existing_log_subscriptions) + LogStasher.suppress_app_logs(app) + end + + context 'when disabled' do + let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => false)) } + it 'does not remove existing subscription' do + LogStasher.should_not_receive(:remove_existing_log_subscriptions) + LogStasher.suppress_app_logs(app) + end + + describe "backward compatibility" do + context 'with spelling "supress_app_log"' do + let(:logstasher_config){ double(:logstasher => double(:suppress_app_log => nil, :supress_app_log => false)) } + it 'does not remove existing subscription' do + LogStasher.should_not_receive(:remove_existing_log_subscriptions) + LogStasher.suppress_app_logs(app) + end + end + end + end + end + + describe '.appended_params' do + it 'returns the stored var in current thread' do + Thread.current[:logstasher_custom_fields] = :test + LogStasher.custom_fields.should == :test + end + end + + describe '.appended_params=' do + it 'returns the stored var in current thread' do + LogStasher.custom_fields = :test + Thread.current[:logstasher_custom_fields].should == :test + end + end + + describe '.log' do + let(:logger) { double() } + before do + LogStasher.logger = logger + LogStash::Time.stub(:now => 'timestamp') + end + it 'adds to log with specified level' do + logger.should_receive(:send).with('warn?').and_return(true) + logger.should_receive(:send).with('warn',"{\"@source\":\"unknown\",\"@tags\":[\"log\"],\"@fields\":{\"message\":\"WARNING\",\"level\":\"warn\"},\"@timestamp\":\"timestamp\"}") + LogStasher.log('warn', 'WARNING') + end + context 'with a source specified' do + before :each do + LogStasher.source = 'foo' + end + it 'sets the correct source' do + logger.should_receive(:send).with('warn?').and_return(true) + logger.should_receive(:send).with('warn',"{\"@source\":\"foo\",\"@tags\":[\"log\"],\"@fields\":{\"message\":\"WARNING\",\"level\":\"warn\"},\"@timestamp\":\"timestamp\"}") + LogStasher.log('warn', 'WARNING') + end + end + end + + %w( fatal error warn info debug unknown ).each do |severity| + describe ".#{severity}" do + let(:message) { "This is a #{severity} message" } + it 'should log with specified level' do + LogStasher.should_receive(:log).with(severity.to_sym, message) + LogStasher.send(severity, message ) + end + end + end +end diff --git a/ruby-logstasher/spec/spec_helper.rb b/ruby-logstasher/spec/spec_helper.rb new file mode 100644 index 0000000..1556eb6 --- /dev/null +++ b/ruby-logstasher/spec/spec_helper.rb @@ -0,0 +1,50 @@ +# Notice there is a .rspec file in the root folder. It defines rspec arguments + +# Ruby 1.9 uses simplecov. The ENV['COVERAGE'] is set when rake coverage is run in ruby 1.9 +if ENV['COVERAGE'] + require 'simplecov' + SimpleCov.start do + # Remove the spec folder from coverage. By default all code files are included. For more config options see + # https://github.com/colszowka/simplecov + add_filter File.expand_path('../../spec', __FILE__) + end +end + +# Modify load path so you can require 'ogstasher directly. +$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) + +require 'rubygems' +# Loads bundler setup tasks. Now if I run spec without installing gems then it would say gem not installed and +# do bundle install instead of ugly load error on require. +require 'bundler/setup' + +# This will require me all the gems automatically for the groups. If I do only .setup then I will have to require gems +# manually. Note that you have still have to require some gems if they are part of bigger gem like ActiveRecord which is +# part of Rails. You can say :require => false in gemfile to always use explicit requiring +Bundler.require(:default, :test) + +# Set Rails environment as test +ENV['RAILS_ENV'] = 'test' + +require 'action_pack' +require 'action_controller' +require 'logstasher' +require 'active_support/notifications' +require 'active_support/core_ext/string' +require 'active_support/log_subscriber' +require 'action_controller/log_subscriber' +require 'action_view/log_subscriber' +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/string' +require 'active_support/core_ext/time/zones' +require 'abstract_controller/base' +require 'logger' +require 'logstash-event' + +RSpec.configure do |config| + config.treat_symbols_as_metadata_keys_with_true_values = true + config.run_all_when_everything_filtered = true + config.filter_run :focus +end -- 1.8.2.3