rackstash 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8cd189bc3e583fbbf1beaff0412eb8e0883c4864
4
- data.tar.gz: 35589d8c2e109887b730cc57168a6f7bc55a16d7
3
+ metadata.gz: 350b0ac721bfde86c8259470b3f7e6c3911d9f5c
4
+ data.tar.gz: ab47f38168d36ca48de001035affae5f9664293d
5
5
  SHA512:
6
- metadata.gz: 8e88aff7362f7aef71ee0b4147d9272e7870982b82c480ed8e3b961dcfa98706bdedd10d8b2994150b6fba340fd69ebb8b365a18e27810120b385d615524a703
7
- data.tar.gz: 025b237ee9c691ccfd743574fb7ac2db181e7e6511a2957e103aa7989048fb53dfa56b740b4c635c4f75c3364d6ff34a0d9fd2d547629b237b3a6a3f14958bd1
6
+ metadata.gz: 895bbb5b842bdac28f4198d6f516c413458fd7da29f5f6c05f215356723431fc6143a5dca9648c04885cd678aa90aae4489017dd1e05581e40692de96cae0510
7
+ data.tar.gz: 5ec42ec1c69e9f4792c61a3a4cd0eeb329941b580a4834ea9a5ff182ac224c030106d515d4e3443a4f97f988c9dc240f55879a568a0321ac5f273850695ab6d7
data/.gitignore CHANGED
@@ -1,9 +1,17 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+ cache: bundler
3
+ sudo: false
4
+ rvm:
5
+ - 1.8.7
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - jruby-18mode
9
+ - jruby-19mode
10
+ env:
11
+ - RACK_VERSION=1.4.1
12
+ - RAILS_VERSION=2.3.15
13
+ - RAILS_VERSION=3.2.0
14
+ - RAILS_VERSION=4.2.0
15
+ before_install:
16
+ - '[ "$TRAVIS_RUBY_VERSION" = "2.1.0" ] && gem install bundler -v ">= 1.5.1" --conservative || true'
17
+ matrix:
18
+ exclude:
19
+ - rvm: 1.8.7
20
+ env: RAILS_VERSION=4.2.0
21
+ - rvm: jruby-18mode
22
+ env: RAILS_VERSION=4.2.0
23
+ - rvm: 2.0.0
24
+ env: RAILS_VERSION=2.3.15
25
+ - rvm: 2.1.0
26
+ env: RAILS_VERSION=2.3.15
data/Gemfile CHANGED
@@ -1,4 +1,35 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ if ENV['RAILS_VERSION']
4
+ gem "rails", "~> #{ENV['RAILS_VERSION']}"
5
+
6
+ if RUBY_VERSION < '2'
7
+ gem "rack-cache", '< 1.3'
8
+ # gem "json", "< 2"
9
+ end
10
+ elsif ENV['RACK_VERSION']
11
+ gem "rack", "~> #{ENV['RACK_VERSION']}"
12
+ end
13
+
14
+ if RUBY_VERSION < "2"
15
+ gem "mime-types", "< 2.0.0"
16
+ gem "json", "< 2"
17
+
18
+ gem "rake", "~> 10.5.0"
19
+
20
+ if RUBY_VERSION < '1.9.3'
21
+ gem "i18n", "~> 0.6.11"
22
+ gem "activesupport", "< 4"
23
+ else
24
+ gem "i18n", "~> 0.7"
25
+ gem "activesupport", "< 5"
26
+ end
27
+ elsif RUBY_VERSION < "2.2.2"
28
+ gem "activesupport", "< 5"
29
+ gem "coveralls"
30
+ else
31
+ gem "coveralls"
32
+ end
33
+
3
34
  # Specify your gem's dependencies in rackstash.gemspec
4
35
  gemspec
@@ -1,21 +1,22 @@
1
- The MIT License (MIT)
1
+ Copyright (c) 2012-2014 Holger Just, Planio GmbH <holger@plan.io>
2
2
 
3
- Copyright (c) 2016 Holger Just
3
+ MIT License
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
11
12
 
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
14
15
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,12 +1,161 @@
1
- # Rackstash
1
+ # Rackstash - Sane Logs for Rack and Rails
2
2
 
