logjam_agent 0.29.6 → 0.32.2

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.
@@ -0,0 +1,19 @@
1
+ # see https://github.com/chuckremes/ffi-rzmq/pull/133
2
+
3
+ require 'ffi-rzmq'
4
+ require 'ffi-rzmq/message'
5
+
6
+ module ZMQ
7
+ class Message
8
+ def copy_in_bytes bytes, len
9
+ data_buffer = LibC.malloc len
10
+ # writes the exact number of bytes, no null byte to terminate string
11
+ data_buffer.put_bytes 0, bytes, 0, len
12
+
13
+ # use libC to call free on the data buffer; earlier versions used an
14
+ # FFI::Function here that called back into Ruby, but Rubinius won't
15
+ # support that and there are issues with the other runtimes too
16
+ LibZMQ.zmq_msg_init_data @pointer, data_buffer, len, LibC::Free, nil
17
+ end
18
+ end
19
+ end
@@ -7,14 +7,19 @@ module LogjamAgent
7
7
  class Logger < ActiveSupport::LogSubscriber
8
8
  def initialize(app, taggers = nil)
9
9
  @app = app
10
- @taggers = taggers || Rails.application.config.log_tags || []
10
+ @taggers = taggers || (defined?(Rails::Railtie) ? Rails.application.config.log_tags : []) || []
11
11
  @hostname = LogjamAgent.hostname
12
12
  @asset_prefix = Rails.application.config.assets.prefix rescue "---"
13
13
  @ignore_asset_requests = LogjamAgent.ignore_asset_requests
14
14
  end
15
15
 
16
16
  def call(env)
17
- request = ActionDispatch::Request.new(env)
17
+ if env["logjam_agent.framework"] == :sinatra
18
+ request = ::Sinatra::Request.new(env)
19
+ env["rack.logger"] = logger
20
+ else
21
+ request = ActionDispatch::Request.new(env)
22
+ end
18
23
 
19
24
  if logger.respond_to?(:tagged) && !@taggers.empty?
20
25
  logger.tagged(compute_tags(request)) { call_app(request, env) }
@@ -88,7 +93,7 @@ module LogjamAgent
88
93
  spoofed = nil
89
94
  ip = nil
90
95
  begin
91
- ip = LogjamAgent.ip_obfuscator(env["action_dispatch.remote_ip"].to_s)
96
+ ip = LogjamAgent.ip_obfuscator((env["action_dispatch.remote_ip"] || request.ip).to_s)
92
97
  rescue ActionDispatch::RemoteIp::IpSpoofAttackError => spoofed
93
98
  ip = "*** SPOOFED IP ***"
94
99
  end
@@ -107,6 +112,11 @@ module LogjamAgent
107
112
  if completed_info = Thread.current.thread_variable_get(:time_bandits_completed_info)
108
113
  _, additions, view_time, _ = completed_info
109
114
  end
115
+ additions ||= []
116
+ if env["logjam_agent.framework"] == :sinatra
117
+ TimeBandits.consumed
118
+ additions.concat TimeBandits.runtimes
119
+ end
110
120
  logjam_request = LogjamAgent.request
111
121
 
112
122
  if (allowed_time_ms = env['HTTP_X_LOGJAM_CALLER_TIMEOUT'].to_i) > 0 && (run_time_ms > allowed_time_ms)
@@ -123,9 +133,9 @@ module LogjamAgent
123
133
  info message unless logjam_request.ignored?
124
134
 
125
135
  ActiveSupport::LogSubscriber.flush_all!
126
- request_info = {
127
- :total_time => run_time_ms, :code => status, :view_time => view_time || 0.0, :wait_time => wait_time_ms
128
- }
136
+ request_info = { :total_time => run_time_ms, :code => status }
137
+ request_info[:view_time] = view_time if view_time
138
+ request_info[:wait_time] = wait_time_ms if wait_time_ms > 0
129
139
  logjam_request.fields.merge!(request_info)
130
140
 
131
141
  env["time_bandits.metrics"] = TimeBandits.metrics
@@ -153,7 +163,7 @@ module LogjamAgent
153
163
 
154
164
  result
155
165
  rescue Exception => e
156
- Rails.logger.error(e)
166
+ logger.error(e)
157
167
  result
158
168
  end
159
169
 
@@ -197,82 +207,10 @@ module LogjamAgent
197
207
 
198
208
  headers
199
209
  end
200
-
201
210
  end
202
211
  end
203
212
  end
204
213
 
