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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9dcfe3a7f8cbdde324e95e0a246a411907faf9f5
4
- data.tar.gz: c7747c2a99b7705e3689b317276969e41e902b3f
3
+ metadata.gz: 9b596362707ab73c1c50f5acbabccf71f550bdeb
4
+ data.tar.gz: 8ae2171ebd12b0af053aa4c0c71cd06a25e7537d
5
5
  SHA512:
6
- metadata.gz: 81fb304e54412d6ed784684b14b64145ee3796b0fe841fb4b41485cd8790068c9b703cc07fe89da23b629f935f3b6cf1d75797544b3bd81a73f9cef49c79f64f
7
- data.tar.gz: 47e00a81952e0633aa9974bea50b7cf9c79f6c72d8899baa315be4330615a43c9d9210409ebedf31d7d89838b46bdb1ff058cc73d8387b0415d0c5935c068ca1
6
+ metadata.gz: 9b61478fed4c1980b615c49ea2f1d4b8678bc3b9942878ab68b0895fd502dd4fddbcb819026512fa0ce3596ef0baea25a3088d4d03b5997fdb97d7c15d975a47
7
+ data.tar.gz: 7a51811366be8ad002c624130fe68de5e759edf9a8bbb696e5ca605be857be692602a6f7f5c1e0e1c084c5121c404d023f90c5f9cf312236c67551f9ad34eef7
data/.gitignore CHANGED
@@ -7,6 +7,7 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ test.rb
10
11
  *.bundle
11
12
  *.so
