logjam_agent 0.0.1

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,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in logjam_agent.gemspec
4
+ gemspec
@@ -0,0 +1,8 @@
1
+ == Logjam Agent
2
+
3
+ Client side library for logjam.
4
+
5
+ Provides a customized logger, which logs lines and sends combined request information via
6
+ AMQP to logjam.
7
+
8
+
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ ActionController::Dispatcher.middleware.insert_before(ActionController::Failsafe, LogjamAgent::Middleware)
@@ -0,0 +1,18 @@
1
+ require "logjam_agent/version"
2
+ require "logjam_agent/amqp_forwarder"
3
+ require "logjam_agent/forwarders"
4
+ require "logjam_agent/request"
5
+ require "logjam_agent/buffered_logger"
6
+ require "logjam_agent/syslog_like_formatter"
7
+
8
+ module LogjamAgent
9
+
10
+ class ForwardingError < StandardError; end
11
+
12
+ mattr_accessor :error_handler
13
+ self.error_handler = lambda { |exception| }
14
+
15
+ mattr_accessor :application_name
16
+ self.application_name = "rails"
17
+
18
+ end
@@ -0,0 +1,94 @@
1
+ module LogjamAgent
2
+ class AMQPForwarder
3
+
4
+ RETRY_AFTER = 10.seconds
5
+
6
+ attr_reader :app, :env
7
+
8
+ def initialize(app, env, opts = {})
9
+ @app = app
10
+ @env = env
11
+ @config = default_options(app, env).merge!(opts)
12
+ @exchange = @bunny = nil
13
+ end
14
+
15
+ def default_options(app, env)
16
+ {
17
+ :host => "localhost",
18
+ :exchange => "request-stream-#{app}-#{env}",
19
+ :exchange_durable => true,
20
+ :exchange_auto_delete => false,
21
+ :routing_key => "logs.#{app}.#{env}"
22
+ }
23
+ end
24
+
25
+ # TODO: mutex!
26
+ def send(msg)
27
+ return if paused?
28
+ begin
29
+ # $stderr.puts msg
30
+ exchange.publish(msg, :key => @config[:routing_key], :persistent => false)
31
+ rescue Exception => exception
32
+ reraise_expectation_errors!
33
+ pause(exception)
34
+ end
35
+ end
36
+
37
+ def reset(exception=nil)
38
+ return unless @bunny
39
+ begin
40
+ if exception
41
+ @bunny.__send__(:close_socket)
42
+ else
43
+ @bunny.stop
44
+ end
45
+ rescue Exception
46
+ # if bunny throws an exception here, its not usable anymore anyway
47
+ ensure
48
+ @exchange = @bunny = nil
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ if defined?(Mocha)
55
+ def reraise_expectation_errors! #:nodoc:
56
+ raise if $!.is_a?(Mocha::ExpectationError)
57
+ end
58
+ else
59
+ def reraise_expectation_errors! #:nodoc:
60
+ # noop
61
+ end
62
+ end
63
+
64
+ def pause(exception)
65
+ @paused = Time.now
66
+ reset(exception)
67
+ raise ForwardingError.new("Could not log to AMQP exchange (#{exception.message})")
68
+ end
69
+
70
+ def paused?
71
+ @paused && @paused > RETRY_AFTER.ago
72
+ end
73
+
74
+ def exchange
75
+ @exchange ||=
76
+ begin
77
+ bunny.start unless bunny.connected?
78
+ bunny.exchange(@config[:exchange],
79
+ :durable => @config[:exchange_durable],
80
+ :auto_delete => @config[:exchange_auto_delete],
81
+ :type => :topic)
82
+ end
83
+ end
84
+
85
+ def bunny
86
+ @bunny ||=
87
+ begin
88
+ require "bunny" unless defined?(Bunny)
89
+ Bunny.new(:host => @config[:host], :socket_timeout => 1.0)
90
+ end
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,55 @@
1
+ require 'active_support/buffered_logger'
2
+ require 'active_support/core_ext/logger'
3
+ require 'fileutils'
4
+
5
+ module LogjamAgent
6
+ class BufferedLogger < ActiveSupport::BufferedLogger
7
+
8
+ attr_accessor :formatter
9
+
10
+ def initialize(*args)
11
+ super(*args)
12
+ # stupid bug in the buffered logger code
13
+ @log.write "\n"
14
+ @formatter = lambda{|_, _, _, message| message}
15
+ end
16
+
17
+ def start_request(app, env, initial_fields={})
18
+ Thread.current[:logjam_request] = Request.new(app, env, self, initial_fields)
19
+ end
20
+
21
+ def finish_request(additional_fields={})
22
+ # puts "finishing request"
23
+ if request = self.request
24
+ request.fields.merge!(additional_fields)
25
+ Thread.current[:logjam_request] = nil
26
+ request.forward
27
+ end
28
+ end
29
+
30
+ def request
31
+ Thread.current[:logjam_request]
32
+ end
33
+
34
+ def add(severity, message = nil, progname = nil, &block)
35
+ return if @level > severity
36
+ message = (message || (block && block.call) || '').to_s
37
+ # If a newline is necessary then create a new message ending with a newline.
38
+ # Ensures that the original message is not mutated.
39
+ message = "#{message}\n" unless message[-1] == ?\n
40
+ time = Time.now
41
+ buffer << formatter.call(severity, time, progname, message)
42
+ auto_flush
43
+ if request = self.request
44
+ # puts "adding line to request"
45
+ request.add_line(severity, time, message[0..-2])
46
+ end
47
+ message
48
+ end
49
+
50
+ def logdev=(log_device)
51
+ raise "cannot connect logger to new log device" unless log_device.respond_to?(:write)
52
+ @log = log_device
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,19 @@
1
+ module LogjamAgent
2
+ module Forwarders
3
+ @@forwarders = {}
4
+
5
+ extend self
6
+
7
+ def add(f)
8
+ @@forwarders["#{f.app}-#{f.env}"] = f
9
+ end
10
+
11
+ def get(app, env)
12
+ @@forwarders["#{app}-#{env}"]
13
+ end
14
+
15
+ def reset
16
+ @@forwarders.each_value {|f| f.reset}
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module LogjamAgent
2
+ class Middleware
3
+ def initialize(app, options={})
4
+ @app = app
5
+ @options = options
6
+ end
7
+
8
+ def call(env)
9
+ start_request(env)
10
+ @app.call(env)
11
+ ensure
12
+ finish_request(env)
13
+ end
14
+
15
+ private
16
+
17
+ def start_request(env)
18
+ Rails.logger.start_request(env["logjam_agent.application_name"]||LogjamAgent.application_name, Rails.env)
19
+ end
20
+
21
+ def finish_request(env)
22
+ Rails.logger.finish_request(env["time_bandits.metrics"])
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ require "json"
2
+ require "socket"
3
+
4
+ module LogjamAgent
5
+ class Request
6
+ attr_reader :fields
7
+
8
+ @@hostname = Socket.gethostname.split('.').first
9
+
10
+ def initialize(app, env, logger, initial_fields)
11
+ @logger = logger
12
+ @forwarder = Forwarders.get(app, env)
13
+ @lines = []
14
+ @fields = initial_fields.merge(:host => @@hostname, :process_id => Process.pid, :lines => @lines)
15
+ end
16
+
17
+ def add_line(severity, timestamp, message)
18
+ @lines << [severity, format_time(timestamp), message]
19
+ end
20
+
21
+ def forward
22
+ @forwarder.send(@fields.to_json)
23
+ rescue Exception => e
24
+ handle_forwarding_error(e)
25
+ end
26
+
27
+ private
28
+
29
+ def format_time(t)
30
+ # iso time with microseconds
31
+ t.strftime("%Y-%m-%dT%H:%M:%S.#{t.usec}")
32
+ end
33
+
34
+ def handle_forwarding_error(exception)
35
+ @logger.error exception.to_s
36
+ LogjamAgent.error_handler.call(exception)
37
+ rescue Exception
38
+ # swallow all exceptions
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,39 @@
1
+ require 'socket'
2
+ require 'logger'
3
+
4
+ module LogjamAgent
5
+ class SyslogLikeFormatter
6
+ def initialize
7
+ @hostname = Socket.gethostname.split('.').first
8
+ @app_name = "rails"
9
+ end
10
+
11
+ attr_accessor :extra_attributes
12
+
13
+ SEV_LABEL = Logger::SEV_LABEL.map{|sev| "%-5s" % sev}
14
+
15
+ def format_severity(severity)
16
+ SEV_LABEL[severity] || 'ALIEN'
17
+ end
18
+
19
+ def format_time(timestamp)
20
+ timestamp.strftime("%b %d %H:%M:%S.#{timestamp.usec}")
21
+ end
22
+
23
+ def format_msg(msg)
24
+ "#{msg}".sub(/^[\s\n]+/, '').sub(/[\s\n]+$/,"\n")
25
+ end
26
+
27
+ def call(severity, timestamp, progname, msg)
28
+ "#{format_severity(severity)} #{format_time(timestamp)} #{@hostname} #{progname||@app_name}[#{$$}]#{render_extra_attributes}: #{format_msg(msg)}"
29
+ end
30
+
31
+ def render_extra_attributes
32
+ (self.extra_attributes||[]).map{|key, value| " #{key}[#{value}]"}
33
+ end
34
+
35
+ def add_extra_attributes(attributes)
36
+ (self.extra_attributes ||= []).concat(attributes)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module LogjamAgent
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "logjam_agent/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "logjam_agent"
7
+ s.version = LogjamAgent::VERSION
8
+ s.authors = ["Stefan Kaes"]
9
+ s.email = ["stefan.kaes@xing.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Logjam client library to be used with logjam}
12
+ s.description = %q{Logjam logger and request information forwarding}
13
+
14
+ s.rubyforge_project = "logjam_agent"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ s.add_runtime_dependency "activesupport"
24
+ s.add_runtime_dependency "time_bandits", [">= 0.1.0"]
25
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path('../../init.rb', __FILE__)
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logjam_agent
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Stefan Kaes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-08-28 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: time_bandits
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 27
44
+ segments:
45
+ - 0
46
+ - 1
47
+ - 0
48
+ version: 0.1.0
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ description: Logjam logger and request information forwarding
52
+ email:
53
+ - stefan.kaes@xing.com
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files: []
59
+
60
+ files:
61
+ - .gitignore
62
+ - Gemfile
63
+ - README.rdoc
64
+ - Rakefile
65
+ - init.rb
66
+ - lib/logjam_agent.rb
67
+ - lib/logjam_agent/amqp_forwarder.rb
68
+ - lib/logjam_agent/buffered_logger.rb
69
+ - lib/logjam_agent/forwarders.rb
70
+ - lib/logjam_agent/middleware.rb
71
+ - lib/logjam_agent/request.rb
72
+ - lib/logjam_agent/syslog_like_formatter.rb
73
+ - lib/logjam_agent/version.rb
74
+ - logjam_agent.gemspec
75
+ - rails/init.rb
76
+ has_rdoc: true
77
+ homepage: ""
78
+ licenses: []
79
+
80
+ post_install_message:
81
+ rdoc_options: []
82
+
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ required_rubygems_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ requirements: []
104
+
105
+ rubyforge_project: logjam_agent
106
+ rubygems_version: 1.6.2
107
+ signing_key:
108
+ specification_version: 3
109
+ summary: Logjam client library to be used with logjam
110
+ test_files: []
111
+