205
- # patch the actioncontroller logsubscriber to set the action on the logjam logger as soon as it starts processing the request
206
- require 'action_controller/metal/instrumentation'
207
- require 'action_controller/log_subscriber'
208
-
209
- module ActionController #:nodoc:
210
-
211
- class LogSubscriber
212
- if Rails::VERSION::STRING =~ /\A3\.0/
213
- def start_processing(event)
214
- payload = event.payload
215
- params = payload[:params].except(*INTERNAL_PARAMS)
216
-
217
- controller = payload[:controller]
218
- action = payload[:action]
219
- full_name = "#{controller}##{action}"
220
- action_name = LogjamAgent.action_name_proc.call(full_name)
221
-
222
- LogjamAgent.request.fields[:action] = action_name
223
-
224
- info " Processing by #{full_name} as #{payload[:formats].first.to_s.upcase}"
225
- info " Parameters: #{params.inspect}" unless params.empty?
226
- end
227
-
228
- elsif Rails::VERSION::STRING =~ /\A3\.1/
229
-
230
- def start_processing(event)
231
- payload = event.payload
232
- params = payload[:params].except(*INTERNAL_PARAMS)
233
- format = payload[:format]
234
- format = format.to_s.upcase if format.is_a?(Symbol)
235
-
236
- controller = payload[:controller]
237
- action = payload[:action]
238
- full_name = "#{controller}##{action}"
239
- action_name = LogjamAgent.action_name_proc.call(full_name)
240
-
241
- LogjamAgent.request.fields[:action] = action_name
242
-
243
- info " Processing by #{full_name} as #{format}"
244
- info " Parameters: #{params.inspect}" unless params.empty?
245
- end
246
-
247
- elsif Rails::VERSION::STRING =~ /\A(3\.2|4|5|6)/
248
-
249
- # Rails 4.1 uses method_added to automatically subscribe newly
250
- # added methods. Since start_processing is already defined, the
251
- # net effect is that start_processing gets called
252
- # twice. Therefore, we temporarily switch to protected mode and
253
- # change it back later to public.
254
- protected
255
- def start_processing(event)
256
- payload = event.payload
257
- params = payload[:params].except(*INTERNAL_PARAMS)
258
- format = payload[:format]
259
- format = format.to_s.upcase if format.is_a?(Symbol)
260
-
261
- controller = payload[:controller]
262
- action = payload[:action]
263
- full_name = "#{controller}##{action}"
264
- action_name = LogjamAgent.action_name_proc.call(full_name)
265
-
266
- LogjamAgent.request.fields[:action] = action_name
267
-
268
- info "Processing by #{full_name} as #{format}"
269
- info " Parameters: #{params.inspect}" unless params.empty?
270
- end
271
- public :start_processing
272
-
273
- else
274
- raise "logjam_agent ActionController monkey patch is not compatible with your Rails version"
275
- end
276
- end
277
-
214
+ if defined?(Rails::Railtie)
215
+ require_relative "rails_support"
278
216
  end
@@ -0,0 +1,26 @@
1
+ # patch the actioncontroller logsubscriber to set the action on the logjam logger as soon as it starts processing the request
2
+ require 'action_controller/metal/instrumentation'
3
+ require 'action_controller/log_subscriber'
4
+
5
+ module ActionController #:nodoc:
6
+
7
+ class LogSubscriber
8
+ def start_processing(event)
9
+ payload = event.payload
10
+ params = payload[:params].except(*INTERNAL_PARAMS)
11
+ format = payload[:format]
12
+ format = format.to_s.upcase if format.is_a?(Symbol)
13
+
14
+ controller = payload[:controller]
15
+ action = payload[:action]
16
+ full_name = "#{controller}##{action}"
17
+ action_name = LogjamAgent.action_name_proc.call(full_name)
18
+
19
+ LogjamAgent.request.fields[:action] = action_name
20
+
21
+ info "Processing by #{full_name} as #{format}"
22
+ info " Parameters: #{params.inspect}" unless params.empty?
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,32 @@
1
+ require 'sinatra/base'
2
+ begin
3
+ require 'active_support/parameter_filter'
4
+ rescue LoadError
5
+ require_relative '../active_support/parameter_filter'
6
+ end
7
+
8
+ # Extend the Sinatra Request class with some methods to make it look more like an
9
+ # ActionDispatch request.
10
+
11
+ class Sinatra::Request
12
+ alias_method :method, :request_method
13
+ def query_parameters; self.GET; end
14
+ def request_parameters; self.POST; end
15
+
16
+ def parameter_filter
17
+ ActiveSupport::ParameterFilter.new(LogjamAgent.parameter_filters)
18
+ end
19
+
20
+ KV_RE = '[^&;=]+'
21
+ PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
22
+
23
+ def filtered_path
24
+ return path if query_string.empty?
25
+ filter = parameter_filter
26
+ filtered_query_string = query_string.gsub(PAIR_RE) do |_|
27
+ filter.filter($1 => $2).first.join("=")
28
+ end
29
+ "#{path}?#{filtered_query_string}"
30
+ end
31
+
32
+ end
@@ -80,6 +80,9 @@ module LogjamAgent
80
80
  # disable logjam request forwarding by default in test environment
