batlog 0.9
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/.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
|
+

|
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
|