rackstash 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +17 -9
- data/.travis.yml +26 -0
- data/Gemfile +31 -0
- data/LICENSE.txt +18 -17
- data/README.md +155 -6
- data/Rakefile +7 -7
- data/bin/rackstash +5 -0
- data/lib/rackstash.rb +85 -2
- data/lib/rackstash/buffered_logger.rb +269 -0
- data/lib/rackstash/framework/base.rb +13 -0
- data/lib/rackstash/framework/rack.rb +9 -0
- data/lib/rackstash/framework/rails2.rb +43 -0
- data/lib/rackstash/framework/rails3.rb +46 -0
- data/lib/rackstash/log_middleware.rb +27 -0
- data/lib/rackstash/log_severity.rb +9 -0
- data/lib/rackstash/log_subscriber.rb +108 -0
- data/lib/rackstash/rails_ext/action_controller.rb +101 -0
- data/lib/rackstash/rails_ext/initializer.rb +21 -0
- data/lib/rackstash/railtie.rb +19 -0
- data/lib/rackstash/runner.rb +36 -0
- data/lib/rackstash/version.rb +1 -1
- data/lib/tasks/rackstash.rb +9 -0
- data/rackstash.gemspec +26 -26
- data/test/buffered_logger_test.rb +191 -0
- data/test/log_subscriber_test.rb +89 -0
- data/test/rackstash_test.rb +104 -0
- data/test/runner_test.rb +82 -0
- data/test/test_helper.rb +13 -0
- metadata +105 -27
- data/CODE_OF_CONDUCT.md +0 -49
- data/bin/console +0 -14
- data/bin/setup +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 350b0ac721bfde86c8259470b3f7e6c3911d9f5c
|
4
|
+
data.tar.gz: ab47f38168d36ca48de001035affae5f9664293d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 895bbb5b842bdac28f4198d6f516c413458fd7da29f5f6c05f215356723431fc6143a5dca9648c04885cd678aa90aae4489017dd1e05581e40692de96cae0510
|
7
|
+
data.tar.gz: 5ec42ec1c69e9f4792c61a3a4cd0eeb329941b580a4834ea9a5ff182ac224c030106d515d4e3443a4f97f988c9dc240f55879a568a0321ac5f273850695ab6d7
|
data/.gitignore
CHANGED
@@ -1,9 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
data/.travis.yml
ADDED
@@ -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
|
data/LICENSE.txt
CHANGED
@@ -1,21 +1,22 @@
|
|
1
|
-
|
1
|
+
Copyright (c) 2012-2014 Holger Just, Planio GmbH <holger@plan.io>
|
2
2
|
|
3
|
-
|
3
|
+
MIT License
|
4
4
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
of this software and associated documentation files (the
|
7
|
-
in the Software without restriction, including
|
8
|
-
to use, copy, modify, merge, publish,
|
9
|
-
copies of the Software, and to
|
10
|
-
furnished to do so, subject to
|
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
|
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,
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
+
[](https://rubygems.org/gems/rackstash)
|
148
|
+
[](https://travis-ci.org/planio-gmbh/rackstash)
|
149
|
+
[](https://codeclimate.com/github/planio-gmbh/rackstash)
|
150
|
+
[](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
|
-
|
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
|
data/bin/rackstash
ADDED
data/lib/rackstash.rb
CHANGED
@@ -1,5 +1,88 @@
|
|
1
|
-
require
|
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
|
-
#
|
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
|