81
81
  LogjamAgent.disable! if Rails.env.test?
82
82
 
83
+ # only sent pings in production like environments
84
+ LogjamAgent.ensure_ping_at_exit = !%w(test development).include?(Rails.env.to_s)
85
+
83
86
  # patch controller testing to create a logjam request, because middlewares aren't executed
84
87
  if Rails.env.test?
85
88
  ActiveSupport.on_load(:action_controller) do
@@ -0,0 +1,22 @@
1
+ module LogjamAgent
2
+ class Receiver
3
+ def initialize
4
+ @socket = ZMQForwarder.context.socket(ZMQ::ROUTER)
5
+ @socket.setsockopt(ZMQ::RCVTIMEO, 100)
6
+ if @socket.bind("inproc://app") < 0
7
+ raise "ZMQ error on binding: #{ZMQ::Util.error_string}"
8
+ end
9
+ at_exit { @socket.close }
10
+ end
11
+
12
+ def receive
13
+ answer_parts = []
14
+ if @socket.recv_strings(answer_parts) < 0
15
+ raise "ZMQ error on receiving: #{ZMQ::Util.error_string}"
16
+ end
17
+ answer_parts.shift
18
+ answer_parts[2] = JSON.parse(answer_parts[2])
19
+ answer_parts
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,116 @@
1
+ require 'sinatra'
2
+ require 'logger'
3
+ require 'logjam_agent'
4
+ require 'logjam_agent/middleware'
5
+ require 'logjam_agent/rack/sinatra_request'
6
+ require 'logjam_agent/rack/logger'
7
+ require 'time_bandits'
8
+
9
+ module LogjamAgent
10
+ module Sinatra
11
+ class Middleware
12
+ def initialize(app)
13
+ app_with_logging = LogjamAgent::Rack::Logger.new(app)
14
+ @app = LogjamAgent::Middleware.new(app_with_logging, :sinatra)
15
+ end
16
+ def call(env)
17
+ @app.call(env)
18
+ end
19
+ end
20
+
21
+ module Helpers
22
+ def action_name(action_name)
23
+ LogjamAgent.request.fields[:action] = action_name
24
+ end
25
+
26
+ def logger
27
+ LogjamAgent.logger
28
+ end
29
+ end
30
+
31
+ def setup_logjam_logger
32
+ log_path = ENV["APP_LOG_TO_STDOUT"].present? ? STDOUT : "#{settings.root}/log/#{LogjamAgent.environment_name}.log"
33
+ logger = LogjamAgent::BufferedLogger.new(log_path) rescue LogjamAgent::BufferedLogger.new(STDERR)
34
+
35
+ loglevel = settings.respond_to?(:loglevel) ? settings.loglevel : :info
36
+ logger.level = ::Logger.const_get(loglevel.to_s.upcase)
37
+
38
+ LogjamAgent.log_device_log_level = logger.level
39
+ LogjamAgent.log_device_log_level = ::Logger::ERROR unless %i[test development].include?(settings.environment.to_sym)
40
+
41
+ logger.formatter = LogjamAgent::SyslogLikeFormatter.new
42
+ logger = ActiveSupport::TaggedLogging.new(logger)
43
+ LogjamAgent.logger = logger
44
+ ActiveSupport::LogSubscriber.logger = logger
45
+
46
+ log_path = ENV["APP_LOG_TO_STDOUT"].present? ? STDOUT : "#{settings.root}/log/logjam_agent_error.log"
47
+ forwarding_error_logger = ::Logger.new(log_path) rescue ::Logger.new(STDERR)
48
+ forwarding_error_logger.level = ::Logger::ERROR
49
+ forwarding_error_logger.formatter = ::Logger::Formatter.new
50
+ LogjamAgent.forwarding_error_logger = forwarding_error_logger
51
+
52
+ truncate_overlong_params = lambda { |key, value|
53
+ max_size = LogjamAgent.max_logged_size_for(key)
54
+ if value.is_a?(String) && value.size > max_size
55
+ value[max_size..-1] = " ... [TRUNCATED]"
56
+ end
57
+ }
58
+ LogjamAgent.parameter_filters << truncate_overlong_params
59
+ end
60
+
61
+ def self.registered(app)
62
+ app.helpers Helpers
63
+ LogjamAgent.environment_name = ENV['LOGJAM_ENV'] || app.settings.environment.to_s
64
+ LogjamAgent.auto_detect_logged_exceptions
65
+ LogjamAgent.disable! if app.settings.environment.to_sym == :test
66
+ end
67
+ end
68
+ end
69
+
70
+ # For classic apps.
71
+ Sinatra.register LogjamAgent::Sinatra
72
+
73
+ # We already supply a logger.
74
+ Sinatra::Base.class_eval do
75
+ class << self
76
+ def setup_logging(builder); end
77
+ end
78
+ end
79
+
80
+ # Patch Sinatra's render logic to compute corrected view times.
81
+ module LogjamAgent
82
+ module ComputeRenderTimes
83
+ def render(engine, data, options = {}, locals = {}, &block)
84
+ consumed_before_rendering = TimeBandits.consumed
85
+ result = exception = nil
86
+ duration = Benchmark.ms do
87
+ begin
88
+ result = super
89
+ rescue => exception
90
+ end
91
+ end
92
+ consumed_during_rendering = TimeBandits.consumed - consumed_before_rendering
93
+ duration -= consumed_during_rendering
94
+ raise exception if exception
95
+ result
96
+ ensure
97
+ Thread.current.thread_variable_set(
98
+ :time_bandits_completed_info,
99
+ [ duration, ["Views: %.3fms" % duration.to_f], duration, "" ]
100
+ )
101
+ end
102
+ end
103
+ end
104
+
105
+ Sinatra::Base.prepend LogjamAgent::ComputeRenderTimes
106
+
107
+ # Define exception, but don't do anything about it. Sneaky!
108
+ module ActionDispatch
109
+ module RemoteIp
110
+ class IpSpoofAttackError < StandardError; end
111
+ end
112
+ end
113
+
114
+ # Add GC time bandit
115
+ TimeBandits.reset
116
+ TimeBandits.add TimeBandits::TimeConsumers::GarbageCollection.instance if GC.respond_to? :enable_stats
@@ -5,11 +5,19 @@ module LogjamAgent
5
5
  def initialize
