yeti_logger 3.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 +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +18 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +193 -0
- data/Rakefile +12 -0
- data/lib/yeti_logger.rb +164 -0
- data/lib/yeti_logger/configuration.rb +14 -0
- data/lib/yeti_logger/constants.rb +5 -0
- data/lib/yeti_logger/message_formatters.rb +93 -0
- data/lib/yeti_logger/test_helper.rb +65 -0
- data/lib/yeti_logger/version.rb +3 -0
- data/lib/yeti_logger/wrapped_logger.rb +24 -0
- data/spec/lib/yeti_logger/message_formatters_spec.rb +143 -0
- data/spec/lib/yeti_logger/test_helper_spec.rb +140 -0
- data/spec/lib/yeti_logger/wrapped_logger_spec.rb +28 -0
- data/spec/lib/yeti_logger_spec.rb +218 -0
- data/spec/spec_helper.rb +30 -0
- data/yeti_logger.gemspec +28 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3e5e9a8554d3123f83bd27c2cefe1663025fa9b9
|
4
|
+
data.tar.gz: 8f79e414571cbc0c5815eb5ce888892be8114231
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3fa2b8fedb91ee5f6cb0d0dcf41b4219cc16a5c98092c574ee55576d2c1ad9e0ce702c2d6e05c87af6445fbf80016e8f4e9134afc0d210a7554728fc8e99432a
|
7
|
+
data.tar.gz: 61e988b8d18bc89954b49cbd49a6d4d288280bc53312f9507f15e7404ac3e828f0fd433548599005ca49f8136c41caf02c47db2bdafb12f230dc65ae0823ebf4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.2
|
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013-2015 Yesware, Inc
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SaALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
# yeti_logger
|
2
|
+
|
3
|
+
Provides standardized logging across Yesware apps.
|
4
|
+
|
5
|
+
[](https://travis-ci.org/Yesware/yeti_logger)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'yeti_logger'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install yeti_logger
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Initialization
|
24
|
+
|
25
|
+
To use YetiLogger within an app it must be configured with a logger. For example
|
26
|
+
in a Rails application, create an initializer such as
|
27
|
+
`config/initializers/yeti_logger.rb`:
|
28
|
+
|
29
|
+
require 'yeti_logger'
|
30
|
+
|
31
|
+
YetiLogger.configure do |config|
|
32
|
+
config.logger = Rails.logger
|
33
|
+
end
|
34
|
+
|
35
|
+
### Logging for Classes
|
36
|
+
|
37
|
+
In classes where you want to use YetiLogger you must include the module:
|
38
|
+
|
39
|
+
include YetiLogger
|
40
|
+
|
41
|
+
This will define `log_error`, `log_warn`, `log_info`, and `log_debug` methods
|
42
|
+
at both the class and instance level.
|
43
|
+
|
44
|
+
Each method has a similar signature:
|
45
|
+
|
46
|
+
log_info(obj = nil, exception = nil, &block)
|
47
|
+
|
48
|
+
Each of these arguments is optional. Passing no args results in a blank log
|
49
|
+
message.
|
50
|
+
|
51
|
+
All log messages are automatically prefixed with the name of the class.
|
52
|
+
|
53
|
+
Exceptions will always be logged as their message and a backtrace. If you
|
54
|
+
would like a message along with the exception, use this form:
|
55
|
+
|
56
|
+
log_info("My messsage", exception)
|
57
|
+
|
58
|
+
If you only need the exception, then use the block form above:
|
59
|
+
|
60
|
+
log_info { exception }
|
61
|
+
|
62
|
+
In situations where a logger object is required, the `#as_logger` instance
|
63
|
+
method can be used to return an object that responds to `error`, `warn`, `info`,
|
64
|
+
and `debug`, and forwards to the instance to format log messages using
|
65
|
+
`YetiLogger`:
|
66
|
+
|
67
|
+
logger = instance.as_logger
|
68
|
+
|
69
|
+
# The following result in equivalent log messages
|
70
|
+
logger.info('this message')
|
71
|
+
instance.log_info('this message')
|
72
|
+
|
73
|
+
### Preferred use
|
74
|
+
|
75
|
+
The preferred way to use `YetiLogger` is via the block as it defers evaluation
|
76
|
+
of the string until we've decided whether or not to log the message. Along with
|
77
|
+
blocks, passing data in as a hash is also preferred.
|
78
|
+
|
79
|
+
log_debug { { system: "dagobah", jedi: expensive_to_compute() } }
|
80
|
+
|
81
|
+
log_debug({ system: "dagobah", jedi: expensive_to_compute() })
|
82
|
+
|
83
|
+
Both of these will result in a log message formatted with `key=value` pairs
|
84
|
+
separated by whitespace. The latter will call the `expensive_to_compute()`
|
85
|
+
method prior to entry into the `log_debug` function meaning it will be computed
|
86
|
+
whether or not the log statement is actually written out.
|
87
|
+
|
88
|
+
The block format does not support separate arguments for exception and
|
89
|
+
non-exception data. If you need both, either use the block format and use the
|
90
|
+
functions in `YetiLogger::MessageFormatters` to format the exception, or use the
|
91
|
+
`(obj, exception)` arguments taking note of any performance implications in
|
92
|
+
building the log message.
|
93
|
+
|
94
|
+
### Message formatting
|
95
|
+
|
96
|
+
The value passed in for obj or returned by the block will be formatted
|
97
|
+
depending on the content of it. If it is a hash, it will be formatted into
|
98
|
+
"key=value" pairs separated by whitespace. Any value that needs to be quoted
|
99
|
+
(embedded quotes, or has whitespace), will be quoted and embedded quotes
|
100
|
+
escaped.
|
101
|
+
|
102
|
+
Formatting of exceptions is dependent on the data type of the obj argument. If
|
103
|
+
it is a string, then a string form of the exception details is included. If obj
|
104
|
+
is a hash, then the exception in injected into the hash and printed as
|
105
|
+
additional `key=value` pairs. Classname, message and backtrace are included in
|
106
|
+
the message.
|
107
|
+
|
108
|
+
### Nested Hashes
|
109
|
+
|
110
|
+
For hash logging, each key and value are converted to strings which means
|
111
|
+
nested hashes might not serialize like you would think. Additionally, no
|
112
|
+
quotes are provided around keys or values, meaning for hashes that contain
|
113
|
+
data that may include whitespace, it might make sense to pass in a serialized
|
114
|
+
form of the hash instead of the hash itself. If you would like to override
|
115
|
+
this behavior, pass in the serialized format for the hash, such as:
|
116
|
+
|
117
|
+
log_info { hash.to_json }
|
118
|
+
log_info { hash.to_s }
|
119
|
+
log_info { hash.to_my_log_format }
|
120
|
+
|
121
|
+
## Test Support
|
122
|
+
|
123
|
+
There are a couple helpers provided to support testing of YetiLogger calls. All
|
124
|
+
helpers are available by requiring the relevant file and importing the module:
|
125
|
+
|
126
|
+
require 'yeti_logger/test_helper'
|
127
|
+
|
128
|
+
describe MyClass do
|
129
|
+
include YetiLogger::TestHelper
|
130
|
+
|
131
|
+
# tests here
|
132
|
+
end
|
133
|
+
|
134
|
+
The simpler form sets up an expectation that the log level method in YetiLogger
|
135
|
+
will be called. It returns that expectation and you can extend it with your
|
136
|
+
preferred matcher:
|
137
|
+
|
138
|
+
...
|
139
|
+
should_log(:info).with("exact message here")
|
140
|
+
...
|
141
|
+
should_log(:warn).with(/a pattern/)
|
142
|
+
...
|
143
|
+
|
144
|
+
If you have an application that produces many lines of log messages at any one
|
145
|
+
level, this can be cumbersome so `YetiLogger::TestHelper` provides methods for
|
146
|
+
you to set up expectation to see specific log messages amongst all of the
|
147
|
+
messages it may receive:
|
148
|
+
|
149
|
+
messages = [
|
150
|
+
'YourClass: one',
|
151
|
+
/match a regex!/,
|
152
|
+
'YourOtherClass: three'
|
153
|
+
]
|
154
|
+
|
155
|
+
expect_to_see_log_messages(messages, :info) do
|
156
|
+
trigger_code_that_results_in_logging
|
157
|
+
end
|
158
|
+
|
159
|
+
There is also a singular form of this `expect_to_see_log_message` that takes a
|
160
|
+
single message to match.
|
161
|
+
|
162
|
+
If you have code that logs at a level below your threshold set during testing
|
163
|
+
you may have hidden bugs. Consider the following:
|
164
|
+
|
165
|
+
def my_method
|
166
|
+
...
|
167
|
+
log_debug do
|
168
|
+
compute_log_message(arg1, arg2)
|
169
|
+
end
|
170
|
+
...
|
171
|
+
end
|
172
|
+
|
173
|
+
If in test you set your log level to be warn, then this block will never
|
174
|
+
execute. Now, if this method were recently renamed, or another argument was
|
175
|
+
added, you would have a runtime error just waiting for someone to set the log
|
176
|
+
level down to debug. If you want to temporarily raise the log level for a given
|
177
|
+
test, you can do so:
|
178
|
+
|
179
|
+
with_log_level(:debug) do
|
180
|
+
should_log(:debug).with("expected message")
|
181
|
+
my_method()
|
182
|
+
end
|
183
|
+
|
184
|
+
Once the block is finished, the log level will be returned to whatever level you
|
185
|
+
had it previously.
|
186
|
+
|
187
|
+
## Contributing
|
188
|
+
|
189
|
+
1. Fork it
|
190
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
191
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
192
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
193
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/yeti_logger.rb
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'yeti_logger/version'
|
3
|
+
require 'yeti_logger/constants'
|
4
|
+
require 'yeti_logger/configuration'
|
5
|
+
require 'yeti_logger/wrapped_logger'
|
6
|
+
require 'yeti_logger/message_formatters'
|
7
|
+
require 'active_support/core_ext/benchmark'
|
8
|
+
require 'active_support/concern'
|
9
|
+
require 'active_support/core_ext/object/blank'
|
10
|
+
require 'active_support/core_ext/object/try'
|
11
|
+
|
12
|
+
# Mixin module providing Yesware logging functionality including formatting of
|
13
|
+
# log message data (exceptions, hashes, etc). Refer to the Readme for further
|
14
|
+
# information and examples.
|
15
|
+
#
|
16
|
+
# Module you can include in your class to get logging with class name prefixing.
|
17
|
+
# This will also log hashes as key=value pairs and exception backtraces. These
|
18
|
+
# methods are added via metaprogramming. When it's done, you'll have methods to
|
19
|
+
# log at each level:
|
20
|
+
# - debug
|
21
|
+
# - info
|
22
|
+
# - warn
|
23
|
+
# - error
|
24
|
+
#
|
25
|
+
# Each method will have a signature that looks like:
|
26
|
+
# log_info(obj = nil, exception = nil, &block)
|
27
|
+
#
|
28
|
+
# Each of these arguments is optional. Pass no arguments results in a blank line
|
29
|
+
# being logged (with a classname prefix).
|
30
|
+
#
|
31
|
+
# The ideal usage for YetiLogger is to pass in blocks:
|
32
|
+
#
|
33
|
+
# log_info { "Here is my message with #{some} class #{values}" }
|
34
|
+
#
|
35
|
+
# This will defer evaluation of the string until the logger has determined if
|
36
|
+
# the current log level is high enough to warrant evaluating the string and
|
37
|
+
# logging it.
|
38
|
+
#
|
39
|
+
# Exceptions will be logged as a combination of the message, the class and some
|
40
|
+
# number of lines of the backtrace. If you pass in a value for obj that is a
|
41
|
+
# Hash, then the exception will be injected into the hash.
|
42
|
+
#
|
43
|
+
# log_info("My message", exception)
|
44
|
+
#
|
45
|
+
# If you only need the exception, then use the block form above:
|
46
|
+
#
|
47
|
+
# log_info { exception }
|
48
|
+
#
|
49
|
+
# The value passed in for obj or returned by the block will be formatted
|
50
|
+
# depending on the content of it. If it is a hash, we will format into
|
51
|
+
# "key=value" pairs separated by whitespace. If the value is an exception, the
|
52
|
+
# message will be logged along with the backtrace. All other objects are
|
53
|
+
# converted to string via the to_s method.
|
54
|
+
#
|
55
|
+
# For hash logging, each key and value are converted to strings which means
|
56
|
+
# nested hashes might not serialize like you would think. Additionally, no
|
57
|
+
# quotes are provided around keys or values, meaning for hashes that contain
|
58
|
+
# data that may include whitespace, it might make sense to pass in a serialized
|
59
|
+
# form of the hash instead of the hash itself. If you would like to override
|
60
|
+
# this behavior, pass in the serialized format for the hash, such as:
|
61
|
+
#
|
62
|
+
# log_info { hash.to_json }
|
63
|
+
# log_info { hash.to_s }
|
64
|
+
# log_info { hash.to_my_log_format }
|
65
|
+
#
|
66
|
+
#
|
67
|
+
module YetiLogger
|
68
|
+
extend ActiveSupport::Concern
|
69
|
+
|
70
|
+
# This module contains log method definitions that are used at both the
|
71
|
+
# class and the instance level.
|
72
|
+
# Each log method is defined explicitly, despite the obvious repetition,
|
73
|
+
# to avoid the cost of creating a Proc for any, possibly unused, block
|
74
|
+
# passed to the log method.
|
75
|
+
# Define these methods explicitly allows the use of yield.
|
76
|
+
module LogMethods
|
77
|
+
def log_debug(obj = nil, ex = nil)
|
78
|
+
if YetiLogger.logger.level <= Logger::DEBUG
|
79
|
+
msg = if block_given?
|
80
|
+
MessageFormatters.build_log_message(log_class_name, yield)
|
81
|
+
else
|
82
|
+
MessageFormatters.build_log_message(log_class_name, obj, ex)
|
83
|
+
end
|
84
|
+
YetiLogger.logger.send(:debug, msg)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def log_info(obj = nil, ex = nil)
|
89
|
+
if YetiLogger.logger.level <= Logger::INFO
|
90
|
+
msg = if block_given?
|
91
|
+
MessageFormatters.build_log_message(log_class_name, yield)
|
92
|
+
else
|
93
|
+
MessageFormatters.build_log_message(log_class_name, obj, ex)
|
94
|
+
end
|
95
|
+
YetiLogger.logger.send(:info, msg)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def log_warn(obj = nil, ex = nil)
|
100
|
+
if YetiLogger.logger.level <= Logger::WARN
|
101
|
+
msg = if block_given?
|
102
|
+
MessageFormatters.build_log_message(log_class_name, yield)
|
103
|
+
else
|
104
|
+
MessageFormatters.build_log_message(log_class_name, obj, ex)
|
105
|
+
end
|
106
|
+
YetiLogger.logger.send(:warn, msg)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def log_error(obj = nil, ex = nil)
|
111
|
+
if YetiLogger.logger.level <= Logger::ERROR
|
112
|
+
msg = if block_given?
|
113
|
+
MessageFormatters.build_log_message(log_class_name, yield)
|
114
|
+
else
|
115
|
+
MessageFormatters.build_log_message(log_class_name, obj, ex)
|
116
|
+
end
|
117
|
+
YetiLogger.logger.send(:error, msg)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def log_fatal(obj = nil, ex = nil)
|
122
|
+
if YetiLogger.logger.level <= Logger::FATAL
|
123
|
+
msg = if block_given?
|
124
|
+
MessageFormatters.build_log_message(log_class_name, yield)
|
125
|
+
else
|
126
|
+
MessageFormatters.build_log_message(log_class_name, obj, ex)
|
127
|
+
end
|
128
|
+
YetiLogger.logger.send(:fatal, msg)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Class-level methods.
|
134
|
+
module ClassMethods
|
135
|
+
include LogMethods
|
136
|
+
|
137
|
+
def log_class_name
|
138
|
+
self.name
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Instance-level log methods
|
143
|
+
include LogMethods
|
144
|
+
|
145
|
+
def log_class_name
|
146
|
+
self.class.name
|
147
|
+
end
|
148
|
+
|
149
|
+
def log_time(action, level = :info)
|
150
|
+
ms = Benchmark.ms do
|
151
|
+
yield
|
152
|
+
end
|
153
|
+
YetiLogger.logger.send(level,
|
154
|
+
MessageFormatters.build_log_message(self.class.name,
|
155
|
+
{ action: action,
|
156
|
+
time_ms: ms.to_i }))
|
157
|
+
end
|
158
|
+
|
159
|
+
# Wrap self in an object that responds to :info, :warn, :error, :debug, etc.
|
160
|
+
# @return [YetiLogger::WrapperLogger]
|
161
|
+
def as_logger
|
162
|
+
YetiLogger::WrappedLogger.new(self)
|
163
|
+
end
|
164
|
+
end
|