logjam_agent 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+