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.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.rdoc +8 -0
- data/Rakefile +1 -0
- data/init.rb +1 -0
- data/lib/logjam_agent.rb +18 -0
- data/lib/logjam_agent/amqp_forwarder.rb +94 -0
- data/lib/logjam_agent/buffered_logger.rb +55 -0
- data/lib/logjam_agent/forwarders.rb +19 -0
- data/lib/logjam_agent/middleware.rb +25 -0
- data/lib/logjam_agent/request.rb +42 -0
- data/lib/logjam_agent/syslog_like_formatter.rb +39 -0
- data/lib/logjam_agent/version.rb +3 -0
- data/logjam_agent.gemspec +25 -0
- data/rails/init.rb +1 -0
- metadata +111 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -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)
|
data/lib/logjam_agent.rb
ADDED
@@ -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,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
|
data/rails/init.rb
ADDED
@@ -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
|
+
|