transaction_logger 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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