yarder 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -7,16 +7,16 @@
7
7
 
8
8
  JSON Based Replacement logging system for Ruby on Rails.
9
9
 
10
- This is an experimental gem to see how easy / difficult it is to completely replace the default Ruby
10
+ This is an experimental gem to see how easy / difficult it is to completely replace the default Ruby
11
11
  on Rails logging system with one based on outputting JSON messages.
12
12
 
13
- This gem will create JSON based log entries designed for consumption by Logstash (although being
14
- JSON they can be read by other software). The JSON will contain the same information as can be found
13
+ This gem will create JSON based log entries designed for consumption by Logstash (although being
14
+ JSON they can be read by other software). The JSON will contain the same information as can be found
15
15
  in the default rails logging output.
16
16
 
17
17
  ## Current Status
18
18
 
19
- This gem is not production ready however it is probably ready for people interested in seeing the
19
+ This gem is not production ready however it is probably ready for people interested in seeing the
20
20
  results. All logging in a Rails3 app should be JSON formatted, including ad-hoc logging.
21
21
 
22
22
  Yarder has only been tested against Rails 3.2.8 on Ruby 1.9.3 and JRuby running in 1.9 mode. Test
@@ -26,9 +26,12 @@ unique to this gem still need to be created.
26
26
  There may be issues regarding outputting UTF-8 characters in logs on JRuby 1.6 in --1.9 mode. JRuby
27
27
  1.7 is recommended (These same issues exist in the man ruby loggers so use that as a guide).
28
28
 
29
- Any help, feedback or pull-requests would be much appreciated, especially related to refactoring and
29
+ Any help, feedback or pull-requests would be much appreciated, especially related to refactoring and
30
30
  test improvement
31
31
 
32
+ Version 0.1.0 of this gem is designed for logstash 1.2 and Kibana version 3. If you are using an older
33
+ version then it is probably best to stick with 0.0.2
34
+
32
35
  ## Installation
33
36
 
34
37
  Add this line to your Rails application's Gemfile:
@@ -57,16 +60,19 @@ module MyApp
57
60
 
58
61
  # Set a logger compatible with the standard ruby logger to be used by Yarder
59
62
  config.logger = Yarder::Logger.new(Rails.root.join('log',"#{Rails.env}.log").to_s)
60
-
63
+ # Logs AR SQL queries in debug level
64
+ config.logger.level = Logger::DEBUG
65
+ # Root name of nested logs, formerly known as :fields
66
+ config.logger.log_namespace = :rails
67
+ # Logstash logtype
68
+ config.logger.log_type = :rails_json_log
61
69
  end
62
70
  end
63
71
  ```
64
72
 
65
73
  ## Logstash Configuration
66
74
 
67
- Yarder currently creates log entries with a hard-coded logtype of "rails_json_log" (This may change
68
- in future and may become configurable) therefore your Logstash configuration file should be as
69
- follows:
75
+ Yarder currently creates log entries with default logtype of "rails_json_log" therefore your Logstash configuration file should be as follows:
70
76
 
71
77
  ```