12
13
  *.o
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
- [ ![Codeship Status for blinkist/transaction_logger](https://codeship.com/projects/fb9745c0-edc7-0132-b6b1-1efd3f886df2/status?branch=master)](https://codeship.com/projects/84119) [![Code Climate](https://codeclimate.com/github/blinkist/transaction_logger/badges/gpa.svg)](https://codeclimate.com/github/blinkist/transaction_logger) [![Dependency Status](https://www.versioneye.com/ruby/transaction_logger/badge.svg)](https://www.versioneye.com/ruby/transaction_logger/)
2
+ [ ![Codeship Status for blinkist/transaction_logger](https://codeship.com/projects/fb9745c0-edc7-0132-b6b1-1efd3f886df2/status?branch=master)](https://codeship.com/projects/84119) [![Gem Version](https://badge.fury.io/rb/transaction_logger.svg)](http://badge.fury.io/rb/transaction_logger) [![Code Climate](https://codeclimate.com/github/blinkist/transaction_logger/badges/gpa.svg)](https://codeclimate.com/github/blinkist/transaction_logger) [![Dependency Status](https://www.versioneye.com/ruby/transaction_logger/badge.svg)](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
- Given a root transaction, the TransactionLogger is expected to print out every log that occurred under this root transaction, and each sub-transaction's local information.
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
- ## Usage
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
- Wrap a business transaction method with a TransactionLogger lambda:
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
- def some_method
54
- TransactionLogger.start -> (t) do
55
- # your code.
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
- From within this lambda, you may call upon t to add a custom name, context and log your messages, like so:
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
- t.name = "YourClass.some_method"
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
- # Sets output to new instance of Logger
74
- TransactionLogger.logger
100
+ logger = Logger.new STDOUT
75
101
 
76
- # Sets the prefix of each hash key in the log to "transaction_"
77
- # eg. An error message has key: "transaction_error_message"
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.start -> (t) do
87
- t.name = "ExampleClass.some_method"
88
- t.context = { some_id: 12 }
110
+ def some_method(result)
111
+ include TransactionLogger
89
112
 
90
- t.log "Trying something complex"
91
- raise RuntimeError, "Error"
113
+ logger.info "Trying something complex"
114
+ raise RuntimeError, "Error"
92
115
 
93
- result
94
- t.log "Success"
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.some_method",
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
- "example.rb:84:in `block in nested_method'",
116
- ".../TransactionLogger_Example/transaction_logger.rb:26:in `call'",
117
- ".../TransactionLogger_Example/transaction_logger.rb:26:in `run'",
118
- ".../TransactionLogger_Example/transaction_logger.rb:111:in `start'",
119
- "example.rb:79:in `nested_method'"
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,2 +1,8 @@
1
1
  require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require "rubocop/rake_task"
2
4
 
5
+ RuboCop::RakeTask.new
6
+ RSpec::Core::RakeTask.new :spec
7
+
8
+ task default: [:rubocop, :spec]
@@ -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
- class TransactionLogger::Transaction
3
- attr_reader :parent
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
- attr_accessor :name
6
- attr_accessor :context
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
- # @param parent [TransactionLogger::Transaction] Parent of transaction
9
- # @param lmbda [Proc] The surrounded block
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
- @lmbda = lmbda
50
+ failure error, self
51
+ else
52
+ success
53
+ end
16
54
 
17
- @name = "undefined"
18
- @context = {}
19
- @log_queue = Array.new
20
- @start = Time.now
21
- @error_printed = nil
22
- end
55
+ result
56
+ end
23
57
 
24
- # @private
25
- # Runs the lines of code from within the lambda. FOR INTERNAL USE ONLY.
26
- #
27
- def run
28
- begin
29
- result = @lmbda.call self
30
- rescue => error
31
-
32
- e_message_key = "#{TransactionLogger.log_prefix}error_message"
33
- e_class_key = "#{TransactionLogger.log_prefix}error_class"
34
- e_backtrace_key = "#{TransactionLogger.log_prefix}error_backtrace"
35
-
36
- log({
37
- e_message_key => error.message,
38
- e_class_key => error.class.name,
39
- e_backtrace_key => error.backtrace.take(5)
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
- result
48
- end
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
- # Pushes a message into the log queue. Logs are stored in order
51
- # of time logged. Note that this method will not output a log, but just
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
- # @private
69
- # Logs the error and raises error to the parent process
70
- def failure(error, transaction)
71
- calc_duration
72
-
73
- if @parent
74
- @parent.failure error, transaction
75
- else
76
- unless @error_printed
77
- print_transactions
78
- @error_printed = true
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
- raise error
119
+ output
82
120
  end
83
- end
84
121
 
85
- # @private
86
- # Converts a Transaction and it's children into a single nested hash
87
- def to_hash
88
- name_key = "#{TransactionLogger.log_prefix}name"
89
- context_key = "#{TransactionLogger.log_prefix}context"
90
- duration_key = "#{TransactionLogger.log_prefix}duration"
91
- history_key = "#{TransactionLogger.log_prefix}history"
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
- output[history_key] << entry
131
+ @level_threshold_broken = true if input_level_id >= 3
107
132
  end
108
- }
133
+ end
109
134
 
110
- output
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
- private
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
- # Calculates the duration upon the success of a transaction
117
- def success
118
- calc_duration
119
- end
150
+ # unless @parent || @error_printed || !@level_threshold_broken
151
+ # end
152
+ end
120
153
 
121
- # Calculates the number of milliseconds that the Transaction has taken
122
- def calc_duration
123
- @duration = (Time.now - @start) * 1000.0
124
- end
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
- # Sends the transaction context and log to an instance of logger
127
- def print_transactions(transaction=nil)
128
- TransactionLogger.logger.error to_hash
129
- end
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
- class TransactionLogger
2
- VERSION = "0.1.0"
1
+ module TransactionLogger
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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
- class TransactionLogger
5
+ module TransactionLogger
5
6
 
6
- @@prefix = ""
7
- @@current_transactions = {}
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
- # Marks the beginning of a "Transaction lambda," which will log an error if the
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
- # Whatever the outer method is, if a value is returned within the Transaction
15
- # lambda it will be returned to the outer method as well.
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
- # The start method does not catch errors, so if an error is raised, it will simply
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
- # This also checks which thread is envoking the method in order to make sure the
21
- # logs are thread-safe.
21
+ def self.log_prefix=(prefix)
22
+ @prefix = "#{prefix}"
23
+ end
24
+
25
+ # @private
26
+ # Returns the log_prefix
22
27
  #
23
- # @param lmbda [Proc]
28
+ # @return [String] The currently stored prefix.
24
29
  #
25
- def self.start(lmbda)
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
- def self.logger=(logger)
48
- @@logger = logger
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
- @@logger ||= Logger.new(STDOUT)
45
+ @logger ||= Logger.new(STDOUT)
55
46
  end
56
47
 
57
- # Sets the hash keys on the TransactionLogger's log to have a prefix.
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 prefix [#to_s] Any String or Object that responds to to_s
50
+ # @param level [Symbol] A symbol recognized by logger, such as :warn
63
51
  #
64
- def self.log_prefix=(prefix)
65
- @@prefix = "#{prefix}"
52
+ class << self
53
+ attr_writer :level_threshold
66
54
  end
67
55
 
68
- # @private
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
- # @private
78
- # Returns the current parent of a thread of Transactions.
79
- #
80
- # @return [TransactionLogger::Transaction] The current parent given a Thread
81
- #
82
- def self.get_active_transaction
83
- if @@current_transactions.has_key?(Thread.current.object_id)
84
- @@current_transactions[Thread.current.object_id]
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
- # @private
89
- # Sets the current parent of a thread of Transactions.
90
- #
91
- def self.set_active_transaction(transaction)
92
- @@current_transactions[Thread.current.object_id] = transaction
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
- -> (t) do
6
+ lambda do |_t|
8
7
  end
9
8
  }
10
9
 
11
- subject { TransactionLogger::Transaction.new nil, test_lmbda }
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 -> (t) do end
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) { child = TransactionLogger::Transaction.new test_parent, test_lmbda }
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(:parent => test_parent)
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 -> (t) do
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
- -> (t) do
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
- -> (t) do
138
- fail RuntimeError, "test error"
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
@@ -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 = %q{Contextual Business Transaction Logger for Ruby}
12
- spec.description = %q{A logger that silently collects information in the
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: 0.1.0
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-10 00:00:00.000000000 Z
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
- description: "A logger that silently collects information in the\n background
113
- and when an error is raised, logs a hash either\n out to
114
- the System or pushes the log to a service such as\n Loggly.
115
- The log hash contains information such as the\n backtrace,
116
- any logs from calling classes and methods, \n and configurable
117
- contextual information."
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