rails-graylogger 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 42cf591e1b56d39e146aa353a2ed0f3bbb0bd93e
4
+ data.tar.gz: 9a7779e5a53422ea3e6e6fc946b9c58c8df3490c
5
+ SHA512:
6
+ metadata.gz: 9fc47c8907de6a7d46b567d191936d627790d91125f408d84b8ccca9a64605c0f5cd5623fa86cee00fef17c5330aedd7740d5a92d9c241f7379f4f6d91b21977
7
+ data.tar.gz: 88b6c0dadbe9e8f81de79fbb6a170c063f9114dd063c9733aa8a4a1cda232655fffaea5ef1bef20e8b5de998d7f78c876402646f242599bc4e4e4ae0f4fe7f60
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ pkg
4
+ .yardoc
5
+ test.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rails-graylogger (0.1.0)
5
+ activesupport (~> 4)
6
+ gelf (~> 1.4.0)
7
+ request_store (~> 1.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (4.2.0)
13
+ i18n (~> 0.7)
14
+ json (~> 1.7, >= 1.7.7)
15
+ minitest (~> 5.1)
16
+ thread_safe (~> 0.3, >= 0.3.4)
17
+ tzinfo (~> 1.1)
18
+ diff-lcs (1.2.5)
19
+ gelf (1.4.0)
20
+ json
21
+ i18n (0.7.0)
22
+ json (1.8.2)
23
+ minitest (5.5.1)
24
+ rake (10.4.2)
25
+ request_store (1.1.0)
26
+ rspec (3.1.0)
27
+ rspec-core (~> 3.1.0)
28
+ rspec-expectations (~> 3.1.0)
29
+ rspec-mocks (~> 3.1.0)
30
+ rspec-core (3.1.7)
31
+ rspec-support (~> 3.1.0)
32
+ rspec-expectations (3.1.2)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.1.0)
35
+ rspec-mocks (3.1.3)
36
+ rspec-support (~> 3.1.0)
37
+ rspec-support (3.1.2)
38
+ thread_safe (0.3.4)
39
+ tzinfo (1.2.2)
40
+ thread_safe (~> 0.1)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ rails-graylogger!
47
+ rake
48
+ rspec (~> 3)
data/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # rails-graylogger
2
+ Rails logs to Graylog2
3
+
4
+ # Configuring Middleware
5
+ Add the following to config/application.rb:
6
+
7
+ ```
8
+ config.middleware.insert_before Rack::Runtime, RailsGraylogger::Middleware
9
+ config.after_initialize do
10
+ config.logger = Rails.logger = RailsGraylogger::Logger.new(Rails.logger)
11
+ end
12
+ ```
13
+
14
+ # Watching output without graylog2
15
+ One way is to use logstash to listen for gelf input and output it to a file
16
+
17
+ Config:
18
+
19
+ ```
20
+ input {
21
+ gelf {
22
+ }
23
+ }
24
+ output {
25
+ file {
26
+ path => "/tmp/test.log"
27
+ }
28
+ }
29
+ ```
30
+
31
+ ```
32
+ brew install logstash
33
+ logstash -f <path_to_config>
34
+ ```
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
@@ -0,0 +1,37 @@
1
+ module RailsGraylogger
2
+ class LogSubscriber < ::ActiveSupport::LogSubscriber
3
+
4
+ def process_action(event)
5
+ payload = event.payload
6
+ payload[:path] = URI(payload[:path]).path if payload[:path]
7
+ payload[:ipaddress] = RequestStore.store[:ipaddress]
8
+ payload[:duration] = event.duration.round
9
+ payload[:status] = status_from_exception(payload[:exception]) unless payload[:status].present?
10
+
11
+ message = RailsGraylogger::Message.from_event_payload(payload)
12
+ message.tags = RailsGraylogger::Logger.request_tags
13
+ message.full_message = buffered_messages
14
+
15
+ RailsGraylogger::Notifier.notify!(message.to_hash)
16
+ rescue => ex
17
+ Rails.logger.error "Exception in RailsGraylogger: #{ex.class}: #{ex.message}"
18
+ end
19
+
20
+ def buffered_messages
21
+ unless RailsGraylogger::Logger.request_buffer.blank?
22
+ RailsGraylogger::Logger.request_buffer.map{ |item| item[:short_message] unless item[:short_message].blank? }.compact.join("\n")
23
+ else
24
+ []
25
+ end
26
+ end
27
+
28
+ def status_from_exception(exception)
29
+ if exception.present?
30
+ exception_class_name = exception.first
31
+ ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ RailsGraylogger::LogSubscriber.attach_to :action_controller
@@ -0,0 +1,59 @@
1
+ module RailsGraylogger
2
+ class Logger
3
+ FIELD_KEY_REGEXP = /^[\w\.\-]*$/
4
+
5
+ def initialize(logger)
6
+ @logger = logger
7
+ end
8
+
9
+ class << self
10
+ def initialize_log_buffer
11
+ RequestStore.store[:graylog2_buffer] = []
12
+ end
13
+
14
+ def request_tags
15
+ RequestStore.store[:graylog2_tags] ||= []
16
+ end
17
+
18
+ def request_buffer
19
+ RequestStore.store[:graylog2_buffer]
20
+ end
21
+ end
22
+
23
+ def tagged(*new_tags, &block)
24
+ new_tags = Array.wrap(new_tags).flatten.reject(&:blank?)
25
+ self.class.request_tags << new_tags - ( self.class.request_tags & new_tags )
26
+ @logger.send("tagged", *new_tags, &block)
27
+ end
28
+
29
+ private
30
+
31
+ def method_missing(method, *args, &block)
32
+ if [:add, :info, :debug, :warn, :error, :fatal].include?(method) && !args[0].nil?
33
+ if args.size == 1 && args[0].is_a?(String)
34
+ hash = { short_message: args[0] }
35
+ else
36
+ return if args.blank?
37
+ hash = { short_message: args.compact.map(&:to_s).join("\n") }
38
+ end
39
+
40
+ unless self.class.request_buffer.nil?
41
+ self.class.request_buffer << hash
42
+ else
43
+ notify!(method, hash)
44
+ end
45
+ end
46
+ @logger.send(method, *args, &block)
47
+ end
48
+
49
+ def notify!(method, payload)
50
+ message = RailsGraylogger::Message.new({
51
+ level: "GELF::Levels::#{method.upcase}".constantize,
52
+ short_message: payload.delete(:short_message)
53
+ })
54
+ message.process_extra_fields(payload)
55
+ message.tags = self.class.request_tags unless self.class.request_tags.blank?
56
+ RailsGraylogger::Notifier.notify!(message.to_hash)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,74 @@
1
+ module RailsGraylogger
2
+ class Message
3
+ FIELD_KEY_REGEXP = /^[\w\.\-]*$/
4
+
5
+ attr_accessor :level, :full_message, :short_message, :extra_fields
6
+
7
+ def initialize(opts = {})
8
+ @level = opts[:level] || GELF::Levels::INFO
9
+ @short_message = opts[:short_message] || ""
10
+ @full_message = opts[:full_message] || ""
11
+ @extra_fields = {}
12
+ end
13
+
14
+ class << self
15
+ def from_event_payload(payload)
16
+ message = self.new
17
+ message.process_event_payload(payload)
18
+ message
19
+ end
20
+ end
21
+
22
+ def process_event_payload(payload)
23
+ self.level = level_from_status(payload[:status])
24
+ self.short_message = "#{payload[:controller]}##{payload[:action]} #{payload[:method]} \"#{payload[:path]}\" from #{payload[:ipaddress]}"
25
+ process_extra_fields(payload)
26
+ end
27
+
28
+ def tags=(tags)
29
+ raise ArgumentError.new("Not an array: #{tags.inspect}") unless tags.is_a?(Array)
30
+ self.extra_fields[:_tags] = tags.join(" ")
31
+ end
32
+
33
+ def to_hash
34
+ {
35
+ level: level,
36
+ short_message: short_message
37
+ }.tap do |hash|
38
+ hash[:full_message] = short_message + "\n" + full_message unless full_message.blank?
39
+ end.merge(extra_fields)
40
+ end
41
+
42
+ def process_extra_fields(payload)
43
+ process_exception_data(payload.delete(:exception)) if payload[:exception]
44
+
45
+ payload.each do |key, value|
46
+ @extra_fields["_#{key}"] = formatted_value(value) if valid_key?(key)
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def formatted_value(value)
53
+ if value.is_a?(Numeric)
54
+ value
55
+ else
56
+ value.to_s
57
+ end
58
+ end
59
+
60
+ def valid_key?(key)
61
+ key.match(FIELD_KEY_REGEXP)
62
+ end
63
+
64
+ def level_from_status(status)
65
+ status < 400 ? GELF::Levels::INFO : GELF::Levels::ERROR
66
+ end
67
+
68
+ def process_exception_data(exception)
69
+ exception.each do |key, value|
70
+ @extra_fields["_exception_#{key}"] = value.to_s if valid_key?(key)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,18 @@
1
+ module RailsGraylogger
2
+ class Middleware
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ RailsGraylogger::Logger.initialize_log_buffer
9
+ logger = Rails.logger
10
+ request = Rack::Request.new(env)
11
+ RequestStore.store[:ipaddress] = request.ip
12
+
13
+ status, headers, body = @app.call(env)
14
+
15
+ [status, headers, body]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ module RailsGraylogger
2
+ class Notifier
3
+ class << self
4
+ def notify!(gelf_message)
5
+ @@notifier ||= self.new
6
+ @@notifier.notify!(gelf_message)
7
+ end
8
+ end
9
+
10
+ def initialize
11
+ @gelf_notifier = GELF::Notifier.new(remote_host, remote_port, 1420, facility: facility, host: sender_host)
12
+ end
13
+
14
+ def remote_host
15
+ ENV["GRAYLOG_HOST"] || "127.0.0.1"
16
+ end
17
+
18
+ def remote_port
19
+ ENV["GRAYLOG_PORT"] || 12201
20
+ end
21
+
22
+ def facility
23
+ ENV["GRAYLOG_FACILITY"] || "Rails"
24
+ end
25
+
26
+ def sender_host
27
+ Socket.gethostname.split(".").first
28
+ end
29
+
30
+ def notify!(gelf_message)
31
+ return if Rails.env == 'test'
32
+ @gelf_notifier.notify!(gelf_message.merge(timestamp: Time.now.utc.to_f))
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,3 @@
1
+ module RailsGraylogger
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'gelf'
2
+ require 'request_store'
3
+ require 'rails-graylogger/middleware.rb'
4
+ require 'rails-graylogger/notifier.rb'
5
+ require 'rails-graylogger/logger.rb'
6
+ require 'rails-graylogger/message.rb'
7
+ require 'rails-graylogger/log_subscriber.rb'
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/rails-graylogger/version', __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "rails-graylogger"
6
+ s.version = RailsGraylogger::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Tuomas Silen"]
9
+ s.email = ["tuomas@flowdock.com"]
10
+ s.homepage = "https://github.com/flowdock/rails-graylogger"
11
+ s.summary = "Push Rails logs also to Graylog2"
12
+ s.description = s.summary
13
+
14
+ s.add_dependency 'gelf', '~> 1.4.0'
15
+ s.add_dependency 'activesupport', '~> 4'
16
+ s.add_dependency 'request_store', '~> 1.0'
17
+
18
+ s.add_development_dependency 'rspec', '~> 3'
19
+ s.add_development_dependency 'rake'
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end
@@ -0,0 +1,49 @@
1
+ require "spec_helper"
2
+
3
+ describe RailsGraylogger::Logger do
4
+ before :each do
5
+ RequestStore.clear!
6
+ @logger = RailsGraylogger::Logger.new(ActiveSupport::TaggedLogging.new(Logger.new("test.log")))
7
+ end
8
+
9
+ it "sends log lines to Graylog server" do
10
+ expect(RailsGraylogger::Notifier).to receive(:notify!).with(level: GELF::Levels::INFO, short_message: "Foobar").once
11
+ @logger.info("Foobar")
12
+ end
13
+
14
+ it "handles tagged logging with nested tags" do
15
+ expect(RailsGraylogger::Notifier).to receive(:notify!).with(level: GELF::Levels::INFO, short_message: "Test", _tags: "Foo Bar").once
16
+ @logger.tagged("Foo") do
17
+ @logger.tagged("Bar") do
18
+ @logger.info("Test")
19
+ end
20
+ end
21
+ end
22
+
23
+ it "handles tagged logging with multiple log lines" do
24
+ expect(RailsGraylogger::Notifier).to receive(:notify!).with(_tags: "Foobar", short_message: "Line1", level: GELF::Levels::INFO).once
25
+ expect(RailsGraylogger::Notifier).to receive(:notify!).with(_tags: "Foobar", short_message: "Line2", level: GELF::Levels::INFO).once
26
+ @logger.tagged("Foobar") do
27
+ @logger.info("Line1")
28
+ @logger.info("Line2")
29
+ end
30
+ end
31
+
32
+ it "handles passing a hash as argument" do
33
+ hash = { foo: "test", bar: "test" }
34
+ expect(RailsGraylogger::Notifier).to receive(:notify!).with(level: GELF::Levels::INFO, short_message: "{:foo=>\"test\", :bar=>\"test\"}", _tags: "Foobar").once
35
+ @logger.tagged("Foobar") do
36
+ @logger.info(hash)
37
+ end
38
+ end
39
+
40
+ it "should buffer logging output when request store is initialized" do
41
+ allow(RailsGraylogger::Notifier).to receive(:notify!)
42
+ RailsGraylogger::Logger.initialize_log_buffer
43
+ expect(RailsGraylogger::Logger.request_buffer.size).to eq(0)
44
+ @logger.info("Foobar")
45
+ expect(RailsGraylogger::Notifier).not_to have_received(:notify!)
46
+ expect(RailsGraylogger::Logger.request_buffer.size).to eq(1)
47
+ expect(RailsGraylogger::Logger.request_buffer[0]).to eq({short_message: "Foobar"})
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper"
2
+
3
+ describe RailsGraylogger::Message do
4
+ describe "#process_extra_fields" do
5
+ before(:each) do
6
+ @msg = RailsGraylogger::Message.new
7
+ end
8
+
9
+ it "should add undescrore before field names" do
10
+ @msg.process_extra_fields({ test: "abc" })
11
+ expect(@msg.extra_fields).to eq({ "_test" => "abc" })
12
+ end
13
+
14
+ it "should keep numeric values" do
15
+ @msg.process_extra_fields({ test: 123 })
16
+ expect(@msg.extra_fields).to eq({ "_test" => 123 })
17
+ end
18
+
19
+ it "should convert hash to string" do
20
+ @msg.process_extra_fields({ test: { foo: "bar" } })
21
+ expect(@msg.extra_fields).to eq({ "_test" => "{:foo=>\"bar\"}" })
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require "active_support"
3
+ require "active_support/inflector"
4
+ require "active_support/core_ext"
5
+ require File.expand_path("../../lib/rails-graylogger", __FILE__)
6
+
7
+ #require "rspec/rails"
8
+
9
+ RSpec.configure do |config|
10
+ config.filter_run_excluding slow: true unless ENV["CI"] == "true"
11
+
12
+ # Print the 10 slowest examples and example groups at the
13
+ # end of the spec run, to help surface which specs are running
14
+ # particularly slow.
15
+ config.profile_examples = 10
16
+
17
+ # Randomise spec order
18
+ # config.order = :random
19
+ # Kernel.srand config.seed
20
+
21
+ config.expect_with :rspec do |expectations|
22
+ expectations.syntax = :expect
23
+ end
24
+
25
+ config.mock_with :rspec do |mocks|
26
+ mocks.syntax = :expect
27
+ mocks.verify_partial_doubles = false
28
+ end
29
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails-graylogger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tuomas Silen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gelf
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: request_store
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Push Rails logs also to Graylog2
84
+ email:
85
+ - tuomas@flowdock.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - Gemfile.lock
93
+ - README.md
94
+ - Rakefile
95
+ - lib/rails-graylogger.rb
96
+ - lib/rails-graylogger/log_subscriber.rb
97
+ - lib/rails-graylogger/logger.rb
98
+ - lib/rails-graylogger/message.rb
99
+ - lib/rails-graylogger/middleware.rb
100
+ - lib/rails-graylogger/notifier.rb
101
+ - lib/rails-graylogger/version.rb
102
+ - rails-graylogger.gemspec
103
+ - spec/rails-graylogger/logger_spec.rb
104
+ - spec/rails-graylogger/message_spec.rb
105
+ - spec/spec_helper.rb
106
+ homepage: https://github.com/flowdock/rails-graylogger
107
+ licenses: []
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.4.5
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: Push Rails logs also to Graylog2
129
+ test_files:
130
+ - spec/rails-graylogger/logger_spec.rb
131
+ - spec/rails-graylogger/message_spec.rb
132
+ - spec/spec_helper.rb