transaction_logger 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +63 -0
- data/README.md +74 -31
- data/Rakefile +6 -0
- data/lib/transaction_logger/transaction.rb +141 -106
- data/lib/transaction_logger/transaction_manager.rb +64 -0
- data/lib/transaction_logger/version.rb +2 -2
- data/lib/transaction_logger.rb +100 -62
- data/spec/transaction_logger_spec.rb +26 -157
- data/transaction_logger.gemspec +5 -4
- metadata +25 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9b596362707ab73c1c50f5acbabccf71f550bdeb
|
4
|
+
data.tar.gz: 8ae2171ebd12b0af053aa4c0c71cd06a25e7537d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b61478fed4c1980b615c49ea2f1d4b8678bc3b9942878ab68b0895fd502dd4fddbcb819026512fa0ce3596ef0baea25a3088d4d03b5997fdb97d7c15d975a47
|
7
|
+
data.tar.gz: 7a51811366be8ad002c624130fe68de5e759edf9a8bbb696e5ca605be857be692602a6f7f5c1e0e1c084c5121c404d023f90c5f9cf312236c67551f9ad34eef7
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
Lint/ParenthesesAsGroupedExpression:
|
2
|
+
Enabled: true
|
3
|
+
Exclude:
|
4
|
+
- 'spec/**/*'
|
5
|
+
|
6
|
+
Style/BlockDelimiters:
|
7
|
+
Enabled: true
|
8
|
+
Exclude:
|
9
|
+
- 'spec/**/*'
|
10
|
+
|
11
|
+
Style/Documentation:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Style/ClassVars:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Style/EmptyLinesAroundClassBody:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Style/EmptyLinesAroundModuleBody:
|
21
|
+
Enabled: false
|
22
|
+
|
23
|
+
Style/GuardClause:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/PredicateName:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Style/SignalException:
|
30
|
+
Enabled: false
|
31
|
+
|
32
|
+
Style/SpaceAroundEqualsInParameterDefault:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
Style/StringLiterals:
|
36
|
+
Enabled: false
|
37
|
+
|
38
|
+
# Offense count: 9
|
39
|
+
Metrics/AbcSize:
|
40
|
+
Enabled: false
|
41
|
+
|
42
|
+
# Offense count: 1
|
43
|
+
# Configuration parameters: CountComments.
|
44
|
+
Metrics/ClassLength:
|
45
|
+
Enabled: false
|
46
|
+
|
47
|
+
# Offense count: 2
|
48
|
+
Metrics/CyclomaticComplexity:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
# Offense count: 79
|
52
|
+
# Configuration parameters: AllowURI, URISchemes.
|
53
|
+
Metrics/LineLength:
|
54
|
+
Enabled: false
|
55
|
+
|
56
|
+
# Offense count: 10
|
57
|
+
# Configuration parameters: CountComments.
|
58
|
+
Metrics/MethodLength:
|
59
|
+
Enabled: false
|
60
|
+
|
61
|
+
# Offense count: 2
|
62
|
+
Metrics/PerceivedComplexity:
|
63
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -1,8 +1,17 @@
|
|
1
1
|
# Blinkist TransactionLogger
|
2
|
-
[ ](https://codeship.com/projects/84119) [](https://codeclimate.com/github/blinkist/transaction_logger) [](https://www.versioneye.com/ruby/transaction_logger/)
|
2
|
+
[ ](https://codeship.com/projects/84119) [](http://badge.fury.io/rb/transaction_logger) [](https://codeclimate.com/github/blinkist/transaction_logger) [](https://www.versioneye.com/ruby/transaction_logger/)
|
3
3
|
|
4
4
|
Business Transactions Logger for Ruby that compiles contextual logging information and can send it to a configured logging service such as Logger or Loggly in a nested hash.
|
5
5
|
|
6
|
+
## Table of Contents
|
7
|
+
|
8
|
+
1. [Installation](#installation)
|
9
|
+
2. [Output](#output)
|
10
|
+
3. [Configuration](#configuration)
|
11
|
+
4. [Usage](#usage)
|
12
|
+
5. [Version History](#version-history)
|
13
|
+
6. [Contributing](#contributing)
|
14
|
+
|
6
15
|
## Installation
|
7
16
|
|
8
17
|
Add this line to your application's Gemfile:
|
@@ -21,11 +30,13 @@ Or install it yourself as:
|
|
21
30
|
|
22
31
|
## Output
|
23
32
|
|
24
|
-
|
33
|
+
By registering a method with TransactionLogger, the TransactionLogger is expected to print out every log that occurred under this method, and each nested method's local information as well.
|
25
34
|
|
26
35
|
When a transaction raises an error, it will log the *error message*, *error class*, and *10 lines* of the backtrace by default. This will be logged at the level of the transaction that raised the error.
|
27
36
|
|
28
|
-
|
37
|
+
Additionally, if no errors are raised, but an *error* or *fatal* log is made, then the TransactionLogger will send it's log hash to the configured logger.
|
38
|
+
|
39
|
+
## Configuration
|
29
40
|
|
30
41
|
Configure the logger by calling TransactionLogger.logger, such as with Ruby's Logger:
|
31
42
|
|
@@ -36,10 +47,13 @@ TransactionLogger.logger = logger
|
|
36
47
|
|
37
48
|
Calling Transaction_Logger.logger with no parameter sets the logger to a new instance of Logger as shown above.
|
38
49
|
|
50
|
+
### Configuring the Prefix
|
51
|
+
|
39
52
|
You can add a prefix to every hash key in the log by using the class method log_prefix:
|
40
53
|
|
41
54
|
```ruby
|
42
55
|
TransactionLogger.log_prefix = "transaction_logger_"
|
56
|
+
# output hash:
|
43
57
|
# {
|
44
58
|
# "transaction_logger_name" => "some name"
|
45
59
|
# "transaction_logger_context" => { "user_id" => 1 }
|
@@ -47,22 +61,35 @@ TransactionLogger.log_prefix = "transaction_logger_"
|
|
47
61
|
# }
|
48
62
|
```
|
49
63
|
|
50
|
-
|
64
|
+
### Configuring the Log Level Threshold
|
65
|
+
|
66
|
+
You may also choose at which log level the TransactionLogger sends it's log hash. By default, *error* is the threshold, so that if an *error* or *fatal* log is made, then the TransactionLogger will send a JSON hash to it's configured logger. If you wish to set the threshold to *warn*, you can configure the TransactionLogger to do so:
|
51
67
|
|
52
68
|
```ruby
|
53
|
-
|
54
|
-
|
55
|
-
|
69
|
+
TransactionLogger.level_threshold = :warn
|
70
|
+
```
|
71
|
+
|
72
|
+
## Usage
|
73
|
+
|
74
|
+
To register a method as a transaction, include the TransactionLogger and use *add_transaction_log* after the method definition:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
class YourClass
|
78
|
+
include TransactionLogger
|
79
|
+
|
80
|
+
def some_method
|
81
|
+
logger.info "logged message"
|
82
|
+
# method code
|
56
83
|
end
|
84
|
+
|
85
|
+
add_transaction_log :some_method
|
57
86
|
end
|
58
87
|
```
|
59
88
|
|
60
|
-
|
89
|
+
By default, the transaction will be named *YourClass:some_method*, and have no context. You can easily change this by adding the following symbols to an additional options parameter:
|
61
90
|
|
62
91
|
```ruby
|
63
|
-
|
64
|
-
t.context = { specific: "context: #{value}" }
|
65
|
-
t.log "A message you want logged"
|
92
|
+
add_transaction_log :some_method, {name: "Custom Name", context: {}}
|
66
93
|
```
|
67
94
|
|
68
95
|
### Example
|
@@ -70,30 +97,27 @@ t.log "A message you want logged"
|
|
70
97
|
Initial setup, in a config file:
|
71
98
|
|
72
99
|
```ruby
|
73
|
-
|
74
|
-
TransactionLogger.logger
|
100
|
+
logger = Logger.new STDOUT
|
75
101
|
|
76
|
-
# Sets
|
77
|
-
|
78
|
-
TransactionLogger.log_prefix = "transaction_"
|
102
|
+
# Sets output to the new Logger
|
103
|
+
TransactionLogger.logger = logger
|
79
104
|
```
|
80
105
|
|
81
106
|
Here is a transaction that raises an error:
|
82
107
|
|
83
108
|
```ruby
|
84
109
|
class ExampleClass
|
85
|
-
def some_method
|
86
|
-
TransactionLogger
|
87
|
-
t.name = "ExampleClass.some_method"
|
88
|
-
t.context = { some_id: 12 }
|
110
|
+
def some_method(result)
|
111
|
+
include TransactionLogger
|
89
112
|
|
90
|
-
|
91
|
-
|
113
|
+
logger.info "Trying something complex"
|
114
|
+
raise RuntimeError, "Error"
|
92
115
|
|
93
|
-
|
94
|
-
|
95
|
-
end
|
116
|
+
result
|
117
|
+
logger.info "Success"
|
96
118
|
end
|
119
|
+
|
120
|
+
add_transaction_log :some_method, {context: {some_id: 12}}
|
97
121
|
end
|
98
122
|
```
|
99
123
|
|
@@ -101,7 +125,7 @@ The expected output is:
|
|
101
125
|
|
102
126
|
```json
|
103
127
|
{
|
104
|
-
"name": "ExampleClass
|
128
|
+
"name": "ExampleClass:some_method",
|
105
129
|
"context": {
|
106
130
|
"some_id": 12
|
107
131
|
},
|
@@ -112,17 +136,36 @@ The expected output is:
|
|
112
136
|
"error_message": "Error",
|
113
137
|
"error_class": "RuntimeError",
|
114
138
|
"error_backtrace": [
|
115
|
-
"
|
116
|
-
".../
|
117
|
-
".../
|
118
|
-
".../
|
119
|
-
"
|
139
|
+
"example_class.rb:6:in `some_method'",
|
140
|
+
".../transaction_logger.rb:86:in `call'",
|
141
|
+
".../transaction_logger.rb:86:in `block (2 levels) in add_transaction_log'",
|
142
|
+
".../transaction_logger/transaction.rb:37:in `call'",
|
143
|
+
".../transaction_logger/transaction.rb:37:in `run'",
|
144
|
+
".../transaction_logger/transaction_manager.rb:41:in `start'",
|
145
|
+
".../transaction_logger.rb:78:in `block in add_transaction_log'",
|
146
|
+
"test.rb:4:in `<main>'"
|
120
147
|
]
|
121
148
|
|
122
149
|
}]
|
123
150
|
}
|
124
151
|
```
|
125
152
|
|
153
|
+
## Version History
|
154
|
+
|
155
|
+
### v1.0.0
|
156
|
+
|
157
|
+
- AOP approach that provides a much cleaner, easier implementation of the TransactionLogger
|
158
|
+
- Added default transaction name
|
159
|
+
- Added support for log level threshold
|
160
|
+
|
161
|
+
### v0.1.0
|
162
|
+
|
163
|
+
- Added support for log prefixes
|
164
|
+
|
165
|
+
### v0.0.1
|
166
|
+
|
167
|
+
- initial version
|
168
|
+
|
126
169
|
## Contributing
|
127
170
|
|
128
171
|
1. Fork it ( https://github.com/blinkist/transaction_logger/fork )
|
data/Rakefile
CHANGED
@@ -1,131 +1,166 @@
|
|
1
|
+
require "json"
|
2
|
+
require "logger"
|
3
|
+
|
4
|
+
module TransactionLogger
|
5
|
+
class Transaction
|
6
|
+
attr_reader :parent
|
7
|
+
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :context
|
10
|
+
attr_accessor :level_threshold
|
11
|
+
attr_accessor :level_threshold_broken
|
12
|
+
|
13
|
+
# @param parent [TransactionLogger::Transaction] Parent of transaction
|
14
|
+
# @param lmbda [Proc] The surrounded block
|
15
|
+
#
|
16
|
+
def initialize(options, lmbda)
|
17
|
+
@parent = options[:parent]
|
18
|
+
@parent.log self if @parent
|
19
|
+
|
20
|
+
@lmbda = lmbda
|
21
|
+
|
22
|
+
@log_prefix = options[:prefix]
|
23
|
+
@name = "undefined"
|
24
|
+
@context = {}
|
25
|
+
@level_threshold = options[:level_threshold] || :error
|
26
|
+
@log_queue = []
|
27
|
+
@start = Time.now
|
28
|
+
@error_printed = nil
|
29
|
+
@level_threshold_broken = false
|
30
|
+
|
31
|
+
@logger = options[:logger]
|
32
|
+
end
|
1
33
|
|
2
|
-
|
3
|
-
|
34
|
+
# @private
|
35
|
+
# Runs the lines of code from within the lambda. FOR INTERNAL USE ONLY.
|
36
|
+
#
|
37
|
+
def run
|
38
|
+
begin
|
39
|
+
result = @lmbda.call self
|
40
|
+
rescue => error
|
4
41
|
|
5
|
-
|
6
|
-
|
42
|
+
e_message_key = "#{@log_prefix}error_message"
|
43
|
+
e_class_key = "#{@log_prefix}error_class"
|
44
|
+
e_backtrace_key = "#{@log_prefix}error_backtrace"
|
7
45
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def initialize(parent=nil, lmbda)
|
12
|
-
@parent = parent
|
13
|
-
@parent.log self if @parent
|
46
|
+
log(e_message_key => error.message,
|
47
|
+
e_class_key => error.class.name,
|
48
|
+
e_backtrace_key => error.backtrace.take(10))
|
14
49
|
|
15
|
-
|
50
|
+
failure error, self
|
51
|
+
else
|
52
|
+
success
|
53
|
+
end
|
16
54
|
|
17
|
-
|
18
|
-
|
19
|
-
@log_queue = Array.new
|
20
|
-
@start = Time.now
|
21
|
-
@error_printed = nil
|
22
|
-
end
|
55
|
+
result
|
56
|
+
end
|
23
57
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
failure error, self
|
43
|
-
else
|
44
|
-
success
|
58
|
+
# Pushes a message into the log queue. Logs are stored in order
|
59
|
+
# of time logged. Note that this method will not output a log, but just
|
60
|
+
# stores it in the queue to be outputted if an error is raised in a
|
61
|
+
# transaction.
|
62
|
+
#
|
63
|
+
# @param message [#to_s] Any String or Object that responds to to_s
|
64
|
+
# that you want to be stored in the log queue.
|
65
|
+
#
|
66
|
+
def log(message, level=:info)
|
67
|
+
check_level(level)
|
68
|
+
if message.is_a? String
|
69
|
+
message_key = "#{@log_prefix}#{level}"
|
70
|
+
message = { message_key => message }
|
71
|
+
@log_queue.push message
|
72
|
+
else
|
73
|
+
@log_queue.push message
|
74
|
+
end
|
45
75
|
end
|
46
76
|
|
47
|
-
|
48
|
-
|
77
|
+
# @private
|
78
|
+
# Logs the error and raises error to the parent process
|
79
|
+
def failure(error, transaction)
|
80
|
+
calc_duration
|
81
|
+
|
82
|
+
if @parent
|
83
|
+
@parent.failure error, transaction
|
84
|
+
else
|
85
|
+
unless @error_printed
|
86
|
+
print_transactions
|
87
|
+
@error_printed = true
|
88
|
+
end
|
49
89
|
|
50
|
-
|
51
|
-
|
52
|
-
# stores it in the queue to be outputted if an error is raised in a
|
53
|
-
# transaction.
|
54
|
-
#
|
55
|
-
# @param message [#to_s] Any String or Object that responds to to_s
|
56
|
-
# that you want to be stored in the log queue.
|
57
|
-
#
|
58
|
-
def log(message)
|
59
|
-
if message.is_a? String
|
60
|
-
message_key = "#{TransactionLogger.log_prefix}info"
|
61
|
-
message = { message_key => message }
|
62
|
-
@log_queue.push message
|
63
|
-
else
|
64
|
-
@log_queue.push message
|
90
|
+
raise error
|
91
|
+
end
|
65
92
|
end
|
66
|
-
end
|
67
93
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
94
|
+
# @private
|
95
|
+
# Converts a Transaction and it's children into a single nested hash
|
96
|
+
def to_hash
|
97
|
+
name_key = "#{@log_prefix}name"
|
98
|
+
context_key = "#{@log_prefix}context"
|
99
|
+
duration_key = "#{@log_prefix}duration"
|
100
|
+
history_key = "#{@log_prefix}history"
|
101
|
+
|
102
|
+
output = {
|
103
|
+
name_key => @name,
|
104
|
+
context_key => @context,
|
105
|
+
duration_key => @duration,
|
106
|
+
history_key => []
|
107
|
+
}
|
108
|
+
|
109
|
+
@log_queue.each do|entry|
|
110
|
+
if entry.is_a? self.class
|
111
|
+
output[history_key] << entry.to_hash
|
112
|
+
elsif entry.is_a? Hash
|
113
|
+
output[history_key] << entry
|
114
|
+
else
|
115
|
+
output[history_key] << entry
|
116
|
+
end
|
79
117
|
end
|
80
118
|
|
81
|
-
|
119
|
+
output
|
82
120
|
end
|
83
|
-
end
|
84
121
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
output = {
|
94
|
-
name_key => @name,
|
95
|
-
context_key => @context,
|
96
|
-
duration_key => @duration,
|
97
|
-
history_key => []
|
98
|
-
}
|
99
|
-
|
100
|
-
@log_queue.each {|entry|
|
101
|
-
if entry.is_a? TransactionLogger::Transaction
|
102
|
-
output[history_key] << entry.to_hash
|
103
|
-
elsif entry.is_a? Hash
|
104
|
-
output[history_key] << entry
|
122
|
+
# @private
|
123
|
+
#
|
124
|
+
def check_level(level)
|
125
|
+
levels = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }
|
126
|
+
input_level_id = levels[level]
|
127
|
+
level_threshold_id = levels[@level_threshold]
|
128
|
+
if level_threshold_id
|
129
|
+
@level_threshold_broken = true if input_level_id >= level_threshold_id
|
105
130
|
else
|
106
|
-
|
131
|
+
@level_threshold_broken = true if input_level_id >= 3
|
107
132
|
end
|
108
|
-
|
133
|
+
end
|
109
134
|
|
110
|
-
|
111
|
-
end
|
135
|
+
private
|
112
136
|
|
137
|
+
# Calculates the duration upon the success of a transaction
|
138
|
+
def success
|
139
|
+
calc_duration
|
113
140
|
|
114
|
-
|
141
|
+
unless @error_printed || !@level_threshold_broken
|
142
|
+
if @parent
|
143
|
+
@parent.instance_variable_set :@level_threshold_broken, true
|
144
|
+
else
|
145
|
+
print_transactions
|
146
|
+
@error_printed = true
|
147
|
+
end
|
148
|
+
end
|
115
149
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
end
|
150
|
+
# unless @parent || @error_printed || !@level_threshold_broken
|
151
|
+
# end
|
152
|
+
end
|
120
153
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
154
|
+
# Calculates the number of milliseconds that the Transaction has taken
|
155
|
+
def calc_duration
|
156
|
+
@duration = (Time.now - @start) * 1000.0
|
157
|
+
end
|
125
158
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
159
|
+
# Sends the transaction context and log to an instance of logger
|
160
|
+
def print_transactions
|
161
|
+
@logger ||= Logger.new(STDOUT)
|
162
|
+
@logger.error to_hash
|
163
|
+
end
|
130
164
|
|
165
|
+
end
|
131
166
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "transaction_logger/version"
|
3
|
+
require "transaction_logger/transaction"
|
4
|
+
|
5
|
+
module TransactionLogger
|
6
|
+
class TransactionManager
|
7
|
+
|
8
|
+
@@prefix = ""
|
9
|
+
@@current_transactions = {}
|
10
|
+
|
11
|
+
# Marks the beginning of a "Transaction lambda," which will log an error if the
|
12
|
+
# containing code raises an error. A lambda instance variable let's you call
|
13
|
+
# the .log method and access the ".name" and ".context" variables. The start
|
14
|
+
# method must take a lambda as an argument.
|
15
|
+
#
|
16
|
+
# Whatever the outer method is, if a value is returned within the Transaction
|
17
|
+
# lambda it will be returned to the outer method as well.
|
18
|
+
#
|
19
|
+
# The start method does not catch errors, so if an error is raised, it will simply
|
20
|
+
# envoke a logging message to be outputted and then raise the error.
|
21
|
+
#
|
22
|
+
# This also checks which thread is envoking the method in order to make sure the
|
23
|
+
# logs are thread-safe.
|
24
|
+
#
|
25
|
+
# @param prefix [String]
|
26
|
+
# @param logger [Logger]
|
27
|
+
# @param level_threshold [Symbol]
|
28
|
+
# @param lmbda [Proc]
|
29
|
+
#
|
30
|
+
def self.start(options={}, lmbda)
|
31
|
+
options[:parent] = active_transaction
|
32
|
+
|
33
|
+
transaction = TransactionLogger::Transaction.new options, lmbda
|
34
|
+
self.active_transaction = transaction
|
35
|
+
|
36
|
+
begin
|
37
|
+
transaction.run
|
38
|
+
rescue StandardError => e
|
39
|
+
raise e
|
40
|
+
ensure
|
41
|
+
self.active_transaction = transaction.parent
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @private
|
46
|
+
# Returns the current parent of a thread of Transactions.
|
47
|
+
#
|
48
|
+
# @return [TransactionLogger::Transaction] The current parent given a Thread
|
49
|
+
#
|
50
|
+
def self.active_transaction
|
51
|
+
if @@current_transactions.key?(Thread.current.object_id)
|
52
|
+
@@current_transactions[Thread.current.object_id]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @private
|
57
|
+
# Sets the current parent of a thread of Transactions.
|
58
|
+
#
|
59
|
+
def self.active_transaction=(transaction)
|
60
|
+
@@current_transactions[Thread.current.object_id] = transaction
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = "
|
1
|
+
module TransactionLogger
|
2
|
+
VERSION = "1.0.0"
|
3
3
|
end
|
data/lib/transaction_logger.rb
CHANGED
@@ -1,95 +1,133 @@
|
|
1
1
|
require "transaction_logger/version"
|
2
2
|
require "transaction_logger/transaction"
|
3
|
+
require "transaction_logger/transaction_manager"
|
3
4
|
|
4
|
-
|
5
|
+
module TransactionLogger
|
5
6
|
|
6
|
-
|
7
|
-
|
7
|
+
# @private
|
8
|
+
# Extends ClassMethods of including class to the TransactionLogger
|
9
|
+
#
|
10
|
+
def self.included(base)
|
11
|
+
base.extend(ClassMethods)
|
12
|
+
end
|
8
13
|
|
9
|
-
#
|
10
|
-
# containing code raises an error. A lambda instance variable let's you call
|
11
|
-
# the .log method and access the ".name" and ".context" variables. The start
|
12
|
-
# method must take a lambda as an argument.
|
14
|
+
# Sets the hash keys on the TransactionLogger's log to have a prefix.
|
13
15
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
+
# Using .log_prefix "str_", the output of the log hash will contain keys
|
17
|
+
# prefixed with "str_", such as { "str_name" => "Class.method" }.
|
16
18
|
#
|
17
|
-
#
|
18
|
-
# envoke a logging message to be outputted and then raise the error.
|
19
|
+
# @param prefix [#to_s] Any String or Object that responds to to_s
|
19
20
|
#
|
20
|
-
|
21
|
-
|
21
|
+
def self.log_prefix=(prefix)
|
22
|
+
@prefix = "#{prefix}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# @private
|
26
|
+
# Returns the log_prefix
|
22
27
|
#
|
23
|
-
# @
|
28
|
+
# @return [String] The currently stored prefix.
|
24
29
|
#
|
25
|
-
def self.
|
26
|
-
|
27
|
-
active_transaction = get_active_transaction
|
28
|
-
|
29
|
-
transaction = TransactionLogger::Transaction.new active_transaction, lmbda
|
30
|
-
active_transaction = transaction
|
31
|
-
|
32
|
-
set_active_transaction active_transaction
|
33
|
-
|
34
|
-
begin
|
35
|
-
transaction.run
|
36
|
-
rescue Exception => e
|
37
|
-
raise e
|
38
|
-
ensure
|
39
|
-
set_active_transaction transaction.parent
|
40
|
-
end
|
30
|
+
def self.log_prefix
|
31
|
+
@prefix
|
41
32
|
end
|
42
33
|
|
43
34
|
# Sets the TransactionLogger's output to a specific instance of Logger.
|
44
35
|
#
|
45
36
|
# @param logger [Logger] Any instace of ruby Logger
|
46
37
|
#
|
47
|
-
|
48
|
-
|
38
|
+
class << self
|
39
|
+
attr_writer :logger
|
49
40
|
end
|
50
41
|
|
51
42
|
# Sets the TransactionLogger's output to a new instance of Logger
|
52
43
|
#
|
53
44
|
def self.logger
|
54
|
-
|
45
|
+
@logger ||= Logger.new(STDOUT)
|
55
46
|
end
|
56
47
|
|
57
|
-
# Sets the
|
58
|
-
#
|
59
|
-
# Using .log_prefix "str_", the output of the log hash will contain keys prefixed
|
60
|
-
# with "str_", such as { "str_name" => "Class.method" }.
|
48
|
+
# Sets the TransactionLogger's logger level threshold.
|
61
49
|
#
|
62
|
-
# @param
|
50
|
+
# @param level [Symbol] A symbol recognized by logger, such as :warn
|
63
51
|
#
|
64
|
-
|
65
|
-
|
52
|
+
class << self
|
53
|
+
attr_writer :level_threshold
|
66
54
|
end
|
67
55
|
|
68
|
-
|
69
|
-
# Returns the log_prefix
|
70
|
-
#
|
71
|
-
# @return [String] The currently stored prefix.
|
72
|
-
#
|
73
|
-
def self.log_prefix
|
74
|
-
@@prefix
|
75
|
-
end
|
56
|
+
module ClassMethods
|
76
57
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
58
|
+
# Registers a method with the TransactionLogger, which will begin tracking the
|
59
|
+
# logging within the method and it's nested methods. These logs are collected
|
60
|
+
# under one transaction, and if the level threshold is broken or when an error
|
61
|
+
# is raised, the collected logs are pushed to the configured logger as a JSON
|
62
|
+
# hash.
|
63
|
+
#
|
64
|
+
# By registering multiple methods as transactions, each method becomes it's
|
65
|
+
# own transaction.
|
66
|
+
#
|
67
|
+
# @param method [Symbol] The method you want to register with TransactionLogger
|
68
|
+
# @param options [Hash] Additional options, such as custom transaction name and context
|
69
|
+
#
|
70
|
+
def add_transaction_log(method, options={})
|
71
|
+
old_method = instance_method method
|
72
|
+
|
73
|
+
prefix = Module.nesting.last.instance_variable_get :@prefix
|
74
|
+
logger = Module.nesting.last.instance_variable_get :@logger
|
75
|
+
level_threshold = Module.nesting.last.instance_variable_get :@level_threshold
|
76
|
+
|
77
|
+
options = { prefix: prefix, logger: logger, level_threshold: level_threshold }
|
78
|
+
|
79
|
+
define_method method do
|
80
|
+
TransactionManager.start options, lambda { |transaction|
|
81
|
+
transaction.name = options[:name]
|
82
|
+
transaction.name ||= "#{old_method.bind(self).owner}#{method.inspect}"
|
83
|
+
transaction.context = options[:context]
|
84
|
+
transaction.context ||= {}
|
85
|
+
|
86
|
+
self.class.trap_logger method, transaction
|
87
|
+
old_method.bind(self).call
|
88
|
+
}
|
89
|
+
end
|
85
90
|
end
|
86
|
-
end
|
87
91
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
92
|
+
# @private
|
93
|
+
# Traps the original logger inside the TransactionLogger
|
94
|
+
#
|
95
|
+
# @param method [Symbol]
|
96
|
+
# @param transaction [Transaction]
|
97
|
+
#
|
98
|
+
def trap_logger(_method, transaction)
|
99
|
+
logger_method = instance_method :logger
|
100
|
+
|
101
|
+
define_method :logger do
|
102
|
+
@original_logger ||= logger_method.bind(self).call
|
103
|
+
calling_method = caller_locations(1, 1)[0].label
|
104
|
+
|
105
|
+
@trapped_logger ||= {}
|
106
|
+
@trapped_logger[calling_method] ||= LoggerProxy.new @original_logger, transaction
|
107
|
+
end
|
108
|
+
end
|
93
109
|
end
|
94
110
|
|
111
|
+
class LoggerProxy
|
112
|
+
|
113
|
+
# @private
|
114
|
+
def initialize(original_logger, transaction)
|
115
|
+
@original_logger = original_logger
|
116
|
+
@transaction = transaction
|
117
|
+
end
|
118
|
+
|
119
|
+
levels = %i( debug info warn error fatal )
|
120
|
+
|
121
|
+
levels.each do |level|
|
122
|
+
define_method level do |*args|
|
123
|
+
@original_logger.send level, *args
|
124
|
+
@transaction.log(*args, level)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# @private
|
129
|
+
def method_missing(method, *args)
|
130
|
+
@original_logger.send method, *args
|
131
|
+
end
|
132
|
+
end
|
95
133
|
end
|
@@ -2,13 +2,15 @@ require "spec_helper"
|
|
2
2
|
require "logger"
|
3
3
|
|
4
4
|
describe TransactionLogger do
|
5
|
-
|
6
5
|
let (:test_lmbda) {
|
7
|
-
|
6
|
+
lambda do |_t|
|
8
7
|
end
|
9
8
|
}
|
10
9
|
|
11
|
-
subject {
|
10
|
+
subject {
|
11
|
+
TransactionLogger::Transaction.new(
|
12
|
+
{ prefix: nil, logger: Logger.new(STDOUT), level_threshold: nil }, test_lmbda)
|
13
|
+
}
|
12
14
|
|
13
15
|
it "initializes with a nil parent" do
|
14
16
|
expect(subject.parent).to be_nil
|
@@ -16,79 +18,35 @@ describe TransactionLogger do
|
|
16
18
|
|
17
19
|
it "responds to .start" do
|
18
20
|
expect(described_class).to receive(:start)
|
19
|
-
described_class.start -> (
|
21
|
+
described_class.start -> (_t) do end
|
20
22
|
end
|
21
23
|
|
22
24
|
context "when there is a parent" do
|
23
|
-
|
24
25
|
let (:test_parent) { subject }
|
25
|
-
let (:child) {
|
26
|
+
let (:child) {
|
27
|
+
TransactionLogger::Transaction.new(
|
28
|
+
{ parent: test_parent, prefix: nil, logger: Logger.new(STDOUT), level_threshold: nil }, test_lmbda)
|
29
|
+
}
|
26
30
|
let (:test_parent_log_queue) { test_parent.instance_variable_get(:@log_queue) }
|
27
31
|
|
28
32
|
it "has a parent" do
|
29
|
-
expect(child).to have_attributes(:
|
33
|
+
expect(child).to have_attributes(parent: test_parent)
|
30
34
|
end
|
31
35
|
|
32
36
|
it "places itself into the log of the parent" do
|
33
37
|
expect(test_parent_log_queue).to include(child)
|
34
38
|
end
|
35
|
-
|
36
|
-
context "when there is an error" do
|
37
|
-
|
38
|
-
it "raises the error to the parent" do
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
|
46
|
-
context "when in 3-level nested transactions" do
|
47
|
-
it "raises an error" do
|
48
|
-
expect {
|
49
|
-
described_class.start -> (t) do
|
50
|
-
t.context = {user_id: 01, information: "contextual info"}
|
51
|
-
t.log "First Message"
|
52
|
-
|
53
|
-
described_class.start -> (t2) do
|
54
|
-
t2.context = {user_id: 01, information: "contextual info"}
|
55
|
-
t2.log "Second Message"
|
56
|
-
t2.log "Third Message"
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
t.log "Fourth Message"
|
61
|
-
|
62
|
-
described_class.start -> (t3) do
|
63
|
-
t3.context = {user_id: 01, information: "contextual info"}
|
64
|
-
t3.log "Fifth Message"
|
65
|
-
|
66
|
-
described_class.start -> (t4) do
|
67
|
-
t4.context = {user_id: 01, information: "contextual info"}
|
68
|
-
t4.log "Sixth Message"
|
69
|
-
t4.log "Seventh Message"
|
70
|
-
|
71
|
-
fail RuntimeError, "test error"
|
72
|
-
end
|
73
|
-
|
74
|
-
t3.log "Eighth Message"
|
75
|
-
end
|
76
|
-
|
77
|
-
end
|
78
|
-
}.to raise_error RuntimeError
|
79
|
-
end
|
80
39
|
end
|
81
40
|
|
82
41
|
describe ".start" do
|
83
42
|
let (:test_lmbda) {
|
84
|
-
described_class.start
|
43
|
+
described_class.start lambda (t) do
|
85
44
|
t.log ""
|
86
45
|
end
|
87
46
|
}
|
88
47
|
end
|
89
48
|
|
90
49
|
describe ".log_prefix" do
|
91
|
-
|
92
50
|
context "when there is no prefix" do
|
93
51
|
it "does not change the output" do
|
94
52
|
expect(subject.to_hash).to include("name" => "undefined")
|
@@ -102,6 +60,11 @@ describe TransactionLogger do
|
|
102
60
|
described_class.log_prefix = prefix
|
103
61
|
end
|
104
62
|
|
63
|
+
subject {
|
64
|
+
TransactionLogger::Transaction.new(
|
65
|
+
{ prefix: described_class.log_prefix, logger: Logger.new(STDOUT), level_threshold: nil }, test_lmbda)
|
66
|
+
}
|
67
|
+
|
105
68
|
after :example do
|
106
69
|
described_class.log_prefix = ""
|
107
70
|
end
|
@@ -110,15 +73,12 @@ describe TransactionLogger do
|
|
110
73
|
expect(subject.to_hash).to include("bta_name" => "undefined")
|
111
74
|
end
|
112
75
|
end
|
113
|
-
|
114
76
|
end
|
115
77
|
|
116
78
|
describe "#run" do
|
117
|
-
|
118
79
|
context "when no exception is raised" do
|
119
|
-
|
120
80
|
let (:test_lmbda) {
|
121
|
-
|
81
|
+
lambda do |_t|
|
122
82
|
"result"
|
123
83
|
end
|
124
84
|
}
|
@@ -128,121 +88,30 @@ describe TransactionLogger do
|
|
128
88
|
it "returns lmda" do
|
129
89
|
expect(result).to eq "result"
|
130
90
|
end
|
131
|
-
|
132
91
|
end
|
133
92
|
|
134
93
|
context "when an exception is raised" do
|
135
|
-
|
136
94
|
let (:test_lmbda) {
|
137
|
-
|
138
|
-
fail
|
95
|
+
lambda do |_t|
|
96
|
+
fail "test error"
|
139
97
|
end
|
140
98
|
}
|
141
99
|
|
100
|
+
let (:child) {
|
101
|
+
TransactionLogger::Transaction.new(
|
102
|
+
{ prefix: nil, logger: Logger.new(STDOUT), level_threshold: nil }, test_lmbda)
|
103
|
+
}
|
104
|
+
|
142
105
|
let (:result) { subject.run }
|
143
106
|
|
144
107
|
it "raises an exception" do
|
145
|
-
expect{result}.to raise_error "test error"
|
108
|
+
expect { result }.to raise_error "test error"
|
146
109
|
end
|
147
110
|
|
148
111
|
it "calls failure" do
|
149
112
|
expect(subject).to receive(:failure)
|
150
113
|
result
|
151
114
|
end
|
152
|
-
|
153
115
|
end
|
154
|
-
|
155
116
|
end
|
156
|
-
|
157
|
-
# Try to think of ways to refactor this
|
158
|
-
# given that I can test certain specific behaviors
|
159
|
-
# first, and then use those tests to know the bigger
|
160
|
-
# picture is working...
|
161
|
-
describe "Logger" do
|
162
|
-
|
163
|
-
subject {
|
164
|
-
Logger.new STDOUT
|
165
|
-
}
|
166
|
-
|
167
|
-
before :each do
|
168
|
-
described_class.logger = subject
|
169
|
-
end
|
170
|
-
|
171
|
-
context "when there is one transaction" do
|
172
|
-
|
173
|
-
it "recieves nothing" do
|
174
|
-
expect(subject).to_not receive(:error)
|
175
|
-
|
176
|
-
described_class.start -> (t) do
|
177
|
-
t.context = {user_id: 01, information: "contextual info"}
|
178
|
-
t.log "First Message"
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
it "recieves error if an exception occurs" do
|
183
|
-
expect(subject).to receive(:error)
|
184
|
-
expect {
|
185
|
-
described_class.start -> (t) do
|
186
|
-
t.context = {user_id: 01, information: "contextual info"}
|
187
|
-
t.log "First Message"
|
188
|
-
fail RuntimeError, "test error"
|
189
|
-
end
|
190
|
-
}.to raise_error(RuntimeError)
|
191
|
-
end
|
192
|
-
|
193
|
-
end
|
194
|
-
|
195
|
-
context "when there is a nested transaction with two levels" do
|
196
|
-
|
197
|
-
it "recieves error if an exception occurs" do
|
198
|
-
expect(subject).to receive(:error) do |options|
|
199
|
-
expect(options["history"]).to include( {"info" => "First Message"} )
|
200
|
-
expect(options["history"].last["history"]).to include({"info" => "Second Message"})
|
201
|
-
expect(options["history"].last["history"].last["error_message"]).to eq("test error")
|
202
|
-
expect(options["history"].last["history"].last["error_class"]).to eq("RuntimeError")
|
203
|
-
end
|
204
|
-
|
205
|
-
expect {
|
206
|
-
described_class.start -> (t) do
|
207
|
-
t.context = {user_id: 01, information: "contextual info"}
|
208
|
-
t.log "First Message"
|
209
|
-
described_class.start -> (t2) do
|
210
|
-
t2.log "Second Message"
|
211
|
-
fail RuntimeError, "test error"
|
212
|
-
end
|
213
|
-
end
|
214
|
-
}.to raise_error RuntimeError
|
215
|
-
end
|
216
|
-
|
217
|
-
end
|
218
|
-
|
219
|
-
context "when there are two nested transactions, two-levels deep" do
|
220
|
-
|
221
|
-
it "recieves error if an exception occurs" do
|
222
|
-
|
223
|
-
expect(subject).to receive("error") do |options|
|
224
|
-
expect(options["history"].first["history"]).to include({"info" => "First Message"})
|
225
|
-
expect(options["history"].last["history"]).to include({"info" => "Second Message"})
|
226
|
-
expect(options["history"].last["history"].last["error_message"]).to eq("test error")
|
227
|
-
expect(options["history"].last["history"].last["error_class"]).to eq("RuntimeError")
|
228
|
-
end
|
229
|
-
|
230
|
-
expect {
|
231
|
-
described_class.start -> (t) do
|
232
|
-
t.context = {user_id: 01, information: "contextual info"}
|
233
|
-
described_class.start -> (t2) do
|
234
|
-
t2.log "First Message"
|
235
|
-
end
|
236
|
-
described_class.start -> (t2) do
|
237
|
-
t2.log "Second Message"
|
238
|
-
fail RuntimeError, "test error"
|
239
|
-
end
|
240
|
-
end
|
241
|
-
}.to raise_error RuntimeError
|
242
|
-
end
|
243
|
-
|
244
|
-
end
|
245
|
-
|
246
|
-
end
|
247
|
-
|
248
117
|
end
|
data/transaction_logger.gemspec
CHANGED
@@ -8,13 +8,13 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = TransactionLogger::VERSION
|
9
9
|
spec.authors = ["John Donner", "Sebastian Schleicher"]
|
10
10
|
spec.email = ["johnbdonner@gmail.com", "sebastian.julius@gmail.com"]
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
11
|
+
spec.summary = 'Contextual Business Transaction Logger for Ruby'
|
12
|
+
spec.description = 'A logger that silently collects information in the
|
13
13
|
background and when an error is raised, logs a hash either
|
14
14
|
out to the System or pushes the log to a service such as
|
15
15
|
Loggly. The log hash contains information such as the
|
16
|
-
backtrace, any logs from calling classes and methods,
|
17
|
-
and configurable contextual information.
|
16
|
+
backtrace, any logs from calling classes and methods,
|
17
|
+
and configurable contextual information.'
|
18
18
|
spec.homepage = "https://github.com/blinkist/transaction_logger"
|
19
19
|
spec.license = "MIT"
|
20
20
|
|
@@ -31,4 +31,5 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.add_development_dependency "yard"
|
32
32
|
spec.add_development_dependency "redcarpet"
|
33
33
|
spec.add_development_dependency "github-markdown"
|
34
|
+
spec.add_development_dependency "rubocop"
|
34
35
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transaction_logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Donner
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-07-
|
12
|
+
date: 2015-07-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -109,12 +109,27 @@ dependencies:
|
|
109
109
|
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
111
|
version: '0'
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: rubocop
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: |-
|
127
|
+
A logger that silently collects information in the
|
128
|
+
background and when an error is raised, logs a hash either
|
129
|
+
out to the System or pushes the log to a service such as
|
130
|
+
Loggly. The log hash contains information such as the
|
131
|
+
backtrace, any logs from calling classes and methods,
|
132
|
+
and configurable contextual information.
|
118
133
|
email:
|
119
134
|
- johnbdonner@gmail.com
|
120
135
|
- sebastian.julius@gmail.com
|
@@ -123,6 +138,7 @@ extensions: []
|
|
123
138
|
extra_rdoc_files: []
|
124
139
|
files:
|
125
140
|
- ".gitignore"
|
141
|
+
- ".rubocop.yml"
|
126
142
|
- ".yardopts"
|
127
143
|
- Gemfile
|
128
144
|
- LICENSE
|
@@ -131,6 +147,7 @@ files:
|
|
131
147
|
- Rakefile
|
132
148
|
- lib/transaction_logger.rb
|
133
149
|
- lib/transaction_logger/transaction.rb
|
150
|
+
- lib/transaction_logger/transaction_manager.rb
|
134
151
|
- lib/transaction_logger/version.rb
|
135
152
|
- spec/spec_helper.rb
|
136
153
|
- spec/support/debugging.rb
|