batlog 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemspec +23 -0
- data/.gitignore +1 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/.travis.yml +4 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +102 -0
- data/README.md +112 -0
- data/Rakefile +3 -0
- data/app/models/log/db_log.rb +9 -0
- data/lib/batlog.rb +5 -0
- data/lib/log/config.rb +35 -0
- data/lib/log/controller_support.rb +43 -0
- data/lib/log/dispatcher.rb +57 -0
- data/lib/log/engine.rb +8 -0
- data/lib/log/events.rb +19 -0
- data/lib/log/log.rb +80 -0
- data/lib/log/loggable_error.rb +10 -0
- data/lib/log/utils.rb +28 -0
- data/lib/log/version.rb +3 -0
- data/lib/loggers/database_logger.rb +28 -0
- data/lib/loggers/exceptional_logger.rb +19 -0
- data/lib/loggers/rails_logger.rb +49 -0
- data/spec/lib/dispatcher_spec.rb +105 -0
- data/spec/lib/events_spec.rb +24 -0
- data/spec/lib/log_spec.rb +155 -0
- data/spec/lib/loggable_error_spec.rb +14 -0
- data/spec/spec_helper.rb +10 -0
- metadata +136 -0
data/.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require "./lib/log/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'batlog'
|
6
|
+
s.version = Log::VERSION
|
7
|
+
s.homepage = "https://github.com/TheGiftsProject/batlog"
|
8
|
+
s.summary = "A structured logging system"
|
9
|
+
s.authors = ["Asaf Gartner", "Yonatan Bergman"]
|
10
|
+
s.email = 'agartner@ebay.com'
|
11
|
+
s.platform = Gem::Platform::RUBY
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.require_path = "lib"
|
15
|
+
|
16
|
+
s.add_dependency "rails"
|
17
|
+
s.add_development_dependency 'rake'
|
18
|
+
s.add_development_dependency 'rspec'
|
19
|
+
|
20
|
+
s.test_files = Dir.glob('spec/lib/*_spec.rb')
|
21
|
+
|
22
|
+
s.post_install_message = "Run 'rails generate db_log' and then migrate to start using the log with database backend"
|
23
|
+
end
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
.idea
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format documentation
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.8.7@log --create
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
batlog (0.9)
|
5
|
+
rails
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (3.2.8)
|
11
|
+
actionpack (= 3.2.8)
|
12
|
+
mail (~> 2.4.4)
|
13
|
+
actionpack (3.2.8)
|
14
|
+
activemodel (= 3.2.8)
|
15
|
+
activesupport (= 3.2.8)
|
16
|
+
builder (~> 3.0.0)
|
17
|
+
erubis (~> 2.7.0)
|
18
|
+
journey (~> 1.0.4)
|
19
|
+
rack (~> 1.4.0)
|
20
|
+
rack-cache (~> 1.2)
|
21
|
+
rack-test (~> 0.6.1)
|
22
|
+
sprockets (~> 2.1.3)
|
23
|
+
activemodel (3.2.8)
|
24
|
+
activesupport (= 3.2.8)
|
25
|
+
builder (~> 3.0.0)
|
26
|
+
activerecord (3.2.8)
|
27
|
+
activemodel (= 3.2.8)
|
28
|
+
activesupport (= 3.2.8)
|
29
|
+
arel (~> 3.0.2)
|
30
|
+
tzinfo (~> 0.3.29)
|
31
|
+
activeresource (3.2.8)
|
32
|
+
activemodel (= 3.2.8)
|
33
|
+
activesupport (= 3.2.8)
|
34
|
+
activesupport (3.2.8)
|
35
|
+
i18n (~> 0.6)
|
36
|
+
multi_json (~> 1.0)
|
37
|
+
arel (3.0.2)
|
38
|
+
builder (3.0.4)
|
39
|
+
diff-lcs (1.1.3)
|
40
|
+
erubis (2.7.0)
|
41
|
+
hike (1.2.1)
|
42
|
+
i18n (0.6.1)
|
43
|
+
journey (1.0.4)
|
44
|
+
json (1.7.5)
|
45
|
+
mail (2.4.4)
|
46
|
+
i18n (>= 0.4.0)
|
47
|
+
mime-types (~> 1.16)
|
48
|
+
treetop (~> 1.4.8)
|
49
|
+
mime-types (1.19)
|
50
|
+
multi_json (1.3.6)
|
51
|
+
polyglot (0.3.3)
|
52
|
+
rack (1.4.1)
|
53
|
+
rack-cache (1.2)
|
54
|
+
rack (>= 0.4)
|
55
|
+
rack-ssl (1.3.2)
|
56
|
+
rack
|
57
|
+
rack-test (0.6.2)
|
58
|
+
rack (>= 1.0)
|
59
|
+
rails (3.2.8)
|
60
|
+
actionmailer (= 3.2.8)
|
61
|
+
actionpack (= 3.2.8)
|
62
|
+
activerecord (= 3.2.8)
|
63
|
+
activeresource (= 3.2.8)
|
64
|
+
activesupport (= 3.2.8)
|
65
|
+
bundler (~> 1.0)
|
66
|
+
railties (= 3.2.8)
|
67
|
+
railties (3.2.8)
|
68
|
+
actionpack (= 3.2.8)
|
69
|
+
activesupport (= 3.2.8)
|
70
|
+
rack-ssl (~> 1.3.2)
|
71
|
+
rake (>= 0.8.7)
|
72
|
+
rdoc (~> 3.4)
|
73
|
+
thor (>= 0.14.6, < 2.0)
|
74
|
+
rake (0.9.2.2)
|
75
|
+
rdoc (3.12)
|
76
|
+
json (~> 1.4)
|
77
|
+
rspec (2.11.0)
|
78
|
+
rspec-core (~> 2.11.0)
|
79
|
+
rspec-expectations (~> 2.11.0)
|
80
|
+
rspec-mocks (~> 2.11.0)
|
81
|
+
rspec-core (2.11.1)
|
82
|
+
rspec-expectations (2.11.3)
|
83
|
+
diff-lcs (~> 1.1.3)
|
84
|
+
rspec-mocks (2.11.3)
|
85
|
+
sprockets (2.1.3)
|
86
|
+
hike (~> 1.2)
|
87
|
+
rack (~> 1.0)
|
88
|
+
tilt (~> 1.1, != 1.3.0)
|
89
|
+
thor (0.16.0)
|
90
|
+
tilt (1.3.3)
|
91
|
+
treetop (1.4.11)
|
92
|
+
polyglot
|
93
|
+
polyglot (>= 0.3.1)
|
94
|
+
tzinfo (0.3.33)
|
95
|
+
|
96
|
+
PLATFORMS
|
97
|
+
ruby
|
98
|
+
|
99
|
+
DEPENDENCIES
|
100
|
+
batlog!
|
101
|
+
rake
|
102
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# BatLog v0.9
|
2
|
+
![batlog-logo](https://dl.dropbox.com/u/7525692/batlog-withtext.png "BatLog")
|
3
|
+
|
4
|
+
## Setup
|
5
|
+
To install BatLog into your Rails app just run `rails generate db_log` to create the db_log's table migration.
|
6
|
+
By default BatLog writes to three places - the database, the rails logger and exceptions to exceptional
|
7
|
+
You can overide this behaivour by providing your own loggers or changing the default logger in the configuration.
|
8
|
+
|
9
|
+
The cool thing about BatLog is that it adds the concept of `context` - your logs aren't one time message left by a deity but
|
10
|
+
are part of a flow of a user in specific system and circumstance. BatLog lets you collect a context for the current Thread
|
11
|
+
and dump it as part of the BatLog whenever needed be it in Exception and be it in debug prints.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
### Logging
|
15
|
+
```ruby
|
16
|
+
Log.debug(message, context)
|
17
|
+
Log.info(message, context)
|
18
|
+
Log.warn(message, context)
|
19
|
+
Log.error(message, context)
|
20
|
+
Log.fatal(message, context)
|
21
|
+
```
|
22
|
+
Use these to record logs. The method indicates the severity.
|
23
|
+
* `message` - The message to record to log. Can be of the following types:
|
24
|
+
* `String` - Will be used as-is.
|
25
|
+
* `Exception` - Message will be extracted from exception.
|
26
|
+
* `Log::LoggableError` - Message will be extracted from exception and data will be merged into context
|
27
|
+
* `context` - `Hash`. Extra data that's related to the message.
|
28
|
+
|
29
|
+
### Events
|
30
|
+
Events are kept in memory (until clear_events is called or the thread exits) and
|
31
|
+
are only written when one of the above log methods is called.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
Log.event(name, data)
|
35
|
+
```
|
36
|
+
Adds an event to `Log`'s internal event array.
|
37
|
+
* `name` - `String`. The name of the event.
|
38
|
+
* `data` - `Hash`. Extra data that's related to the event.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
Log.clear_events
|
42
|
+
```
|
43
|
+
Empties the list of events.
|
44
|
+
|
45
|
+
### Asserts
|
46
|
+
```ruby
|
47
|
+
Log.assert(condition, message, context, options)
|
48
|
+
```
|
49
|
+
* `condition` - `Boolean`. Indicates whether the condition you were testing succeeded. When `false` - triggers a log.
|
50
|
+
* `message` - Same as in the logging methods.
|
51
|
+
* `context` - Same as in the logging methods.
|
52
|
+
* `options` - `Hash`. Includes the following:
|
53
|
+
* `severity` - `Symbol`. The severity to use for logging when the condition fails. (Default: `:error`)
|
54
|
+
* `raise_error` - `Boolean`. Indicates whether the assert should raise an error if condition fails. (Default: `false`)
|
55
|
+
|
56
|
+
### Configuring `Log`
|
57
|
+
To configure Log just create an initializer `config/initializers/log.rb`
|
58
|
+
There you can set the configuration of the log system.
|
59
|
+
```ruby
|
60
|
+
Log.config.loggers << MyCustomLogger
|
61
|
+
# or
|
62
|
+
Log.config.loggers = [MyCustomLogger]
|
63
|
+
```
|
64
|
+
`Log.config.loggers` is an array of the loggers that will be called by the log dispatch each time you write a log.
|
65
|
+
They will be called in the order they appear in the array.
|
66
|
+
Default: `[DBlogger, RailsLogger, ExceptionalLogger]`
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
Log.config,raise_on_failed_asserts = false #boolean
|
70
|
+
```
|
71
|
+
Set to `true` if you want all asserts to raise an exception when they fail. Useful
|
72
|
+
when developing and testing.
|
73
|
+
Default: `false`
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
Log.config,raise_on_log_failure = false #boolean
|
77
|
+
```
|
78
|
+
Set to true if you want to raise an exception when one of the loggers raises an
|
79
|
+
exception.
|
80
|
+
If this is `false`, `Log` will only try to log the failure using the loggers that
|
81
|
+
didn't fail.
|
82
|
+
Default: `false`
|
83
|
+
|
84
|
+
### Creating a custom logger
|
85
|
+
A logger object needs to implement the following interface:
|
86
|
+
```ruby
|
87
|
+
class YourLogger
|
88
|
+
def self.log(severity, message, context, events, metadata)
|
89
|
+
# record stuff here
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
93
|
+
|
94
|
+
* `severity` - `Symbol`. The log level. One of the following: `debug`, `info`, `warn`, `error`, `fatal`
|
95
|
+
* `message` - `String`.
|
96
|
+
* `context` - `Hash`. Extra data that's related to the message.
|
97
|
+
* `events` - `Array`. A collection of all recorded events prior to the log call. (contents of the array covered in later section)
|
98
|
+
* `metadata` - `Hash`. Extra data related to the logging process, added by other loggers that ran prior to this one.
|
99
|
+
|
100
|
+
If your log method returns a `Hash`, it will be merged into metadata and sent to
|
101
|
+
all subsequent loggers.
|
102
|
+
|
103
|
+
|
104
|
+
### Controller Support
|
105
|
+
Also included with BatLog is the log controller support to use it we recommend you add it to your ApplicationController
|
106
|
+
by adding these lines to it.
|
107
|
+
```ruby
|
108
|
+
require 'log/controller_support'
|
109
|
+
|
110
|
+
include Log::ControllerSupport
|
111
|
+
```
|
112
|
+
This does two things, first it adds a before_filter that captures as much data as it can into the log.context and also hooks for when a CSRF exception occurs
|
data/Rakefile
ADDED
data/lib/batlog.rb
ADDED
data/lib/log/config.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'loggers/database_logger'
|
2
|
+
require 'loggers/rails_logger'
|
3
|
+
|
4
|
+
module Log
|
5
|
+
class Config
|
6
|
+
|
7
|
+
attr_accessor :raise_on_log_failure, :raise_on_failed_asserts, :loggers
|
8
|
+
|
9
|
+
def self.default
|
10
|
+
new.instance_eval {
|
11
|
+
@raise_on_log_failure = false
|
12
|
+
@raise_on_failed_asserts = false
|
13
|
+
@loggers = default_loggers
|
14
|
+
|
15
|
+
self
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_loggers
|
20
|
+
loggers = []
|
21
|
+
|
22
|
+
loggers << DatabaseLogger
|
23
|
+
loggers << RailsLogger
|
24
|
+
|
25
|
+
if defined?(Exceptional)
|
26
|
+
require 'loggers/exceptional_logger'
|
27
|
+
loggers << ExceptionalLogger
|
28
|
+
end
|
29
|
+
|
30
|
+
loggers
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Log
|
4
|
+
module ControllerSupport
|
5
|
+
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
before_filter :set_log_context if respond_to? :before_filter
|
10
|
+
after_filter :clear_log_context if respond_to? :after_filter
|
11
|
+
alias_method_chain :handle_unverified_request, :log
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_log_context
|
15
|
+
context = {}
|
16
|
+
context.merge!(
|
17
|
+
:url => request.url,
|
18
|
+
:user_agent => request.user_agent,
|
19
|
+
:ip => request.ip,
|
20
|
+
:remote_ip => request.remote_ip,
|
21
|
+
:referer => request.referer,
|
22
|
+
:environment => Rails.env.to_s,
|
23
|
+
:session_id => request.session_options[:id],
|
24
|
+
:session => session,
|
25
|
+
:params => params
|
26
|
+
)
|
27
|
+
context.merge!(:current_user => current_user.try(:id)) if respond_to? :current_user
|
28
|
+
context.merge!(more_context) if respond_to? :more_context
|
29
|
+
Log.clear_context
|
30
|
+
Log.context(context)
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear_log_context
|
34
|
+
Log.clear_context
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_unverified_request_with_log
|
38
|
+
Log.warn("Can't verify CSRF token authenticity")
|
39
|
+
handle_unverified_request_without_log
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'log/loggable_error'
|
2
|
+
|
3
|
+
module Log
|
4
|
+
class Dispatcher
|
5
|
+
attr_accessor :raise_on_log_failure
|
6
|
+
|
7
|
+
def self.dispatch(severity, message, context = {}, events = [])
|
8
|
+
Dispatcher.new(Log.config.loggers).dispatch(severity, message, context, events)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(loggers)
|
12
|
+
Dispatcher.verify_loggers(config.loggers)
|
13
|
+
@loggers = loggers
|
14
|
+
end
|
15
|
+
|
16
|
+
def dispatch(severity, message, context={}, events=[])
|
17
|
+
raise ArgumentError.new("context must be Hash") unless context.kind_of?(Hash)
|
18
|
+
|
19
|
+
failed_loggers = {}
|
20
|
+
metadata = {}
|
21
|
+
@loggers.each do |logger|
|
22
|
+
begin
|
23
|
+
result = logger.log(severity, message, context, events, metadata)
|
24
|
+
metadata.merge!(result) if (result.kind_of?(Hash))
|
25
|
+
rescue => e
|
26
|
+
failed_loggers[logger.name] = e
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
handle_failed_loggers(failed_loggers)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def config
|
36
|
+
Log.config
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.verify_loggers(loggers)
|
40
|
+
raise ArgumentError.new("Dispatcher requires an array of loggers") if (!loggers.kind_of?(Array))
|
41
|
+
|
42
|
+
loggers.each do |logger|
|
43
|
+
raise ArgumentError.new("Logger #{logger.name} doesn't implement self.log") if (!logger.respond_to?(:log))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_failed_loggers(failed_loggers)
|
48
|
+
unless failed_loggers.empty?
|
49
|
+
logger_errors = failed_loggers.map{ |logger_name, logger_error| { logger_name => logger_error.message } }
|
50
|
+
working_loggers = @loggers.reject{ |logger| failed_loggers.keys.include?(logger.name) }
|
51
|
+
Dispatcher.new(working_loggers).dispatch(:error, "Loggers failed", :logger_errors => logger_errors)
|
52
|
+
raise LoggableError.new("Loggers failed", { :logger_errors => logger_errors }) if config.raise_on_log_failure
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
data/lib/log/engine.rb
ADDED
data/lib/log/events.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Log
|
2
|
+
module Events
|
3
|
+
EVENTS_KEY = :log_events
|
4
|
+
|
5
|
+
def self.add(name, data)
|
6
|
+
Thread.current[EVENTS_KEY] = all.push({ :name => name, :data => data })
|
7
|
+
return self
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.reset
|
11
|
+
Thread.current[EVENTS_KEY] = []
|
12
|
+
return self
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.all
|
16
|
+
(Thread.current[EVENTS_KEY] || []).dup
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/log/log.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'log/config'
|
2
|
+
require 'log/dispatcher'
|
3
|
+
require 'log/events'
|
4
|
+
require 'log/loggable_error'
|
5
|
+
require 'log/utils'
|
6
|
+
|
7
|
+
module Log
|
8
|
+
|
9
|
+
SEVERITIES = {
|
10
|
+
:debug => 0,
|
11
|
+
:info => 1,
|
12
|
+
:warn => 2,
|
13
|
+
:error => 3,
|
14
|
+
:fatal => 4
|
15
|
+
}
|
16
|
+
|
17
|
+
def self.config
|
18
|
+
@config ||= Log::Config.default
|
19
|
+
end
|
20
|
+
|
21
|
+
# This generates the following interface for each severity:
|
22
|
+
# log.{severity}(message, context=nil)
|
23
|
+
# i.e. log.info("hello", {:subsystem => :user, :action => "user creation"})
|
24
|
+
SEVERITIES.each_key do |severity|
|
25
|
+
(class << self; self; end).send(:define_method, severity.to_s) do |*args|
|
26
|
+
message, ctx = *args
|
27
|
+
ctx = context.merge(ctx)
|
28
|
+
self.write(severity, message, ctx)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.assert(condition, message, context = {}, severity=:error, raise_error=nil)
|
33
|
+
if condition
|
34
|
+
return true
|
35
|
+
else
|
36
|
+
assert_failed(severity, message, context, raise_error)
|
37
|
+
return false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.event(name, data={})
|
42
|
+
Events.add(name, data)
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.clear_events
|
46
|
+
Events.reset
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.context(hash = {})
|
50
|
+
Thread.current[:log_context] ||= {}
|
51
|
+
Thread.current[:log_context].merge!(hash)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.clear_context
|
55
|
+
Thread.current[:log_context] = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def self.write(severity, message, context)
|
61
|
+
context = {} if context.nil? # This is different from setting a default. If a user passes nil, it'll be converted to {}.
|
62
|
+
context = handle_loggable_error(message, context)
|
63
|
+
Dispatcher.dispatch(severity, message, context, Events.all)
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.handle_loggable_error(message, context)
|
67
|
+
if message.kind_of?(LoggableError)
|
68
|
+
context.merge(:error_data => message.data)
|
69
|
+
else
|
70
|
+
context
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.assert_failed(severity, message, context, raise_error)
|
75
|
+
write(severity, message, context)
|
76
|
+
return if raise_error == false #specific if raise_error is false but config is on
|
77
|
+
raise LoggableError.new(message, context) if (raise_error || config.raise_on_failed_asserts)
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
data/lib/log/utils.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Log
|
2
|
+
class Utils
|
3
|
+
def self.pretty_print_exception(exception)
|
4
|
+
raise ArgumentError.new("must pass exception") if (!exception.kind_of? Exception)
|
5
|
+
|
6
|
+
# Code taken from C:\Ruby192\lib\ruby\gems\1.9.1\gems\actionpack-3.0.3\lib\action_dispatch\middleware\show_exceptions.rb
|
7
|
+
message = "\n#{exception.class} (#{exception.message}):\n"
|
8
|
+
message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
|
9
|
+
message << " " << (defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) && exception.backtrace ?
|
10
|
+
Rails.backtrace_cleaner.clean(exception.backtrace) :
|
11
|
+
exception.backtrace).try(:join, "\n ").to_s
|
12
|
+
|
13
|
+
return message
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.prepare_message(message, simple=false)
|
17
|
+
if message.kind_of? Exception
|
18
|
+
if simple
|
19
|
+
return message.message
|
20
|
+
else
|
21
|
+
return pretty_print_exception(message)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
return message.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/log/version.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'log/utils'
|
2
|
+
|
3
|
+
class DatabaseLogger
|
4
|
+
def self.log(severity, message, context, events, metadata)
|
5
|
+
log = nil
|
6
|
+
context = context.merge({ :metadata => metadata }) unless metadata.empty?
|
7
|
+
message = Log::Utils.prepare_message(message, true)
|
8
|
+
# In order to prevent logs from being deleted by a rollback, we need to create a new database connection (a new transaction doesn't work).
|
9
|
+
# The only way to create a new database connection is to open a new thread.
|
10
|
+
# Connection must be manually closed at the end of the thread. This only closes the thread's connection, not the main one.
|
11
|
+
Thread.new do
|
12
|
+
begin
|
13
|
+
log = create_log(severity, message, context, events)
|
14
|
+
ensure
|
15
|
+
ActiveRecord::Base.connection.close # must be done manually on new threads
|
16
|
+
end
|
17
|
+
end.join # we need the result of this operation before moving on.
|
18
|
+
return { :db_log_id => log.id }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def self.create_log(severity, message, context, events)
|
24
|
+
Log::DbLog.create!(:severity => severity.to_s,
|
25
|
+
:message => message,
|
26
|
+
:context => context)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'log/log'
|
2
|
+
require 'exceptional'
|
3
|
+
|
4
|
+
class ExceptionalLogger
|
5
|
+
class HandleMessageError < StandardError; end
|
6
|
+
|
7
|
+
def self.log(severity, message, context, events, metadata)
|
8
|
+
context = context.merge(:severity => severity.to_s, :events => events, :metadata => metadata)
|
9
|
+
if message.kind_of?(Exception)
|
10
|
+
Exceptional.context(context)
|
11
|
+
Exceptional.handle(message)
|
12
|
+
Exceptional.clear!
|
13
|
+
elsif Log::SEVERITIES[severity] >= Log::SEVERITIES[:error]
|
14
|
+
Exceptional.rescue(message, context) do
|
15
|
+
raise HandleMessageError.new(message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'log/utils'
|
2
|
+
|
3
|
+
class RailsLogger
|
4
|
+
|
5
|
+
NEW_LINE = "\r\n"
|
6
|
+
|
7
|
+
def self.log(severity, message, context, events, metadata)
|
8
|
+
text = build_log_text(severity, message, context, events, metadata)
|
9
|
+
|
10
|
+
logger = ::Rails.logger
|
11
|
+
begin
|
12
|
+
logger.send(severity.to_s, text)
|
13
|
+
rescue
|
14
|
+
logger.info(text) # chosen severity might not exist for file logger
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def self.build_log_text(severity, message, context, events, metadata)
|
21
|
+
lines = []
|
22
|
+
lines.push("======= #{severity.to_s.capitalize} =======")
|
23
|
+
lines.push("## Message: #{Log::Utils.prepare_message(message)}")
|
24
|
+
lines.push("##")
|
25
|
+
|
26
|
+
lines.push("## Events:")
|
27
|
+
events.each do |event|
|
28
|
+
lines.push("## #{event[:name]}: #{event[:data]}")
|
29
|
+
end
|
30
|
+
lines.push("##")
|
31
|
+
|
32
|
+
lines.push("## Context:")
|
33
|
+
context.each do |item|
|
34
|
+
lines.push("## #{item[0]}: #{item[1].inspect}")
|
35
|
+
end
|
36
|
+
lines.push("##")
|
37
|
+
|
38
|
+
lines.push("## Metadata:")
|
39
|
+
metadata.each do |item|
|
40
|
+
lines.push("## #{item[0]}: #{item[1].inspect}")
|
41
|
+
end
|
42
|
+
|
43
|
+
lines.push("========#{'=' * severity.to_s.length}========")
|
44
|
+
|
45
|
+
text = "#{NEW_LINE*2}"
|
46
|
+
text += "#{lines.join(NEW_LINE)}"
|
47
|
+
text += "#{NEW_LINE*2}"
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Log
|
4
|
+
describe Dispatcher do
|
5
|
+
class Logger1
|
6
|
+
def self.meta_data
|
7
|
+
{ :some => "Hash" }
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.log(severity, message, context_data, events, metadata)
|
11
|
+
meta_data
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Logger2
|
16
|
+
def self.log(severity, message, context_data, events, metadata)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class BadLogger
|
21
|
+
def self.error_message
|
22
|
+
"Bad logger is bad"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.log(severity, message, context_data, events, metadata)
|
26
|
+
raise error_message
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "dispatch" do
|
31
|
+
|
32
|
+
before do
|
33
|
+
Log.config.loggers = [Logger1, Logger2]
|
34
|
+
end
|
35
|
+
|
36
|
+
let (:severity) { :error }
|
37
|
+
let (:message) { "test message" }
|
38
|
+
let (:context_data) { { :a => 2 } }
|
39
|
+
let (:events) { [{ :name => "bla", :data => { :b => 3 } }] }
|
40
|
+
|
41
|
+
def dispatch
|
42
|
+
Dispatcher.dispatch(severity, message, context_data, events)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "calls each logger's log method in order" do
|
46
|
+
Logger1.should_receive(:log).ordered
|
47
|
+
Logger2.should_receive(:log).ordered
|
48
|
+
dispatch
|
49
|
+
end
|
50
|
+
|
51
|
+
it "passes the severity to the logger" do
|
52
|
+
Logger1.should_receive(:log).with(severity, anything, anything, anything, anything)
|
53
|
+
dispatch
|
54
|
+
end
|
55
|
+
|
56
|
+
it "passes the message to the logger" do
|
57
|
+
Logger1.should_receive(:log).with(anything, message, anything, anything, anything)
|
58
|
+
dispatch
|
59
|
+
end
|
60
|
+
|
61
|
+
it "passes the context to the logger" do
|
62
|
+
Logger1.should_receive(:log).with(anything, anything, context_data, anything, anything)
|
63
|
+
dispatch
|
64
|
+
end
|
65
|
+
|
66
|
+
it "passes the events to the logger" do
|
67
|
+
Logger1.should_receive(:log).with(anything, anything, anything, events, anything)
|
68
|
+
dispatch
|
69
|
+
end
|
70
|
+
|
71
|
+
it "passes the metadata returned from previous loggers" do
|
72
|
+
Logger2.should_receive(:log).with(anything, anything, anything, anything, Logger1.meta_data)
|
73
|
+
dispatch
|
74
|
+
end
|
75
|
+
|
76
|
+
context "when logger failed" do
|
77
|
+
|
78
|
+
before do
|
79
|
+
Log.config.loggers << BadLogger
|
80
|
+
end
|
81
|
+
|
82
|
+
it "logs an error using the loggers that worked" do
|
83
|
+
Logger1.should_receive(:log).twice
|
84
|
+
Logger2.should_receive(:log).twice
|
85
|
+
BadLogger.should_receive(:log).once.and_raise("log error")
|
86
|
+
dispatch
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when set to raise error on log failure" do
|
90
|
+
before do
|
91
|
+
Log.config.raise_on_log_failure = true
|
92
|
+
end
|
93
|
+
|
94
|
+
it "raises an error" do
|
95
|
+
BadLogger.should_receive(:log).and_raise("log error")
|
96
|
+
expect {
|
97
|
+
dispatch
|
98
|
+
}.to raise_error
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Log::Events do
|
4
|
+
let(:test_name) { "Test name" }
|
5
|
+
let(:test_data) { { :a => 5 } }
|
6
|
+
|
7
|
+
it "adds new event to the events array" do
|
8
|
+
subject.add(test_name, test_data)
|
9
|
+
subject.all.last.should == { :name => test_name, :data => test_data }
|
10
|
+
end
|
11
|
+
|
12
|
+
it "clears the events array" do
|
13
|
+
subject.add(test_name, test_data)
|
14
|
+
subject.reset
|
15
|
+
subject.all.should == []
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns the whole events array" do
|
19
|
+
subject.add(test_name, test_data)
|
20
|
+
subject.add(test_name, test_data)
|
21
|
+
subject.add(test_name, test_data)
|
22
|
+
subject.all.count.should == 3
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Log do
|
4
|
+
subject { Log }
|
5
|
+
|
6
|
+
let(:severity) { :error }
|
7
|
+
let(:test_message) { "Test message" }
|
8
|
+
let(:test_context) { { :foo => 2, :bar => "hello" } }
|
9
|
+
|
10
|
+
let(:event_name) { "Event name" }
|
11
|
+
let(:event_data) { { :ev_data => 5 } }
|
12
|
+
|
13
|
+
let(:error_context) { { :error => 10 } }
|
14
|
+
let(:error_message) { Log::LoggableError.new("Loggable Error Message", error_context) }
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
subject.clear_events
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "log.writes" do
|
21
|
+
shared_examples_for "log severity" do |severity|
|
22
|
+
context "when message is a LoggableError" do
|
23
|
+
it "adds the log data to the context" do
|
24
|
+
new_context = subject.send(:handle_loggable_error, error_message, test_context)
|
25
|
+
new_context.should == test_context.merge(:error_data => error_context)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "adds the error's data to the context" do
|
29
|
+
Log::Dispatcher.should_receive(:dispatch).with(severity, error_message, test_context.merge(:error_data => error_context), Log::Events.all)
|
30
|
+
subject.send(severity, error_message, test_context)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "dispatches the log data to the loggers" do
|
35
|
+
Log::Dispatcher.should_receive(:dispatch).with(severity, test_message, test_context, Log::Events.all)
|
36
|
+
subject.send(severity, test_message, test_context)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Log::SEVERITIES.each_key do |severity|
|
41
|
+
describe severity do
|
42
|
+
it_behaves_like "log severity", severity
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "log.asserts" do
|
48
|
+
|
49
|
+
describe "assert" do
|
50
|
+
|
51
|
+
context "when condition is true" do
|
52
|
+
it "returns true" do
|
53
|
+
subject.assert(true, test_message, test_context).should == true
|
54
|
+
end
|
55
|
+
|
56
|
+
it "doesn't fail" do
|
57
|
+
subject.should_not_receive(:assert_failed)
|
58
|
+
subject.assert(true, test_message, test_context)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when condition is false" do
|
63
|
+
it "fails" do
|
64
|
+
subject.should_receive(:assert_failed)
|
65
|
+
subject.assert(false, test_message, test_context)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns false" do
|
69
|
+
subject.stub(:assert_failed)
|
70
|
+
subject.assert(false, test_message, test_context).should == false
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "assert_failed" do
|
76
|
+
|
77
|
+
before do
|
78
|
+
Log.config.raise_on_log_failure = false
|
79
|
+
end
|
80
|
+
|
81
|
+
it "writes to log" do
|
82
|
+
subject.should_receive(:write).with(severity, test_message, test_context)
|
83
|
+
subject.send(:assert_failed, severity, test_message, test_context, false)
|
84
|
+
end
|
85
|
+
|
86
|
+
def should_raise_an_error(raise_error_flag)
|
87
|
+
expect {
|
88
|
+
subject.send(:assert_failed, severity, test_message, test_context, raise_error_flag)
|
89
|
+
}.to raise_error(Log::LoggableError)
|
90
|
+
end
|
91
|
+
|
92
|
+
def should_not_raise_an_error(raise_error_flag)
|
93
|
+
expect {
|
94
|
+
subject.send(:assert_failed, severity, test_message, test_context, raise_error_flag)
|
95
|
+
}.not_to raise_error
|
96
|
+
end
|
97
|
+
|
98
|
+
context "config is false" do
|
99
|
+
|
100
|
+
before do
|
101
|
+
subject.config.raise_on_failed_asserts = false
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should raise error when flag is true" do
|
105
|
+
should_raise_an_error(true)
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should not raise error when flag is nil" do
|
109
|
+
should_not_raise_an_error(nil)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should not raise error when flag is false" do
|
113
|
+
should_not_raise_an_error(false)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "config is true" do
|
118
|
+
|
119
|
+
before do
|
120
|
+
subject.config.raise_on_failed_asserts = true
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should raise error when flag is true" do
|
124
|
+
should_raise_an_error(true)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should raise error when flag is nil" do
|
128
|
+
should_raise_an_error(nil)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should not raise error when flag is false" do
|
132
|
+
should_not_raise_an_error(false)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "write events" do
|
140
|
+
describe "event" do
|
141
|
+
it "adds an event to the events array" do
|
142
|
+
Log::Events.should_receive(:add).with(event_name, event_data)
|
143
|
+
subject.event(event_name, event_data)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "clear_events" do
|
148
|
+
it "resets the events array" do
|
149
|
+
Log::Events.should_receive(:reset)
|
150
|
+
subject.clear_events
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
#require 'lib/log/loggable_error'
|
3
|
+
|
4
|
+
describe Log::LoggableError do
|
5
|
+
subject { Log::LoggableError }
|
6
|
+
|
7
|
+
let (:test_message) { "test message" }
|
8
|
+
let (:test_data) { { :a => 2 } }
|
9
|
+
|
10
|
+
it "contains the error data" do
|
11
|
+
error = subject.new(test_message, test_data)
|
12
|
+
error.data.should == test_data
|
13
|
+
end
|
14
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: batlog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 9
|
9
|
+
version: "0.9"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Asaf Gartner
|
13
|
+
- Yonatan Bergman
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-10-22 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rails
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
type: :development
|
61
|
+
version_requirements: *id003
|
62
|
+
description:
|
63
|
+
email: agartner@ebay.com
|
64
|
+
executables: []
|
65
|
+
|
66
|
+
extensions: []
|
67
|
+
|
68
|
+
extra_rdoc_files: []
|
69
|
+
|
70
|
+
files:
|
71
|
+
- .gemspec
|
72
|
+
- .gitignore
|
73
|
+
- .rspec
|
74
|
+
- .rvmrc
|
75
|
+
- .travis.yml
|
76
|
+
- Gemfile
|
77
|
+
- Gemfile.lock
|
78
|
+
- README.md
|
79
|
+
- Rakefile
|
80
|
+
- app/models/log/db_log.rb
|
81
|
+
- lib/batlog.rb
|
82
|
+
- lib/log/config.rb
|
83
|
+
- lib/log/controller_support.rb
|
84
|
+
- lib/log/dispatcher.rb
|
85
|
+
- lib/log/engine.rb
|
86
|
+
- lib/log/events.rb
|
87
|
+
- lib/log/log.rb
|
88
|
+
- lib/log/loggable_error.rb
|
89
|
+
- lib/log/utils.rb
|
90
|
+
- lib/log/version.rb
|
91
|
+
- lib/loggers/database_logger.rb
|
92
|
+
- lib/loggers/exceptional_logger.rb
|
93
|
+
- lib/loggers/rails_logger.rb
|
94
|
+
- spec/lib/dispatcher_spec.rb
|
95
|
+
- spec/lib/events_spec.rb
|
96
|
+
- spec/lib/log_spec.rb
|
97
|
+
- spec/lib/loggable_error_spec.rb
|
98
|
+
- spec/spec_helper.rb
|
99
|
+
homepage: https://github.com/TheGiftsProject/batlog
|
100
|
+
licenses: []
|
101
|
+
|
102
|
+
post_install_message: Run 'rails generate db_log' and then migrate to start using the log with database backend
|
103
|
+
rdoc_options: []
|
104
|
+
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
hash: 3
|
113
|
+
segments:
|
114
|
+
- 0
|
115
|
+
version: "0"
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ">="
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
hash: 3
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
version: "0"
|
125
|
+
requirements: []
|
126
|
+
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 1.8.10
|
129
|
+
signing_key:
|
130
|
+
specification_version: 3
|
131
|
+
summary: A structured logging system
|
132
|
+
test_files:
|
133
|
+
- spec/lib/dispatcher_spec.rb
|
134
|
+
- spec/lib/events_spec.rb
|
135
|
+
- spec/lib/log_spec.rb
|
136
|
+
- spec/lib/loggable_error_spec.rb
|