catamaran 2.1.0 → 2.2.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.
- data/LICENSE +21 -0
- data/README.md +60 -52
- data/catamaran.gemspec +1 -0
- data/examples/benchmarking_conditional_log_statements.rb +44 -0
- data/examples/benchmarking_the_caller.rb +34 -0
- data/examples/quickstart_with_ruby.rb +76 -0
- data/examples/with_double_colon_delimiter.rb +25 -0
- data/examples/with_the_ruby_profiler.rb +33 -0
- data/lib/catamaran/log_level.rb +29 -0
- data/lib/catamaran/logger.rb +24 -4
- data/lib/catamaran/version.rb +1 -1
- data/lib/generators/catamaran/templates/catamaran/development.rb +3 -0
- data/spec/catamaran_spec.rb +99 -15
- metadata +40 -22
- checksums.yaml +0 -7
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Logging is a powerful and often undervalued tool in software development. When
|
|
|
6
6
|
Gemfile
|
|
7
7
|
-------
|
|
8
8
|
|
|
9
|
-
gem 'catamaran', '~> 2.
|
|
9
|
+
gem 'catamaran', '~> 2.2.0'
|
|
10
10
|
|
|
11
11
|
Rails-related setup:
|
|
12
12
|
|
|
@@ -17,11 +17,12 @@ Now modify `config/initializers/catamaran/development.rb` as needed
|
|
|
17
17
|
Ruby Quickstart
|
|
18
18
|
---------------
|
|
19
19
|
```ruby
|
|
20
|
+
require 'catamaran'
|
|
20
21
|
|
|
21
|
-
class
|
|
22
|
-
LOGGER = Catamaran.logger( "com.mytld.
|
|
22
|
+
class QuickstartWithRuby
|
|
23
|
+
LOGGER = Catamaran.logger( "com.mytld.QuickstartWithRuby" )
|
|
23
24
|
# or equivalently:
|
|
24
|
-
# LOGGER = Catamaran.logger.com.mytld.
|
|
25
|
+
# LOGGER = Catamaran.logger.com.mytld.QuickstartWithRuby
|
|
25
26
|
|
|
26
27
|
def demonstrating_log_levels
|
|
27
28
|
# Disabled by default
|
|
@@ -69,29 +70,29 @@ class WorkingWithCatamaran
|
|
|
69
70
|
end
|
|
70
71
|
end
|
|
71
72
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
quickstart_with_ruby = QuickstartWithRuby.new
|
|
74
|
+
quickstart_with_ruby.demonstrating_log_levels
|
|
75
|
+
quickstart_with_ruby.using_the_caller
|
|
76
|
+
quickstart_with_ruby.changing_the_log_level
|
|
77
|
+
quickstart_with_ruby.displaying_backtrace_information
|
|
77
78
|
```
|
|
78
79
|
|
|
79
80
|
And the output
|
|
80
81
|
|
|
81
82
|
```
|
|
82
|
-
NOTICE pid-
|
|
83
|
-
WARN pid-
|
|
84
|
-
ERROR pid-
|
|
85
|
-
SEVERE pid-
|
|
86
|
-
FATAL pid-
|
|
87
|
-
NOTICE pid-
|
|
88
|
-
NOTICE pid-
|
|
89
|
-
NOTICE pid-
|
|
90
|
-
DEBUG pid-
|
|
91
|
-
WARN pid-
|
|
92
|
-
ERROR pid-
|
|
93
|
-
|
|
94
|
-
|
|
83
|
+
NOTICE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - NOTICE logs are captured by default
|
|
84
|
+
WARN pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - WARN logs are captured by default
|
|
85
|
+
ERROR pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - ERROR logs are captured by default
|
|
86
|
+
SEVERE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - SEVERE logs are captured by default
|
|
87
|
+
FATAL pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - FATAL logs are captured by default
|
|
88
|
+
NOTICE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - The caller will append log location info to this message (quickstart_with_ruby.rb:27:in `using_the_caller')
|
|
89
|
+
NOTICE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - If the user specifies the :file, :line, AND :method, the caller will NOT get invoked (quickstart_with_ruby.rb:28:in `run')
|
|
90
|
+
NOTICE pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - To prove the caller is not used, we can put dummy data in and see that it's being used instead (just_kidding.rb:123456789:in `whatever')
|
|
91
|
+
DEBUG pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - Now DEBUG messages should show up
|
|
92
|
+
WARN pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - Sample WARN statement with backtrace requested
|
|
93
|
+
ERROR pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - Sample ERROR statement with backtrace requested from:
|
|
94
|
+
quickstart_with_ruby.rb:51:in `displaying_backtrace_information'
|
|
95
|
+
quickstart_with_ruby.rb:59:in `<main>'
|
|
95
96
|
```
|
|
96
97
|
|
|
97
98
|
|
|
@@ -114,82 +115,83 @@ Load the `index` page and check out your `development.log` file
|
|
|
114
115
|
|
|
115
116
|
DEBUG pid-86000 [2013-12-17 17:26:39:176] ld.myrailsapp.app.controllers.WidgetsController - "Handling a Widgets index request with params = {"controller"=>"widgets", "action"=>"index"} (`/myrailsapp/app/controllers/widgets_controller.rb:7`:in `index`)
|
|
116
117
|
|
|
118
|
+
More Examples
|
|
119
|
+
-------------
|
|
120
|
+
See the [examples folder](https://github.com/jgithub/catamaran/tree/master/examples).
|
|
121
|
+
|
|
117
122
|
Log Levels
|
|
118
123
|
----------
|
|
119
124
|
Available log levels: `TRACE` (verbose and trivial log messages), `DEBUG`, `INFO`, `NOTICE`, `WARN`, `ERROR`, `SEVERE` (logs related to very serious error conditions), `FATAL`
|
|
120
125
|
|
|
121
126
|
The `NOTICE` log level severity is the default. Any logs with `NOTICE` or higher severity will be captured.
|
|
122
127
|
|
|
123
|
-
|
|
124
128
|
Inspiration
|
|
125
129
|
-----------
|
|
130
|
+
I always liked working with log4j. Much of the inspriation comes from that.
|
|
131
|
+
|
|
132
|
+
* Named Hierarchy
|
|
133
|
+
* Level Inheritance
|
|
134
|
+
* `TRACE`
|
|
135
|
+
|
|
126
136
|
I'm looking for a logging utility that:
|
|
127
137
|
|
|
128
138
|
* records granular timestamps with each log entry
|
|
129
139
|
* records the process ID with each log entry
|
|
130
140
|
* captures from where each log entry was generated
|
|
131
141
|
* works equally well with classes that do and do *not* extend Rails base classes
|
|
132
|
-
* supports the TRACE log level (or other log level less critical than DEBUG).
|
|
133
|
-
* is capable of capturing logs at different severity thresholds from different parts of the app simultaneously
|
|
142
|
+
* supports the TRACE log level (or other log level less critical than DEBUG).
|
|
143
|
+
* is capable of capturing logs at different severity thresholds from different parts of the app simultaneously
|
|
134
144
|
* readily works with Rails
|
|
135
|
-
* http://stackoverflow.com/questions/462651/rails-logger-format-string-configuration
|
|
136
|
-
* http://stackoverflow.com/questions/3654827/logging-in-rails-app
|
|
137
|
-
* http://stackoverflow.com/questions/11991967/rails-log-too-verbose
|
|
138
|
-
* http://www.ietf.org/rfc/rfc3164.txt
|
|
139
|
-
* http://logging.apache.org/log4j/1.2
|
|
140
145
|
|
|
141
146
|
Performance Considerations
|
|
142
147
|
--------------------------
|
|
143
148
|
|
|
144
149
|
### With or without `if LOGGER.debug?`
|
|
145
150
|
```ruby
|
|
151
|
+
$: << '../lib'
|
|
152
|
+
|
|
146
153
|
require 'catamaran'
|
|
147
154
|
require 'benchmark'
|
|
148
155
|
|
|
149
|
-
Catamaran
|
|
156
|
+
Catamaran.logger.log_level = Catamaran::LogLevel::INFO
|
|
150
157
|
Catamaran::Manager.stderr = false
|
|
151
158
|
|
|
152
|
-
class
|
|
153
|
-
LOGGER = Catamaran.logger( "
|
|
159
|
+
class BenchmarkingConditionalLogStatements
|
|
160
|
+
LOGGER = Catamaran.logger( "BenchmarkingConditionalLogStatements" )
|
|
154
161
|
|
|
155
162
|
# NOTE that the log level for this test is set to INFO,
|
|
156
163
|
# so 'warn' logs are enabled and 'debug' logs are disabled
|
|
157
164
|
|
|
158
165
|
n = 500000
|
|
159
166
|
Benchmark.bm(7) do |x|
|
|
160
|
-
x.report("warn WITHOUT if LOGGER.warn? ") {
|
|
167
|
+
x.report("LOGGER.warn WITHOUT if LOGGER.warn? ") {
|
|
161
168
|
n.times do |i|
|
|
162
169
|
LOGGER.warn "Based on the current log level, this log is being captured"
|
|
163
170
|
end
|
|
164
171
|
}
|
|
165
|
-
x.report("warn WITH if LOGGER.warn? ") {
|
|
172
|
+
x.report("LOGGER.warn WITH if LOGGER.warn? ") {
|
|
166
173
|
n.times do |i|
|
|
167
174
|
LOGGER.warn "Based on the current log level, this log is being captured" if LOGGER.warn?
|
|
168
175
|
end
|
|
169
176
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
Benchmark.bm(7) do |x|
|
|
173
|
-
x.report("debug WITHOUT if LOGGER.debug?") {
|
|
177
|
+
x.report("LOGGER.debug WITHOUT if LOGGER.debug?") {
|
|
174
178
|
n.times do |i|
|
|
175
179
|
LOGGER.debug "Based on the current log level, this log is NOT being captured"
|
|
176
180
|
end
|
|
177
181
|
}
|
|
178
|
-
x.report("debug WITH if LOGGER.debug? ") {
|
|
182
|
+
x.report("LOGGER.debug WITH if LOGGER.debug? ") {
|
|
179
183
|
n.times do |i|
|
|
180
184
|
LOGGER.debug "Based on the current log level, this log is NOT being captured" if LOGGER.debug?
|
|
181
185
|
end
|
|
182
186
|
}
|
|
183
187
|
end
|
|
184
|
-
|
|
185
188
|
end
|
|
186
189
|
|
|
187
|
-
#
|
|
188
|
-
# warn WITHOUT if LOGGER.warn? 6.
|
|
189
|
-
# warn WITH if LOGGER.warn? 7.
|
|
190
|
-
#
|
|
191
|
-
# debug
|
|
192
|
-
# debug WITH if LOGGER.debug? 0.560000 0.010000 0.570000 ( 0.574397)
|
|
190
|
+
# user system total real
|
|
191
|
+
# LOGGER.warn WITHOUT if LOGGER.warn? 6.440000 0.090000 6.530000 ( 6.533838)
|
|
192
|
+
# LOGGER.warn WITH if LOGGER.warn? 7.110000 0.120000 7.230000 ( 7.242870)
|
|
193
|
+
# LOGGER.debug WITHOUT if LOGGER.debug? 0.530000 0.020000 0.550000 ( 0.548454)
|
|
194
|
+
# LOGGER.debug WITH if LOGGER.debug? 0.450000 0.030000 0.480000 ( 0.474419)
|
|
193
195
|
```
|
|
194
196
|
|
|
195
197
|
#### Summary
|
|
@@ -275,8 +277,6 @@ require 'ruby-prof'
|
|
|
275
277
|
|
|
276
278
|
class CatamaranProfilerTest
|
|
277
279
|
LOGGER = Catamaran.logger( "com.mycompany.CatamaranProfilerTest" )
|
|
278
|
-
# or equivalently:
|
|
279
|
-
# LOGGER = Catamaran.logger.com.mycompany.CatamaranProfilerTest
|
|
280
280
|
|
|
281
281
|
def run
|
|
282
282
|
# Disabled by default
|
|
@@ -296,7 +296,7 @@ end
|
|
|
296
296
|
|
|
297
297
|
RubyProf.start
|
|
298
298
|
|
|
299
|
-
|
|
299
|
+
catamaran_profiler_test = CatamaranProfilerTest.new
|
|
300
300
|
1000.times do
|
|
301
301
|
demo.run
|
|
302
302
|
end
|
|
@@ -304,7 +304,6 @@ end
|
|
|
304
304
|
result = RubyProf.stop
|
|
305
305
|
|
|
306
306
|
printer = RubyProf::GraphPrinter.new(result)
|
|
307
|
-
|
|
308
307
|
printer.print(STDOUT)
|
|
309
308
|
```
|
|
310
309
|
|
|
@@ -510,6 +509,15 @@ Ideas around what's next
|
|
|
510
509
|
* Heroku support (https://blog.heroku.com/archives/2013/7/15/logging-on-heroku)
|
|
511
510
|
* Buffered file I/O considerations
|
|
512
511
|
|
|
513
|
-
|
|
512
|
+
See also
|
|
513
|
+
--------
|
|
514
|
+
* http://stackoverflow.com/questions/462651/rails-logger-format-string-configuration
|
|
515
|
+
* http://stackoverflow.com/questions/3654827/logging-in-rails-app
|
|
516
|
+
* http://stackoverflow.com/questions/11991967/rails-log-too-verbose
|
|
517
|
+
* http://www.ietf.org/rfc/rfc3164.txt
|
|
518
|
+
* http://logging.apache.org/log4j/1.2
|
|
519
|
+
* https://github.com/TwP/logging
|
|
520
|
+
* https://github.com/colbygk/log4r
|
|
521
|
+
* http://guides.rubyonrails.org/debugging_rails_applications.html#the-logger
|
|
514
522
|
|
|
515
523
|
|
data/catamaran.gemspec
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
$: << '../lib'
|
|
2
|
+
|
|
3
|
+
require 'catamaran'
|
|
4
|
+
require 'benchmark'
|
|
5
|
+
|
|
6
|
+
Catamaran.logger.log_level = Catamaran::LogLevel::INFO
|
|
7
|
+
Catamaran::Manager.stderr = false
|
|
8
|
+
|
|
9
|
+
class BenchmarkingConditionalLogStatements
|
|
10
|
+
LOGGER = Catamaran.logger( "BenchmarkingConditionalLogStatements" )
|
|
11
|
+
|
|
12
|
+
# NOTE that the log level for this test is set to INFO,
|
|
13
|
+
# so 'warn' logs are enabled and 'debug' logs are disabled
|
|
14
|
+
|
|
15
|
+
n = 500000
|
|
16
|
+
Benchmark.bm(7) do |x|
|
|
17
|
+
x.report("LOGGER.warn WITHOUT if LOGGER.warn? ") {
|
|
18
|
+
n.times do |i|
|
|
19
|
+
LOGGER.warn "Based on the current log level, this log is being captured"
|
|
20
|
+
end
|
|
21
|
+
}
|
|
22
|
+
x.report("LOGGER.warn WITH if LOGGER.warn? ") {
|
|
23
|
+
n.times do |i|
|
|
24
|
+
LOGGER.warn "Based on the current log level, this log is being captured" if LOGGER.warn?
|
|
25
|
+
end
|
|
26
|
+
}
|
|
27
|
+
x.report("LOGGER.debug WITHOUT if LOGGER.debug?") {
|
|
28
|
+
n.times do |i|
|
|
29
|
+
LOGGER.debug "Based on the current log level, this log is NOT being captured"
|
|
30
|
+
end
|
|
31
|
+
}
|
|
32
|
+
x.report("LOGGER.debug WITH if LOGGER.debug? ") {
|
|
33
|
+
n.times do |i|
|
|
34
|
+
LOGGER.debug "Based on the current log level, this log is NOT being captured" if LOGGER.debug?
|
|
35
|
+
end
|
|
36
|
+
}
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# user system total real
|
|
41
|
+
# LOGGER.warn WITHOUT if LOGGER.warn? 6.440000 0.090000 6.530000 ( 6.533838)
|
|
42
|
+
# LOGGER.warn WITH if LOGGER.warn? 7.110000 0.120000 7.230000 ( 7.242870)
|
|
43
|
+
# LOGGER.debug WITHOUT if LOGGER.debug? 0.530000 0.020000 0.550000 ( 0.548454)
|
|
44
|
+
# LOGGER.debug WITH if LOGGER.debug? 0.450000 0.030000 0.480000 ( 0.474419)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
$: << '../lib'
|
|
2
|
+
require 'catamaran'
|
|
3
|
+
require 'benchmark'
|
|
4
|
+
|
|
5
|
+
Catamaran::Manager.stderr = false
|
|
6
|
+
|
|
7
|
+
class CatamaranCallerBenchmark
|
|
8
|
+
LOGGER = Catamaran.logger( "CatamaranCallerBenchmark" )
|
|
9
|
+
|
|
10
|
+
n = 500000
|
|
11
|
+
Benchmark.bm(7) do |x|
|
|
12
|
+
Catamaran::Manager.formatter_caller_enabled = false
|
|
13
|
+
|
|
14
|
+
x.report("Catamaran::Manager.formatter_caller_enabled = false") {
|
|
15
|
+
n.times do |i|
|
|
16
|
+
LOGGER.error "This is a ERROR log"
|
|
17
|
+
end
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Catamaran::Manager.formatter_caller_enabled = true
|
|
21
|
+
|
|
22
|
+
x.report("Catamaran::Manager.formatter_caller_enabled = true ") {
|
|
23
|
+
n.times do |i|
|
|
24
|
+
LOGGER.error "This is a ERROR log"
|
|
25
|
+
end
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# user system total real
|
|
32
|
+
# Catamaran::Manager.formatter_caller_enabled = false 5.870000 0.060000 5.930000 ( 5.943813)
|
|
33
|
+
# Catamaran::Manager.formatter_caller_enabled = true 13.120000 0.350000 13.470000 ( 13.469126)
|
|
34
|
+
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
$: << '../lib'
|
|
2
|
+
require 'catamaran'
|
|
3
|
+
|
|
4
|
+
class QuickstartWithRuby
|
|
5
|
+
LOGGER = Catamaran.logger( "com.mytld.QuickstartWithRuby" )
|
|
6
|
+
# or equivalently:
|
|
7
|
+
# LOGGER = Catamaran.logger.com.mytld.QuickstartWithRuby
|
|
8
|
+
|
|
9
|
+
def demonstrating_log_levels
|
|
10
|
+
# Disabled by default
|
|
11
|
+
LOGGER.trace( "TRACE logs are NOT captured by default" ) if LOGGER.trace?
|
|
12
|
+
LOGGER.debug( "DEBUG logs are NOT captured by default" ) if LOGGER.debug?
|
|
13
|
+
LOGGER.info( "INFO logs are NOT captured by default" ) if LOGGER.info?
|
|
14
|
+
|
|
15
|
+
# Enabled by default
|
|
16
|
+
LOGGER.notice( "NOTICE logs are captured by default" )
|
|
17
|
+
LOGGER.warn( "WARN logs are captured by default" )
|
|
18
|
+
LOGGER.error( "ERROR logs are captured by default" )
|
|
19
|
+
LOGGER.severe( "SEVERE logs are captured by default" )
|
|
20
|
+
LOGGER.fatal( "FATAL logs are captured by default" )
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def using_the_caller
|
|
24
|
+
# Enable the caller (it's disabled by default)
|
|
25
|
+
Catamaran::Manager.formatter_caller_enabled = true
|
|
26
|
+
|
|
27
|
+
LOGGER.notice( "The caller will append log location info to this message" )
|
|
28
|
+
LOGGER.notice( "If the user specifies the :file, :line, AND :method, the caller will NOT get invoked", { :file => __FILE__, :line => __LINE__, :method => 'run' } )
|
|
29
|
+
LOGGER.notice( "To prove the caller is not used, we can put dummy data in and see that it's being used instead", { :file => 'just_kidding.rb', :line => 123456789, :method => 'whatever' } )
|
|
30
|
+
|
|
31
|
+
# Turn it back off
|
|
32
|
+
Catamaran::Manager.formatter_caller_enabled = false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def changing_the_log_level
|
|
36
|
+
# Note that the log level can be changed
|
|
37
|
+
Catamaran.logger.log_level = Catamaran::LogLevel::DEBUG
|
|
38
|
+
Catamaran::Manager.forget_cached_log_levels()
|
|
39
|
+
|
|
40
|
+
LOGGER.trace( "TRACE logs are STILL NOT captured" ) if LOGGER.trace?
|
|
41
|
+
LOGGER.debug( "Now DEBUG messages should show up" ) if LOGGER.debug?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def displaying_backtrace_information
|
|
45
|
+
Catamaran.logger.log_level = Catamaran::LogLevel::NOTICE
|
|
46
|
+
Catamaran.logger.backtrace_log_level = Catamaran::LogLevel::ERROR
|
|
47
|
+
Catamaran::Manager.forget_cached_log_levels()
|
|
48
|
+
|
|
49
|
+
LOGGER.debug( "Sample DEBUG statement with backtrace requested", { :backtrace => true } )
|
|
50
|
+
LOGGER.warn( "Sample WARN statement with backtrace requested", { :backtrace => true } )
|
|
51
|
+
LOGGER.error( "Sample ERROR statement with backtrace requested", { :backtrace => true } )
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
quickstart_with_ruby = QuickstartWithRuby.new
|
|
56
|
+
quickstart_with_ruby.demonstrating_log_levels
|
|
57
|
+
quickstart_with_ruby.using_the_caller
|
|
58
|
+
quickstart_with_ruby.changing_the_log_level
|
|
59
|
+
quickstart_with_ruby.displaying_backtrace_information
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# NOTICE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - NOTICE logs are captured by default
|
|
64
|
+
# WARN pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - WARN logs are captured by default
|
|
65
|
+
# ERROR pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - ERROR logs are captured by default
|
|
66
|
+
# SEVERE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - SEVERE logs are captured by default
|
|
67
|
+
# FATAL pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - FATAL logs are captured by default
|
|
68
|
+
# NOTICE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - The caller will append log location info to this message (quickstart_with_ruby.rb:27:in `using_the_caller')
|
|
69
|
+
# NOTICE pid-50247 [2014-01-07 13:22:28:516] com.mytld.QuickstartWithRuby - If the user specifies the :file, :line, AND :method, the caller will NOT get invoked (quickstart_with_ruby.rb:28:in `run')
|
|
70
|
+
# NOTICE pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - To prove the caller is not used, we can put dummy data in and see that it's being used instead (just_kidding.rb:123456789:in `whatever')
|
|
71
|
+
# DEBUG pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - Now DEBUG messages should show up
|
|
72
|
+
# WARN pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - Sample WARN statement with backtrace requested
|
|
73
|
+
# ERROR pid-50247 [2014-01-07 13:22:28:517] com.mytld.QuickstartWithRuby - Sample ERROR statement with backtrace requested from:
|
|
74
|
+
# quickstart_with_ruby.rb:51:in `displaying_backtrace_information'
|
|
75
|
+
# quickstart_with_ruby.rb:59:in `<main>'
|
|
76
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
$: << '../lib'
|
|
2
|
+
require 'catamaran'
|
|
3
|
+
|
|
4
|
+
Catamaran::Manager.delimiter = '::'
|
|
5
|
+
Catamaran::logger.log_level = :debug
|
|
6
|
+
|
|
7
|
+
module MyCoolModule
|
|
8
|
+
class WithDoubleColonDelimiter
|
|
9
|
+
LOGGER = Catamaran.logger( "MyCoolModule::WithDoubleColonDelimiter" )
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
LOGGER.debug( "Does this look more like Ruby?", { :file => __FILE__, :line => __LINE__ } ) if LOGGER.debug?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
with_double_colon_delimiter = MyCoolModule::WithDoubleColonDelimiter.new
|
|
18
|
+
with_double_colon_delimiter.run
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# DEBUG pid-829 [2014-01-08 13:55:09:979] MyCoolModule::WithDoubleColonDelimiter - Does this look more like Ruby? (with_double_colon_delimiter.rb:12)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
$: << '../lib'
|
|
2
|
+
require 'catamaran'
|
|
3
|
+
require 'ruby-prof'
|
|
4
|
+
|
|
5
|
+
class WithTheRubyProfiler
|
|
6
|
+
LOGGER = Catamaran.logger( "com.mycompany.WithTheRubyProfiler" )
|
|
7
|
+
|
|
8
|
+
def run
|
|
9
|
+
# Disabled by default
|
|
10
|
+
LOGGER.trace( "TRACE logs are NOT captured by default" ) if LOGGER.trace?
|
|
11
|
+
LOGGER.debug( "DEBUG logs are NOT captured by default" ) if LOGGER.debug?
|
|
12
|
+
LOGGER.info( "INFO logs are NOT captured by default" ) if LOGGER.info?
|
|
13
|
+
|
|
14
|
+
# Enabled by default
|
|
15
|
+
LOGGER.notice( "NOTICE logs are captured by default" )
|
|
16
|
+
LOGGER.warn( "WARN logs are captured by default" )
|
|
17
|
+
LOGGER.error( "ERROR logs are captured by default" )
|
|
18
|
+
LOGGER.severe( "SEVERE logs are captured by default" )
|
|
19
|
+
LOGGER.fatal( "FATAL logs are captured by default" )
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
RubyProf.start
|
|
24
|
+
|
|
25
|
+
demo = WithTheRubyProfiler.new
|
|
26
|
+
1000.times do
|
|
27
|
+
demo.run
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
result = RubyProf.stop
|
|
31
|
+
|
|
32
|
+
printer = RubyProf::GraphPrinter.new(result)
|
|
33
|
+
printer.print(STDOUT)
|
data/lib/catamaran/log_level.rb
CHANGED
|
@@ -41,6 +41,29 @@ module Catamaran
|
|
|
41
41
|
IO_LESS_CRITICAL_THAN_DEBUG => 'IO'
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
STRING_TO_SEVERITY_MAP = {
|
|
45
|
+
'TRACE' => TRACE,
|
|
46
|
+
'DEBUG' => DEBUG,
|
|
47
|
+
'INFO' => INFO,
|
|
48
|
+
'NOTICE' => NOTICE,
|
|
49
|
+
'WARN' => WARN,
|
|
50
|
+
'ERROR' => ERROR,
|
|
51
|
+
'SEVERE' => SEVERE,
|
|
52
|
+
'FATAL' => FATAL
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
SYMBOL_TO_SEVERITY_MAP = {
|
|
56
|
+
:trace => TRACE,
|
|
57
|
+
:debug => DEBUG,
|
|
58
|
+
:info => INFO,
|
|
59
|
+
:notice => NOTICE,
|
|
60
|
+
:warn => WARN,
|
|
61
|
+
:error => ERROR,
|
|
62
|
+
:severe => SEVERE,
|
|
63
|
+
:fatal => FATAL
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
44
67
|
def self.reset
|
|
45
68
|
self.send( :remove_const, 'IO' ) if self.const_defined?( 'IO' )
|
|
46
69
|
self.const_set( 'IO', IO_LESS_CRITICAL_THAN_INFO )
|
|
@@ -53,7 +76,13 @@ module Catamaran
|
|
|
53
76
|
SEVERITY_TO_STRING_MAP[severity] || ''
|
|
54
77
|
end
|
|
55
78
|
|
|
79
|
+
def self.string_to_severity( str )
|
|
80
|
+
str ? STRING_TO_SEVERITY_MAP[str.strip.upcase] : nil
|
|
81
|
+
end
|
|
56
82
|
|
|
83
|
+
def self.symbol_to_severity( sym )
|
|
84
|
+
sym ? SYMBOL_TO_SEVERITY_MAP[sym] : nil
|
|
85
|
+
end
|
|
57
86
|
|
|
58
87
|
def self.log_level_io=( value )
|
|
59
88
|
self.send( :remove_const, 'IO' ) if self.const_defined?( 'IO' )
|
data/lib/catamaran/logger.rb
CHANGED
|
@@ -16,9 +16,16 @@ module Catamaran
|
|
|
16
16
|
@log_level
|
|
17
17
|
elsif self.parent.nil?
|
|
18
18
|
# No parent means this logger(self) is the root logger. So use the default log level
|
|
19
|
-
|
|
19
|
+
if !@memoized_log_level
|
|
20
|
+
if ENV['CATAMARAN_ROOT_LOGGER_LOG_LEVEL']
|
|
21
|
+
@memoized_log_level = LogLevel.string_to_severity( ENV['CATAMARAN_ROOT_LOGGER_LOG_LEVEL'] )
|
|
22
|
+
else
|
|
23
|
+
@memoized_log_level = Catamaran::LogLevel::NOTICE
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
20
27
|
# Implicit return
|
|
21
|
-
|
|
28
|
+
@memoized_log_level
|
|
22
29
|
else
|
|
23
30
|
recursive = ( opts && ( opts[:recursive] == true || opts[:be_populated] == true ) )
|
|
24
31
|
if recursive == true
|
|
@@ -50,8 +57,21 @@ module Catamaran
|
|
|
50
57
|
##
|
|
51
58
|
# Setter used to explicitly set the log level for this logger
|
|
52
59
|
|
|
60
|
+
|
|
61
|
+
|
|
53
62
|
def log_level=( value )
|
|
54
|
-
|
|
63
|
+
if value
|
|
64
|
+
if value.kind_of?( Symbol )
|
|
65
|
+
@log_level = LogLevel.symbol_to_severity( value )
|
|
66
|
+
elsif value.kind_of?( String )
|
|
67
|
+
@log_level = LogLevel.string_to_severity( value )
|
|
68
|
+
else
|
|
69
|
+
@log_level = value
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
@log_level = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
55
75
|
@memoized_log_level = nil
|
|
56
76
|
end
|
|
57
77
|
|
|
@@ -313,7 +333,7 @@ module Catamaran
|
|
|
313
333
|
|
|
314
334
|
|
|
315
335
|
if path
|
|
316
|
-
method_names = path.split(
|
|
336
|
+
method_names = path.split(/\.|\:\:/)
|
|
317
337
|
|
|
318
338
|
method_names.each do |method_name|
|
|
319
339
|
current_logger = current_logger.send( method_name.to_sym )
|
data/lib/catamaran/version.rb
CHANGED
|
@@ -11,6 +11,9 @@ Catamaran.logger.log_level = Catamaran::LogLevel::DEBUG
|
|
|
11
11
|
# There is a performance hit for using the caller, but it generates more detailed logs
|
|
12
12
|
Catamaran::Manager.formatter_caller_enabled = true
|
|
13
13
|
|
|
14
|
+
# Uncomment if you'd prefer Ruby-style delimiters
|
|
15
|
+
Catamaran::Manager.delimiter = '::'
|
|
16
|
+
|
|
14
17
|
# Uncomment to enable Catamaran internal debugging
|
|
15
18
|
# Catamaran::debugging = true
|
|
16
19
|
|
data/spec/catamaran_spec.rb
CHANGED
|
@@ -48,23 +48,75 @@ describe Catamaran do
|
|
|
48
48
|
logger2.object_id.should == logger.object_id
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
context "when using method_missing" do
|
|
52
|
+
it "should create a new loggers for each point in the path" do
|
|
53
|
+
Catamaran.logger
|
|
54
|
+
Catamaran::Manager.num_loggers.should == 1
|
|
55
|
+
Catamaran.logger.com.mycompany.myrailsproject.app.models.User
|
|
56
|
+
Catamaran::Manager.num_loggers.should == 7
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
context "when using a specified string and period as the delimiter" do
|
|
61
|
+
it "should create a new loggers for each point in the path" do
|
|
62
|
+
Catamaran.logger
|
|
63
|
+
Catamaran::Manager.num_loggers.should == 1
|
|
64
|
+
Catamaran.logger( "com.mycompany.myrailsproject.app.models.User" )
|
|
65
|
+
Catamaran::Manager.num_loggers.should == 7
|
|
66
|
+
end
|
|
61
67
|
end
|
|
62
68
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
context "when using a specified string and double-colons as the delimiter" do
|
|
70
|
+
it "should create a new loggers for each point in the path" do
|
|
71
|
+
Catamaran.logger
|
|
72
|
+
Catamaran::Manager.num_loggers.should == 1
|
|
73
|
+
Catamaran.logger( "com::mycompany::myrailsproject::app::models::User" )
|
|
74
|
+
Catamaran::Manager.num_loggers.should == 7
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "using log level constants" do
|
|
79
|
+
it "should be possible for the user to modify the log level of root logger" do
|
|
80
|
+
Catamaran.logger.log_level = Catamaran::LogLevel::TRACE
|
|
81
|
+
Catamaran.logger.log_level.should == Catamaran::LogLevel::TRACE
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "should be possible for the user to modify the log level of non-root loggers" do
|
|
85
|
+
Catamaran.logger.whatever.log_level.should be_nil
|
|
86
|
+
Catamaran.logger.whatever.log_level = Catamaran::LogLevel::TRACE
|
|
87
|
+
Catamaran.logger.whatever.log_level.should == Catamaran::LogLevel::TRACE
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
context "using symbols" do
|
|
92
|
+
it "should be possible for the user to modify the log level of root logger" do
|
|
93
|
+
Catamaran.logger.log_level = :trace
|
|
94
|
+
Catamaran.logger.log_level.should == Catamaran::LogLevel::TRACE
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "should be possible for the user to modify the log level of non-root loggers" do
|
|
98
|
+
Catamaran.logger.whatever.log_level.should be_nil
|
|
99
|
+
Catamaran.logger.whatever.log_level = :trace
|
|
100
|
+
Catamaran.logger.whatever.log_level.should == Catamaran::LogLevel::TRACE
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
context "using strings" do
|
|
105
|
+
it "should be possible for the user to modify the log level of root logger" do
|
|
106
|
+
Catamaran.logger.log_level = 'trace'
|
|
107
|
+
Catamaran.logger.log_level.should == Catamaran::LogLevel::TRACE
|
|
108
|
+
Catamaran.logger.log_level = 'deBuG '
|
|
109
|
+
Catamaran.logger.log_level.should == Catamaran::LogLevel::DEBUG
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it "should be possible for the user to modify the log level of non-root loggers" do
|
|
113
|
+
Catamaran.logger.whatever.log_level.should be_nil
|
|
114
|
+
Catamaran.logger.whatever.log_level = ' TRACe '
|
|
115
|
+
Catamaran.logger.whatever.log_level.should == Catamaran::LogLevel::TRACE
|
|
116
|
+
Catamaran.logger.whatever.log_level = ' INFO '
|
|
117
|
+
Catamaran.logger.whatever.log_level.should == Catamaran::LogLevel::INFO
|
|
118
|
+
end
|
|
119
|
+
end
|
|
68
120
|
|
|
69
121
|
it "should not provide the same object ID for different paths" do
|
|
70
122
|
Catamaran.logger.A.C.object_id.should_not == Catamaran.logger.B.C
|
|
@@ -85,6 +137,25 @@ describe Catamaran do
|
|
|
85
137
|
Catamaran.logger.log_level.should == Catamaran::LogLevel::NOTICE
|
|
86
138
|
end
|
|
87
139
|
|
|
140
|
+
it "should make use of the environment variable when specified" do
|
|
141
|
+
initial_log_level = Catamaran.logger.log_level
|
|
142
|
+
|
|
143
|
+
ENV['CATAMARAN_ROOT_LOGGER_LOG_LEVEL'] = 'ERROR'
|
|
144
|
+
Catamaran::Manager.forget_memoizations
|
|
145
|
+
Catamaran.logger.log_level.should == Catamaran::LogLevel::ERROR
|
|
146
|
+
|
|
147
|
+
ENV.delete( 'CATAMARAN_ROOT_LOGGER_LOG_LEVEL' )
|
|
148
|
+
Catamaran::Manager.forget_memoizations
|
|
149
|
+
initial_log_level.should == Catamaran::LogLevel::NOTICE
|
|
150
|
+
|
|
151
|
+
ENV['CATAMARAN_ROOT_LOGGER_LOG_LEVEL'] = ' error '
|
|
152
|
+
Catamaran::Manager.forget_memoizations
|
|
153
|
+
Catamaran.logger.log_level.should == Catamaran::LogLevel::ERROR
|
|
154
|
+
|
|
155
|
+
ENV.delete( 'CATAMARAN_ROOT_LOGGER_LOG_LEVEL' )
|
|
156
|
+
Catamaran::Manager.forget_memoizations
|
|
157
|
+
end
|
|
158
|
+
|
|
88
159
|
it "should have a backtrace log level set to WARN" do
|
|
89
160
|
Catamaran.logger.backtrace_log_level.should == Catamaran::LogLevel::WARN
|
|
90
161
|
end
|
|
@@ -325,6 +396,19 @@ describe Catamaran do
|
|
|
325
396
|
Catamaran.logger.debug( "And INFO log should NOT be received" )
|
|
326
397
|
end
|
|
327
398
|
|
|
399
|
+
describe "#path_to_s" do
|
|
400
|
+
it "should use a period as the default delimiter" do
|
|
401
|
+
logger = Catamaran.logger.com.mycompany.myrailsproject.app.models.User
|
|
402
|
+
logger.path_to_s.should == "com.mycompany.myrailsproject.app.models.User"
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
it "should be capable of using a user-specified delimiter" do
|
|
406
|
+
Catamaran::Manager.delimiter = "::"
|
|
407
|
+
logger = Catamaran.logger.App.Model.User
|
|
408
|
+
logger.path_to_s.should == "App::Model::User"
|
|
409
|
+
end
|
|
410
|
+
end
|
|
411
|
+
|
|
328
412
|
describe "#forget_memoizations" do
|
|
329
413
|
it "should forget all the memoized log levels" do
|
|
330
414
|
Catamaran.logger.log_level = Catamaran::LogLevel::ERROR
|
metadata
CHANGED
|
@@ -1,25 +1,37 @@
|
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: catamaran
|
|
3
|
-
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
|
-
authors:
|
|
6
|
+
authors:
|
|
7
7
|
- Jeano
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
date: 2014-01-08 00:00:00 -06:00
|
|
13
|
+
default_executable:
|
|
12
14
|
dependencies: []
|
|
15
|
+
|
|
13
16
|
description: A logging utility
|
|
14
|
-
email:
|
|
17
|
+
email:
|
|
15
18
|
- catamaran@jeano.net
|
|
16
19
|
executables: []
|
|
20
|
+
|
|
17
21
|
extensions: []
|
|
22
|
+
|
|
18
23
|
extra_rdoc_files: []
|
|
19
|
-
|
|
24
|
+
|
|
25
|
+
files:
|
|
20
26
|
- .gitignore
|
|
27
|
+
- LICENSE
|
|
21
28
|
- README.md
|
|
22
29
|
- catamaran.gemspec
|
|
30
|
+
- examples/benchmarking_conditional_log_statements.rb
|
|
31
|
+
- examples/benchmarking_the_caller.rb
|
|
32
|
+
- examples/quickstart_with_ruby.rb
|
|
33
|
+
- examples/with_double_colon_delimiter.rb
|
|
34
|
+
- examples/with_the_ruby_profiler.rb
|
|
23
35
|
- init.rb
|
|
24
36
|
- lib/catamaran.rb
|
|
25
37
|
- lib/catamaran/formatter.rb
|
|
@@ -38,27 +50,33 @@ files:
|
|
|
38
50
|
- lib/generators/catamaran/templates/catamaran/test.rb
|
|
39
51
|
- spec/catamaran_spec.rb
|
|
40
52
|
- spec/spec_helper.rb
|
|
53
|
+
has_rdoc: true
|
|
41
54
|
homepage: http://github.com/jgithub/catamaran
|
|
42
|
-
licenses:
|
|
43
|
-
|
|
55
|
+
licenses:
|
|
56
|
+
- MIT
|
|
44
57
|
post_install_message:
|
|
45
58
|
rdoc_options: []
|
|
46
|
-
|
|
59
|
+
|
|
60
|
+
require_paths:
|
|
47
61
|
- lib
|
|
48
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
|
-
requirements:
|
|
50
|
-
- -
|
|
51
|
-
- !ruby/object:Gem::Version
|
|
52
|
-
version:
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: "0"
|
|
67
|
+
version:
|
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: "0"
|
|
73
|
+
version:
|
|
58
74
|
requirements: []
|
|
75
|
+
|
|
59
76
|
rubyforge_project:
|
|
60
|
-
rubygems_version:
|
|
77
|
+
rubygems_version: 1.3.5
|
|
61
78
|
signing_key:
|
|
62
|
-
specification_version:
|
|
79
|
+
specification_version: 3
|
|
63
80
|
summary: Catamaran Logger
|
|
64
81
|
test_files: []
|
|
82
|
+
|
checksums.yaml
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
SHA1:
|
|
3
|
-
metadata.gz: b7cd46193a36dacdc5ed0f8c6ce8c95c0a81e256
|
|
4
|
-
data.tar.gz: 80e278d4ebd3d9c8640809aa0748e36ace294322
|
|
5
|
-
SHA512:
|
|
6
|
-
metadata.gz: bbb1d0824624d9f21ccb207c27a05ec0dd20132a8a2bd7f77d34971a579aa6cd05cb53a4610500d2296047d535819d974872923ce0d5fcacf3f8bc6d848de12c
|
|
7
|
-
data.tar.gz: d51f7a5b1e9cb1295b431baec9280c701557ecdf550e942937a85694588e48dbfcc5a9f2d16fa9cba8f0683aa3e208f672c79a76a1f9e695991d0feb208d18b5
|