72
78
  input {
@@ -78,7 +84,14 @@ input {
78
84
  }
79
85
  ```
80
86
 
81
- You will need to edit the path to point to your application's log file. Because Yarder creates json
87
+ ### Beaver
88
+
89
+ ```
90
+ [/var/www/rails/application-1/log/production.log]
91
+ format: rawjson
92
+ ```
93
+
94
+ You will need to edit the path to point to your application's log file. Because Yarder creates json
82
95
  serialized Logstash::Event entries there is no need to setup any filters
83
96
 
84
97
  ### Known issues
@@ -88,7 +101,7 @@ for nested JSON but logstash web does not.
88
101
 
89
102
  ## Developers
90
103
 
91
- Thoughts, suggestions, opinions and contributions are welcome.
104
+ Thoughts, suggestions, opinions and contributions are welcome.
92
105
 
93
106
  When contributing please make sure to run your tests with warnings enabled and make sure that
94
107
  yarder creates no warnings. (Warnings from other libraries like capybara etc. are ok)
@@ -96,5 +109,3 @@ yarder creates no warnings. (Warnings from other libraries like capybara etc. ar
96
109
  ```
97
110
  RUBYOPT=-w rake
98
111
  ```
99
-
100
-
@@ -8,58 +8,50 @@ module Yarder
8
8
  def start_processing(event)
9
9
  payload = event.payload
10
10
 
11
- entry.fields['controller'] = payload[:controller]
12
- entry.fields['action'] = payload[:action]
13
-
14
- format = payload[:format]
15
- entry.fields['format'] = format.to_s.downcase if format.is_a?(Symbol)
16
-
11
+ entry['name'] = payload[:controller]
12
+ entry['action'] = payload[:action]
13
+ entry['path'] = payload[:path]
14
+ entry['format'] = payload[:format].to_s.downcase
17
15
  end
18
16
 
19
17
  def process_action(event)
20
-
21
18
  payload = event.payload
22
- #TODO Think about additions. Comment out for the moment to shut up warnings
23
- #additions = ::ActionController::Base.log_process_action(payload)
24
19
 
25
20
  params = payload[:params].except(*INTERNAL_PARAMS)
26
- entry.fields['parameters'] = params unless params.empty?
27
-
28
- entry.fields['controller_duration'] = event.duration
29
-
30
- #TODO What on earth are additions and how should we handle them?
31
- # message << " (#{additions.join(" | ")})" unless additions.blank?
21
+ entry['parameters'] = params unless params.empty?
32
22
 
23
+ root['duration']['controller'] = event.duration
33
24
  end
34
25
 
35
26
  def halted_callback(event)
36
- entry.fields['halted_callback'] = event.payload[:filter]
27
+ entry['halted_callback'] = event.payload[:filter]
37
28
  end
38
29
 
39
30
  def send_file(event)
40
- entry.fields['send_file'] = event.payload[:path]
41
- entry.fields['send_file_duration'] = event.duration
31
+ entry['send_file'] = event.payload[:path]
32
+ root['duration']['send_file'] = event.duration
42
33
  end
43
34
 
44
35
  def redirect_to(event)
45
- entry.fields['redirect_to'] = event.payload[:location]
36
+ entry['redirect_to'] = event.payload[:location]
46
37
  end
47
38
 
48
39
  def send_data(event)
49
- entry.fields['send_data'] = event.payload[:filename]
50
- entry.fields['send_data_duration'] = event.duration
40
+ entry['send_data'] = event.payload[:filename]
41
+ root['duration']['send_data'] = event.duration
51
42
  end
52
43
 
53
44
  %w(write_fragment read_fragment exist_fragment?
54
45
  expire_fragment expire_page write_page).each do |method|
55
46
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
56
47
  def #{method}(event)
57
- entry.fields['cache'] ||= []
58
48
  cache_event = {}
59
49
  cache_event['key_or_path'] = event.payload[:key] || event.payload[:path]
60
50
  cache_event['type'] = #{method.to_s.humanize.inspect}
61
51
  cache_event['duration'] = event.duration
62
- entry.fields['cache'] << cache_event
52
+ cache << cache_event
53
+ root['duration']['cache'] ||= 0
54
+ root['duration']['cache'] += event.duration.to_f
63
55
  end
64
56
  METHOD
65
57
  end
@@ -67,7 +59,15 @@ module Yarder
67
59
  private
68
60
 
69
61
  def entry
70
- Yarder.log_entries[Thread.current]
62
+ @entry ||= (root['controller'] ||= {})
63
+ end
64
+
65
+ def cache
66
+ @cache ||= (root['cache'] ||= [])
67
+ end
68
+
69
+ def root
70
+ @root ||= Yarder.log_entries[Thread.current].fields.tap { |o| o['duration'] ||= {} }
71
71
  end
72
72
 
73
73
  end
@@ -4,14 +4,17 @@ module Yarder
4
4
  class LogSubscriber < ::ActiveSupport::LogSubscriber
5
5
 
6
6
  def render_template(event)
7
- entry.fields['rendering'] ||= []
8
7
  render_entry = {}
9
8
  render_entry['identifier'] = from_rails_root(event.payload[:identifier])
10
9
  render_entry['layout'] = from_rails_root(event.payload[:layout]) if event.payload[:layout]
11
10
  render_entry['duration'] = event.duration
12
11
 
12
+ entry.fields['rendering'] ||= []
13
13
  entry.fields['rendering'] << render_entry
14
14
 
15
+ entry.fields['duration'] ||= {}
16
+ entry.fields['duration']['rendering'] ||= 0
17
+ entry.fields['duration']['rendering'] += event.duration.to_f
15
18
  end
16
19
  alias :render_partial :render_template
17
20
  alias :render_collection :render_template
@@ -66,6 +66,10 @@ module Yarder
66
66
  entry = log_entry
67
67
  entry.fields['sql'] ||= []
68
68
  entry.fields['sql'] << sql_entry
69
+
70
+ entry.fields['duration'] ||= {}
71
+ entry.fields['duration']['sql'] ||= 0
72
+ entry.fields['duration']['sql'] += sql_entry['duration'].to_f
69
73
  entry.write(false)
70
74
  end
71
75
 
@@ -74,12 +78,12 @@ module Yarder
74
78
  end
75
79
 
76
80
  def log_entry
77
- Yarder.log_entries[Thread.current] ||
78
- Yarder::Event.new(Rails.logger, false).tap do |entry|
79
- entry.fields['uuid'] = SecureRandom.uuid
80
- #TODO Should really move this into the base logger
81
- entry.source = Socket.gethostname
82
- entry.type = "rails_json_log"
81
+ Yarder.log_entries[Thread.current] || Yarder::Event.create(Rails.logger, tags)
82
+ end
83
+
84
+ def tags
85
+ if log_tags = Rails.configuration.log_tags
86
+ log_tags.select{|tag| tag.is_a? String}
83
87
  end
84
88
  end
85
89
 
@@ -3,12 +3,7 @@ module Yarder
3
3
  class LogSubscriber < ::ActiveSupport::LogSubscriber
4
4
 
5
5
  def request(event)
6
-
7
- #TODO Think of a better name for this!
8
- entry.fields['active_resource'] ||= []
9
-
10
6
  request_entry = {}
11
-
12
7
  request_entry['method'] = event.payload[:method].to_s.upcase
13
8
  request_entry['uri'] = event.payload[:request_uri]
14
9
 
@@ -19,7 +14,12 @@ module Yarder
19
14
  request_entry['length'] = result.body.to_s.length
20
15
  request_entry['duration'] = event.duration
21
16
 
22
- entry.fields['active_resource'] << request_entry
17
+ entry.fields['rest'] ||= []
18
+ entry.fields['rest'] << request_entry
19
+
20
+ entry.fields['duration'] ||= {}
21
+ entry.fields['duration']['rest'] ||= 0
22
+ entry.fields['duration']['rest'] += event.duration.to_f
23
23
  end
24
24
 
25
25
  private
@@ -31,4 +31,3 @@ module Yarder
31
31
  end
32
32
  end
33
33
  end
34
-
@@ -3,14 +3,18 @@ module Yarder
3
3
  # Basically a wrapper for a LogStash event that keeps track of if it was created from a rack
4
4
  # middle-ware or not. This is important when it comes to deciding when to write the log
5
5
  class Event
6
-
7
6
  extend Forwardable
8
- def_delegators :@logstash_event, :fields, :message=, :source=, :type=, :tags, :to_json
7
+ def_delegators :@logstash_event, :[]=, :[], :to_json
9
8
 
10
9
  def initialize(logger, rack = false)
11
10
  @logger = logger
12
11
  @rack = rack
13
12
  @logstash_event = LogStash::Event.new
13
+
14
+ self['type'] = logger.log_type
15
+ self['tags'] ||= []
16
+ self.fields['duration'] = {}
17
+ self.fields['env'] = logger.env
14
18
  end
15
19
 
16
20
  def write(rack = false)
@@ -21,24 +25,14 @@ module Yarder
21
25
  end
22
26
  end
23
27
 
24
- def add_tags_to_logger(request, tags)
25
- tag_hash = []
26
- if tags
27
- tags.each do |tag|
28
- case tag
29
- when Symbol
30
- tag_hash << {tag.to_s => request.send(tag) }
31
- when Proc
32
- tag_hash << tag.call(request)
33
- else
34
- tag_hash << tag
35
- end
36
- end
37
- end
38
-
39
- @logger.push_request_tags(tag_hash)
28
+ def self.create(logger, tags, rack = false)
29
+ logger.push_request_tags(tags) if tags
30
+ new(logger, rack)
40
31
  end
41
32
 
33
+ def fields
34
+ @fields ||= (@logstash_event[@logger.log_namespace.to_s] ||= {})
35
+ end
42
36
  end
43
37
 
44
- end
38
+ end
@@ -1,4 +1,5 @@
1
1
  require 'logger'
2
+ require 'socket'
2
3
 
3
4
  module Yarder
4
5
 
@@ -39,11 +40,26 @@ module Yarder
39
40
  end
40
41
  end
41
42
 
43
+ attr_accessor :log_type, :log_namespace
44
+
42
45
  def initialize(*args)
43
46
  super
47
+ self.log_type = :rails_json_log
48
+ self.log_namespace = :rails
44
49
  @formatter = SimpleFormatter.new
45
50
  end
46
51
 
52
+ def env
53
+ @env ||= {
54
+ :ruby => "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}",
55
+ :env => Rails.env,
56
+ :pwd => Dir.pwd,
57
+ :program => $0,
58
+ :user => ENV['USER'],
59
+ :host => ::Socket.gethostname
60
+ }
61
+ end
62
+
47
63
  # Simple formatter which only displays the message.
48
64
  class SimpleFormatter < ::Logger::Formatter
49
65
  # This method is invoked when a log event occurs
@@ -52,4 +68,4 @@ module Yarder
52
68
  end
53
69
  end
54
70
  end
55
- end
71
+ end
@@ -13,40 +13,43 @@ module Yarder
13
13
  t1 = Time.now
14
14
  request = ActionDispatch::Request.new(env)
15
15
 
16
- event = Yarder::Event.new(Rails.logger, true)
17
- event.message = "#{request.request_method} #{request.filtered_path} for #{request.ip}"
18
- event.fields['client_ip'] = request.ip
19
- event.fields['method'] = request.request_method
20
- event.fields['path'] = request.filtered_path
21
- #TODO Should really move this into the base logger
22
- event.source = "http://#{Socket.gethostname}#{request.filtered_path}"
23
- event.type = "rails_json_log"
16
+ event = Yarder::Event.create Rails.logger, tags(request), true
17
+ event['message'] = "#{request.request_method} #{request.filtered_path} for #{request.ip}"
24
18
 
25
- event.add_tags_to_logger(request, @tags) if @tags
19
+ entry = (event.fields['rack'] ||= {})
20
+ entry['client_ip'] = request.ip
21
+ entry['method'] = request.request_method
22
+ entry['path'] = request.filtered_path
23
+ entry['url'] = request.url
26
24
 
27
25
  Yarder.log_entries[Thread.current] = event
28
26
 
29
27
  status, headers, response = @app.call(env)
30
28
  [status, headers, response]
31
-
32
29
  ensure
33
30
  if event
34
- event.fields['total_duration'] = Time.now - t1
35
- event.fields['status'] = status
36
-
37
- ['rendering','sql'].each do |type|
38
- if event.fields[type] && !event.fields[type].empty?
39
- duration = event.fields[type].inject(0) {|result, local_event| result += local_event[:duration].to_f }
40
- event.fields["#{type}_duration"] = duration
41
- end
42
- end
43
-
31
+ entry['status'] = status
32
+ event.fields['duration']['total'] = (Time.now - t1)*1000
44
33
  event.write(true)
45
34
  end
46
35
 
47
36
  Yarder.log_entries[Thread.current] = nil
48
37
  end
49
38
 
39
+ def tags(request)
40
+ return unless @tags
41
+ @tags.reduce([]) do |arr, tag|
42
+ case tag
43
+ when Symbol
44
+ arr << {tag.to_s => request.send(tag) }
45
+ when Proc
46
+ arr << tag.call(request)
47
+ else
48
+ arr << tag
49
+ end
50
+ end
51
+ end
52
+
50
53
  end
51
54
 
52
55
  end
@@ -24,9 +24,10 @@ module Yarder
24
24
  @entry = msg
25
25
  else
26
26
  @entry = Yarder::Event.new(Rails.logger)
27
- @entry.message = msg
27
+ @entry['message'] = msg
28
28
  end
29
- @entry.fields['severity'] = severity
29
+ @entry['severity'] = severity
30
+
30
31
  process_tags(current_tags)
31
32
  process_tags(current_request_tags)
32
33
  #TODO Should we do anything with progname? What about source?
@@ -57,7 +58,7 @@ module Yarder
57
58
  @entry.fields[k] = v
58
59
  end
59
60
  else
60
- @entry.tags << tag
61
+ @entry['tags'] << tag
61
62
  end
62
63
  end
63
64
  end
@@ -1,3 +1,3 @@
1
1
  module Yarder
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -28,12 +28,13 @@ class ACLogSubscriberTest < ActionController::TestCase
28
28
  end
29
29
 
30
30
  def test_start_processing
31
- get :show, {:test => 'test'}
31
+ get :show, {:attr => 'test'}
32
32
  wait
33
33
 
34
- assert_equal "LogSubscribersController", @log_entry.fields['controller']
35
- assert_equal "show", @log_entry.fields['action']
36
- assert_equal "html", @log_entry.fields['format']
34
+ assert_equal "LogSubscribersController", @log_entry.fields['controller']['name']
35
+ assert_equal "show", @log_entry.fields['controller']['action']
36
+ assert_equal "/show?attr=test", @log_entry.fields['controller']['path']
37
+ assert_equal "html", @log_entry.fields['controller']['format']
37
38
  end
38
39
 
39
40
 
@@ -41,28 +42,28 @@ class ACLogSubscriberTest < ActionController::TestCase
41
42
  get :never_executed
42
43
  wait
43
44
 
44
- assert_equal ":redirector" ,@log_entry.fields['halted_callback']
45
+ assert_equal ":redirector" ,@log_entry.fields['controller']['halted_callback']
45
46
  end
46
47
 
47
- def test_process_action
48
+ def test_controller_duration_action
48
49
  get :show
49
50
  wait
50
51
 
51
- assert_present @log_entry.fields['controller_duration']
52
+ assert_present @log_entry.fields['duration']['controller']
52
53
  end
53
54
 
54
55
  def test_process_action_without_parameters
55
56
  get :show
56
57
  wait
57
58
 
58
- assert_blank @log_entry.fields['parameters']
59
+ assert_blank @log_entry.fields['controller']['parameters']
59
60
  end
60
61
 
61
62
  def test_process_action_with_parameters
62
63
  get :show, :id => '10'
63
64
  wait
64
65
 
65
- assert_equal '10', @log_entry.fields['parameters']['id']
66
+ assert_equal '10', @log_entry.fields['controller']['parameters']['id']
66
67
  end
67
68
 
68
69
  def test_process_action_with_wrapped_parameters
@@ -70,8 +71,8 @@ class ACLogSubscriberTest < ActionController::TestCase
70
71
  post :show, :id => '10', :name => 'jose'
71
72
  wait
72
73
 
73
- assert_equal '10', @log_entry.fields['parameters']['id']
74
- assert_equal 'jose', @log_entry.fields['parameters']['name']
74
+ assert_equal '10', @log_entry.fields['controller']['parameters']['id']
75
+ assert_equal 'jose', @log_entry.fields['controller']['parameters']['name']
75
76
  end
76
77
 
77
78
  def test_process_action_with_filter_parameters
@@ -80,7 +81,7 @@ class ACLogSubscriberTest < ActionController::TestCase
80
81
  get :show, :lifo => 'Pratik', :amount => '420', :step => '1'
81
82
  wait
82
83
 
83
- params = @log_entry.fields['parameters']
84
+ params = @log_entry.fields['controller']['parameters']
84
85
  assert_equal '[FILTERED]', params['amount']
85
86
  assert_equal '[FILTERED]', params['lifo']
86
87
  assert_equal '1', params['step']
@@ -90,7 +91,7 @@ class ACLogSubscriberTest < ActionController::TestCase
90
91
  get :redirector
91
92
  wait
92
93
 
93
- assert_equal 'http://foo.bar/', @log_entry.fields['redirect_to']
94
+ assert_equal 'http://foo.bar/', @log_entry.fields['controller']['redirect_to']
94
95
  end
95
96
 
96
97
 
@@ -98,8 +99,8 @@ class ACLogSubscriberTest < ActionController::TestCase
98
99
  get :data_sender
99
100
  wait
100
101
 
101
- assert_equal 'file.txt', @log_entry.fields['send_data']
102
- assert_present @log_entry.fields['send_data_duration']
102
+ assert_equal 'file.txt', @log_entry.fields['controller']['send_data']
103
+ assert_present @log_entry.fields['duration']['send_data']
103
104
  end
104
105
 
105
106
 
@@ -107,8 +108,8 @@ class ACLogSubscriberTest < ActionController::TestCase
107
108
  get :file_sender
108
109
  wait
109
110
 
110
- assert_match 'test/dummy/public/favicon.ico', @log_entry.fields['send_file']
111
- assert_present @log_entry.fields['send_file_duration']
111
+ assert_match 'test/dummy/public/favicon.ico', @log_entry.fields['controller']['send_file']
112
+ assert_present @log_entry.fields['duration']['send_file']
112
113
  end
113
114
 
114
115
  def test_with_fragment_cache
@@ -123,6 +124,8 @@ class ACLogSubscriberTest < ActionController::TestCase
123
124
 
124
125
  assert_match('Write fragment', @log_entry.fields['cache'].last['type'])
125
126
  assert_match('views/foo', @log_entry.fields['cache'].last['key_or_path'])
127
+
128
+ assert_present @log_entry.fields['duration']['cache']
126
129
  ensure
127
130
  LogSubscribersController.config.perform_caching = true
128
131
  end
@@ -144,22 +147,20 @@ class ACLogSubscriberTest < ActionController::TestCase
144
147
  LogSubscribersController.config.perform_caching = true
145
148
  end
146
149
 
147
- =begin TODO Figure out why this last test fails.
148
150
  def test_with_page_cache
149
151
  @controller.config.perform_caching = true
150
152
  get :with_page_cache
151
153
  wait
152
154
 
153
- assert_present @log_entry.fields['cache']
155
+ assert_present @log_entry.fields['cache']
154
156
 
155
- assert_match('Write page', @log_entry.fields['cache'][1]['type'])
156
- assert_match('index.html', @log_entry.fields['cache'][1]['key_or_path'])
157
+ assert_match('Write page', @log_entry.fields['cache'].first['type'])
158
+ assert_match('index.html', @log_entry.fields['cache'].first['key_or_path'])
157
159
  ensure
158
160
  @controller.config.perform_caching = true
159
161
  end
160
- =end
161
162
 
162
163
  def logs
163
164
  @logs ||= @logger.logged(:info)
164
165
  end
165
- end
166
+ end