3
- This is just a placeholder for now. Please have a look at https://github.com/meineerde/rackstash for any progress.
3
+ A gem which tames the Rack and Rails (2.3.x and 3.x) logs and generates JSON
4
+ log lines in the native [Logstash JSON Event format](http://logstash.net).
4
5
 
5
- ## Contributing
6
+ It is thus similar to the excellent
7
+ [Lograge](https://github.com/roidrage/lograge) by Mathias Meyer. The main
8
+ difference between Rackstash and Lograge is that Lograge attempts to
9
+ completely remove the existing logging and to replaces it with its own log
10
+ line. Rackstash instead retains the existing logs and just enhances them with
11
+ structured fields which can then be used in a Logstash environment. By
12
+ default, Rackstash collects the very same data that Lograge collects plus the
13
+ original full request log.
6
14
 
7
- Bug reports and pull requests are welcome on GitHub at https://github.com/meineerde/rackstash. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
15
+ Given that Rackstash deals with potentially large amounts of log data per
16
+ request, it might be difficult to use with syslog. You would have to set the
17
+ supported message size rather high and have to make sure that all syslog
18
+ servers can handle the large messages. Rackstash is known to work with a
19
+ [syslog_logger](https://rubygems.org/gems/SyslogLogger) as the underlying
20
+ logger when its shipping to a sufficiently configured rsyslog.
8
21
 
9
- ## License
22
+ In any case is probably much easier to setup Logstash directly on the
23
+ application server to read the logs from the default log file location and
24
+ to eventually forward them to their final destination.
10
25
 
11
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
26
+ # Installation
12
27
 
28
+ Add this line to your application's Gemfile:
29
+
30
+ gem 'rackstash'
31
+
32
+ And then execute:
33
+
34
+ $ bundle
35
+
36
+ Or install it yourself as:
37
+
38
+ $ gem install rackstash
39
+
40
+ # Usage
41
+
42
+ ## Rails 3, Rails 4
43
+
44
+ Just add Rackstash to your Gemfile as described above. Then, in the
45
+ environment you want to enable Rackstash output, add a simple
46
+
47
+ ```ruby
48
+ # config/environments/production.rb
49
+ MyApp::Application.configure do
50
+ config.rackstash.enabled = true
51
+ end
52
+ ```
53
+
54
+ Additionally, you can configure Rackstash by setting one or more of the
55
+ settings described in the configuration section below in the respective
56
+ environment file.
57
+
58
+ ## Rails 2
59
+
60
+ When using bundler (if not, you *really* should start using it), you can just
61
+ add Rackstash to your Gemfile as described above. Then, in the environment
62
+ you want to enable Rackstash output, add a simple
63
+
64
+ ```ruby
65
+ require 'rackstash'
66
+ config.rackstash.enabled = true
67
+ ```
68
+
69
+ If you use `Bundler.require` during your Rails initialization, you can skip
70
+ the first line of the above step.
71
+
72
+ Note though that is is **not sufficient** to require Rackstash
73
+ in an initializer (i.e. one of the files in `config/initializers`) as these
74
+ files are evaluated too late during Rails initialization for Rackstash to
75
+ take over all of the Rails logging. You have to require it in either
76
+ `config/environment.rb` or one or more of
77
+ `config/environments/<environment name>.rb`.
78
+
79
+ Additionally, you can configure Rackstash by setting one or more of the
80
+ settings described in the configuration section in the respective environment
81
+ file.
82
+
83
+ ## Configuration
84
+
85
+ You have to set the `enabled` attribute to `true` to convert the logs to JSON
86
+ using Rackstash:
87
+
88
+ ```ruby
89
+ config.rackstash.enabled = true
90
+ ```
91
+
92
+ Then you can configure a multitude of options and additional fields
93
+
94
+ ```ruby
95
+ # The source attribute of all Logstash events
96
+ # By default: "unknown"
97
+ config.rackstash.source = "http://rails.example.com"
98
+
99
+ # An array of strings with which all emited log events are tagged.
100
+ # By default empty.
101
+ config.rackstash.tags = ['ruby', 'rails2']
102
+
103
+ # Additional fields which are included into each log event that
104
+ # originates from a captured request.
105
+ # Can either be a Hash or an object which responds to to_proc which
106
+ # subsequently returns a Hash. If it is the latter, the proc will be exceuted
107
+ # similar to an after filter in every request of the controller and thus has
108
+ # access to the controller state after the request was handled.
109
+ config.rackstash.request_fields = lambda do |controller|
110
+ {
111
+ :host => request.host,
112
+ :source_ip => request.remote_ip,
113
+ :user_agent => request.user_agent
114
+ }
115
+ end
116
+
117
+ # Additional fields that are to be included into every emitted log, both
118
+ # buffered and not. You can use this to add global state information to the
119
+ # log, e.g. from the current thread or from the current environment.
120
+ # Similar to the request_fields, this can be either a static Hash or an
121
+ # object which responds to to_proc and returns a Hash there.
122
+ #
123
+ # Note that the proc is not executed in a controller instance and thus doesn't
124
+ # directly have access to the controller state.
125
+ config.rackstash.fields = lambda do
126
+ {
127
+ :thread_id => Thread.current.object_id,
128
+ :app_server => Socket.gethostname
129
+ }
130
+ end
131
+
132
+ # Buffered logs events are emitted with this log level. If the logger is
133
+ # not buffering, it just passes the original log level through.
134
+ # Note that the underlying logger should log events with at least this log
135
+ # level
136
+ # By default: :info
137
+ config.rackstash.log_level = :info
138
+ ```
139
+
140
+ # Caveats
141
+
142
+ * Few tests
143
+ * No plain Rack support yet
144
+
145
+ # Contributing
146
+
147
+ [![Gem Version](https://badge.fury.io/rb/rackstash.png)](https://rubygems.org/gems/rackstash)
148
+ [![Build Status](https://secure.travis-ci.org/planio-gmbh/rackstash.png?branch=master)](https://travis-ci.org/planio-gmbh/rackstash)
149
+ [![Code Climate](https://codeclimate.com/github/planio-gmbh/rackstash.png)](https://codeclimate.com/github/planio-gmbh/rackstash)
150
+ [![Coverage Status](https://coveralls.io/repos/planio-gmbh/rackstash/badge.png?branch=master)](https://coveralls.io/r/planio-gmbh/rackstash?branch=master)
151
+
152
+ 1. Fork it
153
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
154
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
155
+ 4. Push to the branch (`git push origin my-new-feature`)
156
+ 5. Create new Pull Request
157
+
158
+ # License
159
+
160
+ MIT. Code extracted from [Planio](http://plan.io).
161
+ Copyright (c) 2012-2014 Holger Just, Planio GmbH
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
1
  require "bundler/gem_tasks"
2
- require "rake/testtask"
3
2
 
4
- Rake::TestTask.new(:test) do |t|
5
- t.libs << "test"
6
- t.libs << "lib"
7
- t.test_files = FileList['test/**/*_test.rb']
8
- end
3
+ require 'rake/testtask'
9
4
 
10
- task :default => :test
5
+ task :default => [:test]
6
+ Rake::TestTask.new(:test) do |test|
7
+ test.libs << 'test'
8
+ test.test_files = FileList['test/**/*_test.rb']
9
+ test.verbose = true
10
+ end
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # -*- mode: ruby -*-
3
+
4
+ require 'rackstash/runner'
5
+ Rackstash::Runner.start
@@ -1,5 +1,88 @@
1
- require "rackstash/version"
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'active_support/version'
4
+ if ActiveSupport::VERSION::MAJOR < 3
5
+ Hash.send(:include, ActiveSupport::CoreExtensions::Hash::IndifferentAccess) unless Hash.included_modules.include? ActiveSupport::CoreExtensions::Hash::IndifferentAccess
6
+ end
7
+
8
+ require 'rackstash/buffered_logger'
9
+ require 'rackstash/log_middleware'
10
+ require 'rackstash/version'
2
11
 
3
12
  module Rackstash
4
- # Your code goes here...
13
+ # The level with which the logs are emitted, by default info
14
+ mattr_accessor :log_level
15
+ self.log_level = :info
16
+
17
+ # Custom fields that will be merged with the log object when we
18
+ # capture a request.
19
+ #
20
+ # Currently supported formats are:
21
+ # - Hash
22
+ # - Any object that responds to to_proc and returns a hash
23
+ #
24
+ mattr_writer :request_fields
25
+ self.request_fields = HashWithIndifferentAccess.new
26
+ def self.request_fields(controller)
27
+ if @@request_fields.respond_to?(:to_proc)
28
+ ret = controller.instance_eval(&@@request_fields)
29
+ else
30
+ ret = @@request_fields
31
+ end
32
+ HashWithIndifferentAccess.new(ret)
33
+ end
34
+
35
+ # Custom fields that will be merged with every log object, be it a captured
36
+ # request or not.
37
+ #
38
+ # Currently supported formats are:
39
+ # - Hash
40
+ # - Any object that responds to to_proc and returns a hash
41
+ #
42
+ mattr_writer :fields
43
+ self.fields = HashWithIndifferentAccess.new
44
+ def self.fields
45
+ if @@fields.respond_to?(:to_proc)
46
+ ret = @@fields.to_proc.call
47
+ else
48
+ ret = @@fields
49
+ end
50
+ HashWithIndifferentAccess.new(ret)
51
+ end
52
+
53
+ # The source attribute in the generated Logstash output
54
+ mattr_accessor :source
55
+
56
+ # The logger object that is used by the actual application
57
+ mattr_accessor :logger
58
+
59
+ # Additonal tags which are attached to each buffered log event
60
+ mattr_reader :tags
61
+ def self.tags=(tags)
62
+ @@tags = tags.map(&:to_s)
63
+ end
64
+ self.tags = []
65
+
66
+ def self.with_log_buffer(&block)
67
+ if Rackstash.logger.respond_to?(:with_buffer)
68
+ Rackstash.logger.with_buffer(&block)
69
+ else
70
+ yield
71
+ end
72
+ end
73
+
74
+ def self.framework
75
+ @framework ||= begin
76
+ if Object.const_defined?(:Rails)
77
+ Rails::VERSION::MAJOR >= 3 ? "rails3" : "rails2"
78
+ else
79
+ "rack"
80
+ end
81
+ end
82
+ end
83
+
84
+ require "rackstash/framework/base"
85
+ require "rackstash/framework/#{framework}"
86
+ extend Rackstash::Framework::Base
87
+ extend Rackstash::Framework.const_get(framework.capitalize)
5
88
  end
@@ -0,0 +1,269 @@
1
+ require 'forwardable'
2
+ require 'logger'
3
+ require 'securerandom'
4
+ require 'active_support/core_ext/hash/reverse_merge'
5
+ require 'active_support/core_ext/hash/indifferent_access'
6
+ require 'active_support/version'
7
+ if ActiveSupport::VERSION::MAJOR < 3
8
+ Hash.send(:include, ActiveSupport::CoreExtensions::Hash::ReverseMerge) unless Hash.included_modules.include? ActiveSupport::CoreExtensions::Hash::ReverseMerge
9
+ Hash.send(:include, ActiveSupport::CoreExtensions::Hash::IndifferentAccess) unless Hash.included_modules.include? ActiveSupport::CoreExtensions::Hash::IndifferentAccess
10
+ end
11
+
12
+ require 'rackstash/log_severity'
13
+ # MRI 1.8 doesn't set the RUBY_ENGINE constant required by logstash-event
14
+ Object.const_set(:RUBY_ENGINE, "ruby") unless Object.const_defined?(:RUBY_ENGINE)
15
+ require "logstash-event"
16
+
17
+
18
+ module Rackstash
19
+ class BufferedLogger
20
+ extend Forwardable
21
+ include Rackstash::LogSeverity
22
+
23
+ class SimpleFormatter < ::Logger::Formatter
24
+ def call(severity, timestamp, progname, msg)
25
+ "#{String === msg ? msg : msg.inspect}\n"
26
+ end
27
+ end
28
+
29
+ def initialize(logger)
30
+ @logger = logger
31
+ @logger.formatter = SimpleFormatter.new if @logger.respond_to?(:formatter=)
32
+ @buffer = {}
33
+
34
+ @source_is_customized = false
35
+
36
+ # Note: Buffered logs need to be explicitly flushed to the underlying
37
+ # logger using +flush_and_pop_buffer+. This will not flush the underlying
38
+ # logger. If this is required, you still need to call
39
+ # +BufferedLogger#logger.flush+
40
+ delegate_if_required :@logger, :flush, :auto_flushing, :auto_flushing=
41
+ delegate_if_required :@logger, :progname, :progname=
42
+ delegate_if_required :@logger, :silencer, :silencer=, :silence
43
+ end
44
+
45
+ attr_accessor :formatter
46
+ attr_reader :logger
47
+ def_delegators :@logger, :level, :level=
48
+
49
+ def add(severity, message=nil, progname=nil)
50
+ severity ||= UNKNOWN
51
+ return true if level > severity
52
+
53
+ progname ||= logger.progname if logger.respond_to?(:progname)
54
+ if message.nil?
55
+ if block_given?
56
+ message = yield
57
+ else
58
+ message = progname
59
+ end
60
+ end
61
+
62
+ line = {:severity => severity, :message => message}
63
+ if buffering?
64
+ buffer[:messages] << line
65
+ message
66
+ else
67
+ json = logstash_event([line])
68
+ logger.add(severity, json)
69
+ end
70
+ end
71
+
72
+ def <<(message)
73
+ logger << message
74
+ end
75
+
76
+ Severities.each do |severity|
77
+ class_eval <<-EOT, __FILE__, __LINE__ + 1
78
+ def #{severity.to_s.downcase}(message = nil, progname = nil, &block) # def debug(message = nil, progname = nil, &block)
79
+ add(#{severity}, message, progname, &block) # add(DEBUG, message, progname, &block)
80
+ end # end
81
+ #
82
+ def #{severity.to_s.downcase}? # def debug?
83
+ #{severity} >= level # DEBUG >= level
84
+ end # end
85
+ EOT
86
+ end
87
+
88
+ def with_buffer
89
+ push_buffer
90
+ yield
91
+ rescue Exception => exception
92
+ # Add some details about an exception to the logs
93
+ # This won't catch errors in Rails requests as they are catched by
94
+ # the ActionController::Failsafe middleware before our middleware.
95
+ if self.fields
96
+ error_fields = {
97
+ :error => exception.class.name,
98
+ :error_message => exception.message,
99
+ :error_backtrace => exception.backtrace.join("\n")
100
+ }
101
+ self.fields.reverse_merge!(error_fields)
102
+ end
103
+ raise
104
+ ensure
105
+ flush_and_pop_buffer
106
+ end
107
+
108
+ def fields
109
+ buffer && buffer[:fields]
110
+ end
111
+
112
+ def tags
113
+ buffer && buffer[:tags]
114
+ end
115
+
116
+ def source=(value)
117
+ @source = value
118
+ @source_is_customized = true
119
+ end
120
+ def source
121
+ if @source_is_customized
122
+ @source
123
+ else
124
+ Rackstash.respond_to?(:source) && Rackstash.source
125
+ end
126
+ end
127
+
128
+ def close
129
+ flush_and_pop_buffer while buffering?
130
+ logger.flush if logger.respond_to?(:flush)
131
+ logger.close if logger.respond_to?(:close)
132
+ end
133
+
134
+ def push_buffer
135
+ child_buffer = {
136
+ :messages => [],
137
+ :fields => default_fields,
138
+ :tags => [],
139
+ :do_not_log => false
140
+ }
141
+
142
+ self.buffer_stack ||= []
143
+ if parent_buffer = buffer
144
+ parent_buffer[:fields][:child_log_ids] ||= []
145
+ parent_buffer[:fields][:child_log_ids] << child_buffer[:fields][:log_id]
146
+ child_buffer[:fields][:parent_log_id] = parent_buffer[:fields][:log_id]
147
+ end
148
+
149
+ self.buffer_stack << child_buffer
150
+ nil
151
+ end
152
+
153
+ def flush_and_pop_buffer
154
+ if buffer = self.buffer
155
+ unless buffer[:do_not_log]
156
+ json = logstash_event(buffer[:messages], buffer[:fields], buffer[:tags])
157
+ log_level = Rackstash.respond_to?(:log_level) ? Rackstash.log_level : :info
158
+ logger.send(log_level, json)
159
+ end
160
+ logger.flush if logger.respond_to?(:flush)
161
+ end
162
+
163
+ pop_buffer
164
+ end
165
+
166
+ def buffering?
167
+ !!buffer
168
+ end
169
+
170
+ def do_not_log!(yes_or_no=true)
171
+ return false unless buffer
172
+ buffer[:do_not_log] = !!yes_or_no
173
+ end
174
+
175
+ protected
176
+ def delegate_if_required(object_name, *methods)
177
+ object = instance_variable_get(object_name)
178
+ methods = Array(methods).select{|method| object.respond_to? method}
179
+
180
+ metaclass = class << self; self; end
181
+ methods.each do |method|
182
+ metaclass.instance_eval{ def_delegator object, method }
183
+ end
184
+ end
185
+
186
+ def default_fields
187
+ HashWithIndifferentAccess.new({"log_id" => uuid, "pid" => Process.pid})
188
+ end
189
+
190
+ def buffer
191
+ buffer_stack && buffer_stack.last
192
+ end
193
+
194
+ def buffer_stack
195
+ @buffer[Thread.current.object_id]
196
+ end
197
+
198
+ def buffer_stack=(stack)
199
+ @buffer[Thread.current.object_id] = stack
200
+ end
201
+
202
+ # This method removes the top-most buffer.
203
+ # It does not flush the buffer in any way. Use +flush_and_pop_buffer+
204
+ # for that.
205
+ def pop_buffer
206
+ poped = nil
207
+
208
+ if buffer_stack
209
+ poped = buffer_stack.pop
210
+ # We need to delete the whole array to prevent a memory leak
211
+ # from piling threads
212
+ @buffer.delete(Thread.current.object_id) unless buffer_stack.any?
213
+ end
214
+ poped
215
+ end
216
+
217
+ # uuid generates a v4 random UUID (Universally Unique IDentifier).
218
+ #
219
+ # p SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
220
+ # p SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
221
+ # p SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
222
+ #
223
+ # The version 4 UUID is purely random (except the version). It doesn’t
224
+ # contain meaningful information such as MAC address, time, etc.
225
+ #
226
+ # See RFC 4122 for details of UUID.
227
+ def uuid
228
+ if SecureRandom.respond_to?(:uuid)
229
+ # Available since Ruby 1.9.2
230
+ SecureRandom.uuid
231
+ else
232
+ # Copied verbatim from SecureRandom.uuid of MRI 1.9.3
233
+ ary = SecureRandom.random_bytes(16).unpack("NnnnnN")
234
+ ary[2] = (ary[2] & 0x0fff) | 0x4000
235
+ ary[3] = (ary[3] & 0x3fff) | 0x8000
236
+ "%08x-%04x-%04x-%04x-%04x%08x" % ary
237
+ end
238
+ end
239
+
240
+ def normalized_message(logs=[])
241
+ logs.map do |line|
242
+ # normalize newlines
243
+ msg = line[:message].to_s.gsub(/[\n\r]/, "\n")
244
+ # remove any leading newlines and a single trailing newline
245
+ msg = msg.sub(/\A\n+/, '').sub(/\n\z/, '')
246
+ msg = "[#{Severities[line[:severity]]}] ".rjust(10) + msg
247
+ # Normalize the log line to UTF-8
248
+ msg.encode!(Encoding::UTF_8, :invalid => :replace, :undef => :replace) if msg.respond_to?(:encode!)
249
+ msg
250
+ end.join("\n")
251
+ end
252
+
253
+ def logstash_event(logs=[], fields=default_fields, tags=[])
254
+ rackstash_fields = Rackstash.respond_to?(:fields) ? Rackstash.fields : {}
255
+ fields = rackstash_fields.merge(fields)
256
+
257
+ rackstash_tags = Rackstash.respond_to?(:tags) ? Rackstash.tags : []
258
+ tags = rackstash_tags | tags.map(&:to_s)
259
+
260
+ event = LogStash::Event.new(
261
+ "@message" => normalized_message(logs),
262
+ "@fields" => fields,
263
+ "@tags" => tags,
264
+ "@source" => self.source
265
+ )
266
+ event.to_json
267
+ end
268
+ end
269
+ end