6
6
  @hostname = LogjamAgent.hostname
7
7
  @app_name = "rails"
8
- @attributes = []
9
8
  @newline = ActiveSupport::VERSION::STRING < "4.0" ? "" : "\n"
10
9
  end
11
10
 
12
- attr_accessor :attributes
11
+ def attributes=(attributes)
12
+ Thread.current.thread_variable_set(:__logjam_formatter_attributes__, attributes)
13
+ end
14
+
15
+ def attributes
16
+ unless attributes = Thread.current.thread_variable_get(:__logjam_formatter_attributes__)
17
+ attributes = Thread.current.thread_variable_set(:__logjam_formatter_attributes__, [])
18
+ end
19
+ attributes
20
+ end
13
21
 
14
22
  SEV_LABEL = Logger::SEV_LABEL.map{|sev| "%-5s" % sev}
15
23
 
@@ -33,7 +41,7 @@ module LogjamAgent
33
41
  "#{format_severity(severity)} #{format_time(timestamp)}#{render_attributes}#{format_host_info(progname)}: #{format_message(msg)}#{@newline}"
34
42
  end
35
43
 
36
- if !defined?(Rails) || Rails.env.development?
44
+ if !defined?(Rails::Railtie) || Rails.env.development?
37
45
  def format_host_info(progname); ""; end
38
46
  else
39
47
  def format_host_info(progname)
@@ -42,19 +50,19 @@ module LogjamAgent
42
50
  end
43
51
 
44
52
  def render_attributes
45
- @attributes.map{|key, value| " #{key}[#{value}]"}.join
53
+ attributes.map{|key, value| " #{key}[#{value}]"}.join
46
54
  end
47
55
 
48
56
  def set_attribute(name, value)
49
- if attribute = @attributes.detect{|n,v| n == name}
57
+ if attribute = attributes.detect{|n,v| n == name}
50
58
  attribute[1] = value
51
59
  else
52
- @attributes << [name, value]
60
+ attributes << [name, value]
53
61
  end
54
62
  end
55
63
 
56
64
  def reset_attributes
57
- @attributes = []
65
+ self.attributes = []
58
66
  end
59
67
  end
60
68
  end
@@ -62,7 +62,12 @@ module LogjamAgent
62
62
  protocol, host, port = %r{\A(?:([^:]+)://)?([^:]+)(?::(\d+))?\z}.match(spec).captures
63
63
  protocol ||= "tcp"
64
64
  port ||= default_port
65
- "#{protocol}://#{host}:#{port}"
65
+ if protocol == "inproc"
66
+ # should only be used for integration tests
67
+ "#{protocol}://#{host}"
68
+ else
69
+ "#{protocol}://#{host}:#{port}"
70
+ end
66
71
  end
67
72
  end
68
73
  end