rack-key_value_logger 0.3.1
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/.gitignore +1 -0
- data/Gemfile +12 -0
- data/HISTORY.md +4 -0
- data/MIT-LICENSE +21 -0
- data/README.md +56 -0
- data/Rakefile +5 -0
- data/lib/key_value_logger/version.rb +5 -0
- data/lib/rack/key_value_logger.rb +147 -0
- data/lib/rack-key_value_logger.rb +1 -0
- data/rack-key_value_logger.gemspec +24 -0
- data/spec/integration/sinatra_spec.rb +44 -0
- data/spec/rack/key_value_logger_spec.rb +124 -0
- data/spec/spec_helper.rb +42 -0
- metadata +94 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Gemfile.lock
|
data/Gemfile
ADDED
data/HISTORY.md
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2012 Alex Sharp
|
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 SHALL 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.
|
21
|
+
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
[](http://travis-ci.org/zaarly/rack-key_value_logger)
|
2
|
+
|
3
|
+
## What
|
4
|
+
|
5
|
+
Structured, key-value logging for your rack apps. Inspired by [lograge](https://github.com/roidrage/lograge).
|
6
|
+
|
7
|
+
## Why?
|
8
|
+
|
9
|
+
Application logs are an incredibly rich source of information. But digging out
|
10
|
+
the information can be extremely painful if your logs are not structured in an
|
11
|
+
easily parsable manner.
|
12
|
+
|
13
|
+
`Rack::KeyValueLogger` logs key-value pairs, where the key and value are
|
14
|
+
separated by a "=" character. Pairs are separated by pipe ("|") characters.
|
15
|
+
Here's an example of what a log line looks like:
|
16
|
+
|
17
|
+
```
|
18
|
+
[1351714706 2012-10-31 20:18:26 UTC] method=GET|url=/homepage|params=page=2|user_id=123|scheme=http|user_agent=curl/7.24.0 (x86_64-apple-darwin12.0) libcurl/7.24.0 OpenSSL/0.9.8r zlib/1.2.5|remote_ip=127.0.0.1|http_version=HTTP/1.1|requested_content_type=text/html|log_source=key_value_logger|status=200|content-length=111|content_type=text/html|runtime=21.553
|
19
|
+
```
|
20
|
+
|
21
|
+
## Get Started
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'rack-key_value_logger'
|
25
|
+
```
|
26
|
+
|
27
|
+
#### Sinatra
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
class MyApp < Sinatra::Base
|
31
|
+
use Rack::KeyValueLogger
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
#### Rails
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
module MyApp
|
39
|
+
class Application < Rails::Application
|
40
|
+
# ...
|
41
|
+
config.middleware.use "Rack::KeyValueLogger"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
## Configuration
|
47
|
+
|
48
|
+
A number of configuration options are supported when adding
|
49
|
+
`Rack::KeyValueLogger` to the middleware stack.
|
50
|
+
|
51
|
+
* `:log_failure_response_bodies` - `true` or `false`. Logs the entire response
|
52
|
+
body to the `response_body` key on 40x responses. Defaults to `false`.
|
53
|
+
* `:ignore_paths` - A regular expression of paths we should not log.
|
54
|
+
* `:logger` - A `Logger` instance. Defaults to `Logger($stdout)`.
|
55
|
+
* `:user_id` - A string key at which the current user's id is stored in
|
56
|
+
`env["rack.session"]`. Defaults to "user_id".
|
data/Rakefile
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
module Rack
|
5
|
+
class KeyValueLogger
|
6
|
+
SEPARATOR = "|"
|
7
|
+
|
8
|
+
attr_reader :msg, :logger, :opts, :ignore_paths
|
9
|
+
|
10
|
+
# @example Preventing rails assets from being logged
|
11
|
+
# use Rack::KeyValueLogger, :ignore_paths => /\/assets/
|
12
|
+
#
|
13
|
+
# @example Logging non-success response bodies
|
14
|
+
# use Rack::KeyValueLogger, :log_failure_response_bodies => true
|
15
|
+
# # NOTE: Most fields below have been omitted for brevity and replaced with "..."
|
16
|
+
# # => [1351712789 2012-10-31 19:46:29 UTC] method=GET|url=/422|params=|...|response_body=["{\"errors\"=>{\"key\"=>\"val\"}}"]|runtime=0.07
|
17
|
+
#
|
18
|
+
# @param opts
|
19
|
+
# @option opts :logger A logger instance. Defaults to logging to $stdout.
|
20
|
+
# @option opts :log_failure_response_bodies Set to `true` to log response
|
21
|
+
# bodies for non-success codes. Defaults to false.
|
22
|
+
# @option opts :user_id a string key representing the user id key.
|
23
|
+
# Defaults to 'user_id'
|
24
|
+
# @option opts :ignore_paths a regular expression indicating url paths we don't want to
|
25
|
+
# in the session hash.
|
26
|
+
def initialize(app, opts = {})
|
27
|
+
@app, @opts = app, opts
|
28
|
+
@logger = @opts[:logger] || ::Logger.new($stdout)
|
29
|
+
@opts[:log_failure_response_bodies] ||= false
|
30
|
+
@opts[:user_id] ||= 'user_id'
|
31
|
+
@ignore_paths = @opts[:ignore_paths]
|
32
|
+
end
|
33
|
+
|
34
|
+
# Logs key=value pairs of useful information about the request and
|
35
|
+
# response to the a log. We either piggy-back off the
|
36
|
+
# env['rack.logger'] or we log to $stdout.
|
37
|
+
#
|
38
|
+
# Components
|
39
|
+
# * session - session hash, json-encoded
|
40
|
+
# * accept - Accept-Encoding request header
|
41
|
+
# * user-agent - User agent string
|
42
|
+
# * request-time - in seconds since epoch
|
43
|
+
# * method - request method
|
44
|
+
# * status - response status code
|
45
|
+
# * url - the url, without query string
|
46
|
+
# * query-string - query string params
|
47
|
+
# * user-id - user's id
|
48
|
+
# * scheme - http or https
|
49
|
+
# * content-length - length in bytes of the response body
|
50
|
+
# * requested-content-type
|
51
|
+
# * content-type - Content-Type response header
|
52
|
+
# * remote-ip - User's ip address
|
53
|
+
# * runtime - Duration of request in milliseconds
|
54
|
+
# * http-version - http version of the client
|
55
|
+
# * mobile-device - the mobile device return by rack-mobile-detect
|
56
|
+
def call(env)
|
57
|
+
@msg = []
|
58
|
+
start = Time.now
|
59
|
+
request = Rack::Request.new(env)
|
60
|
+
user_id = env['rack.session'] && env['rack.session'][opts[:user_id]]
|
61
|
+
mobile_device = env['X_MOBILE_DEVICE']
|
62
|
+
url = request.path
|
63
|
+
query_string = env['QUERY_STRING']
|
64
|
+
|
65
|
+
if ignored_path?(url)
|
66
|
+
return @app.call(env)
|
67
|
+
end
|
68
|
+
|
69
|
+
# record request attributes
|
70
|
+
msg << "method=#{request.request_method}"
|
71
|
+
msg << "url=#{url}"
|
72
|
+
msg << "params=#{query_string}"
|
73
|
+
msg << "user_id=#{user_id}"
|
74
|
+
msg << "scheme=#{request.scheme}"
|
75
|
+
msg << "user_agent=#{request.user_agent}"
|
76
|
+
msg << "remote_ip=#{request.ip}"
|
77
|
+
msg << "http_version=#{env['HTTP_VERSION']}"
|
78
|
+
msg << "mobile_device=#{mobile_device}" if mobile_device
|
79
|
+
msg << "requested_content_type=#{request.content_type}"
|
80
|
+
msg << "log_source=key_value_logger"
|
81
|
+
|
82
|
+
begin
|
83
|
+
status, headers, body = @app.call(env)
|
84
|
+
|
85
|
+
record_response_attributes(status, headers, body)
|
86
|
+
rescue => e
|
87
|
+
msg << 'status=500'
|
88
|
+
raise e
|
89
|
+
ensure
|
90
|
+
record_runtime(start)
|
91
|
+
|
92
|
+
# Don't log Rack::Cascade fake 404's
|
93
|
+
flush_log unless rack_cascade_404?(headers)
|
94
|
+
end
|
95
|
+
|
96
|
+
[status, headers, body]
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# Returns true if the passed in `url` argument matches the `ignore_paths`
|
102
|
+
# attribute. Return false otherwise, or if `ignore_paths` is not set.
|
103
|
+
#
|
104
|
+
# @param [String] url a url path
|
105
|
+
# @return [Boolean]
|
106
|
+
def ignored_path?(url)
|
107
|
+
return false if ignore_paths.nil?
|
108
|
+
url =~ ignore_paths
|
109
|
+
end
|
110
|
+
|
111
|
+
def record_runtime(start)
|
112
|
+
msg << "runtime=#{((Time.now - start) * 1000).round(5)}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Flush `msg` to the logger instance.
|
116
|
+
def flush_log
|
117
|
+
result = msg.join(SEPARATOR)
|
118
|
+
now = Time.now
|
119
|
+
result = "[#{now.to_i} #{now.utc}] " + result
|
120
|
+
|
121
|
+
logger.info result
|
122
|
+
end
|
123
|
+
|
124
|
+
def record_response_attributes(status, headers, body)
|
125
|
+
msg << "status=#{status}"
|
126
|
+
msg << "content-length=#{headers['Content-Length']}"
|
127
|
+
msg << "content_type=#{headers['Content-Type']}"
|
128
|
+
|
129
|
+
if status.to_s =~ /^4[0-9]{2}/ && opts[:log_failure_response_bodies]
|
130
|
+
response = Rack::Response.new(body, status, headers)
|
131
|
+
msg << "response_body=#{MultiJson.encode(response.body)}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Sinatra adds a "X-Cascade" header with a value of "pass" to the response.
|
136
|
+
# This makes it possible to detect whether this is a 404 worth logging,
|
137
|
+
# or just a Rack::Cascade 404.
|
138
|
+
#
|
139
|
+
# @param [Hash, nil] headers the headers hash from the response
|
140
|
+
# @return [Boolean]
|
141
|
+
def rack_cascade_404?(headers)
|
142
|
+
return false if headers.nil?
|
143
|
+
cascade_header = headers['X-Cascade']
|
144
|
+
cascade_header && cascade_header == 'pass'
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rack/key_value_logger'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "key_value_logger/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rack-key_value_logger"
|
7
|
+
s.version = Rack::KeyValueLogger::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Alex Sharp"]
|
10
|
+
s.email = ["ajsharp@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/zaarly/rack-key_value_logger"
|
12
|
+
s.summary = %q{Structured, key-value logging for your rack apps.}
|
13
|
+
s.description = %q{Structured, key-value logging for your rack apps. Inspired by lograge.}
|
14
|
+
|
15
|
+
s.rubyforge_project = s.name
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency 'multi_json'
|
23
|
+
s.add_dependency 'rack'
|
24
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module SinatraTest
|
4
|
+
describe 'ignoring rack cascade 404s' do
|
5
|
+
DRAIN = StringIO.new
|
6
|
+
LOGGER = Logger.new(DRAIN)
|
7
|
+
|
8
|
+
before do
|
9
|
+
DRAIN.truncate(0)
|
10
|
+
end
|
11
|
+
let(:drain) { DRAIN } # for shared helpers
|
12
|
+
|
13
|
+
class Base < Sinatra::Base
|
14
|
+
use Rack::KeyValueLogger, :logger => LOGGER
|
15
|
+
end
|
16
|
+
|
17
|
+
class FirstApp < Base
|
18
|
+
get('/other') { status(200) }
|
19
|
+
end
|
20
|
+
|
21
|
+
class SecondApp < Base
|
22
|
+
get('/success') { status(200) }
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:app) do
|
26
|
+
Rack::Builder.app do
|
27
|
+
run Rack::Cascade.new([FirstApp, SecondApp])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
before do
|
32
|
+
do_get '/success'
|
33
|
+
end
|
34
|
+
|
35
|
+
it_behaves_like 'it logs', 'status', '200'
|
36
|
+
it_behaves_like 'it logs', 'url', '/success'
|
37
|
+
it_behaves_like 'it does not log', 'status', '404'
|
38
|
+
|
39
|
+
it 'should only log one entry' do
|
40
|
+
drain.rewind
|
41
|
+
drain.lines.count.should == 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
describe "logging non success response bodies" do
|
5
|
+
let(:logger) { Logger.new(drain) }
|
6
|
+
let(:drain) { StringIO.new }
|
7
|
+
|
8
|
+
let(:app) do
|
9
|
+
a = lambda do |env|
|
10
|
+
case env['PATH_INFO']
|
11
|
+
when '/200'
|
12
|
+
[200, default_test_headers, ['Success']]
|
13
|
+
when '/422'
|
14
|
+
[422, default_test_headers, [{'errors' => {'key' => 'val'}}]]
|
15
|
+
when '/401'
|
16
|
+
[401, default_test_headers, ['Unauthorized']]
|
17
|
+
when '/400'
|
18
|
+
[400, default_test_headers, ['Fail']]
|
19
|
+
when '/500'
|
20
|
+
raise "oh noez!"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
log = logger # hold scope out of the block
|
25
|
+
Rack::Builder.app do
|
26
|
+
use Rack::KeyValueLogger, :log_failure_response_bodies => true, :logger => log
|
27
|
+
run a
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should clear the msg attr out after each log line" do
|
32
|
+
do_get('/200')
|
33
|
+
do_get('/401')
|
34
|
+
drain.rewind
|
35
|
+
drain.read.scan('method').size.should == 2
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'when the proper option is passed in' do
|
39
|
+
it "logs the response body for 401's" do
|
40
|
+
do_get('/401')
|
41
|
+
drain.should include_entry "response_body=.*Unauthorized.*"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "logs the response body for 400's" do
|
45
|
+
do_get('/400')
|
46
|
+
drain.should include_entry "response_body=.*Fail.*"
|
47
|
+
end
|
48
|
+
|
49
|
+
it "logs the response body for 422's" do
|
50
|
+
do_get('/422')
|
51
|
+
drain.should include_entry 'response_body=.*errors.*'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'a 200 response' do
|
56
|
+
before do
|
57
|
+
do_get('/200')
|
58
|
+
end
|
59
|
+
|
60
|
+
it_behaves_like "it logs", 'status', 200
|
61
|
+
|
62
|
+
it 'does not log the response body for success endpoints' do
|
63
|
+
drain.should_not include_entry 'response_body=Unauthorized'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'a 400 bad request response' do
|
68
|
+
before do
|
69
|
+
do_get('/400')
|
70
|
+
end
|
71
|
+
|
72
|
+
it_behaves_like 'it logs', 'status', 400
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'an unexpected 500 response' do
|
76
|
+
before do
|
77
|
+
begin
|
78
|
+
do_get('/500')
|
79
|
+
rescue => e
|
80
|
+
# raise other exceptions
|
81
|
+
raise e unless e.message == 'oh noez!'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it_behaves_like 'it logs', 'url', '/500'
|
86
|
+
it_behaves_like 'it logs', 'status', 500
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe "ignoring certain paths" do
|
91
|
+
let(:logger) { Logger.new(drain) }
|
92
|
+
let(:drain) { StringIO.new }
|
93
|
+
|
94
|
+
let(:app) do
|
95
|
+
ignore_app = lambda do |env|
|
96
|
+
case env['PATH_INFO']
|
97
|
+
when '/ignore'
|
98
|
+
[200, default_test_headers, ['ignore me!']]
|
99
|
+
when '/do-not-ignore'
|
100
|
+
[200, default_test_headers, ["don't ignore me!"]]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
log = logger
|
105
|
+
Rack::Builder.app do
|
106
|
+
use Rack::KeyValueLogger, :logger => log, :ignore_paths => /^\/ignore/
|
107
|
+
run ignore_app
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'does not log anything for ignored paths' do
|
112
|
+
do_get('/ignore')
|
113
|
+
drain.should_not include_entry "url=/ignore"
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'logging non-ignored paths' do
|
117
|
+
before do
|
118
|
+
do_get '/do-not-ignore'
|
119
|
+
end
|
120
|
+
|
121
|
+
it_behaves_like 'it logs', 'status', '200'
|
122
|
+
it_behaves_like 'it logs', 'url', '/do-not-ignore'
|
123
|
+
end
|
124
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rspec'
|
3
|
+
require 'rack'
|
4
|
+
require 'sinatra/base'
|
5
|
+
|
6
|
+
$:.push(File.expand_path(File.dirname(__FILE__)))
|
7
|
+
$:.push(File.expand_path(File.dirname(__FILE__)) + '/../lib')
|
8
|
+
|
9
|
+
require 'rack-key_value_logger'
|
10
|
+
require 'rack/key_value_logger'
|
11
|
+
require 'debugger'
|
12
|
+
|
13
|
+
RSpec.configure do |c|
|
14
|
+
def do_get(url)
|
15
|
+
Rack::MockRequest.new(app).get(url)
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_test_headers
|
19
|
+
{'Content-Type' => 'text/plain'}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# @example
|
24
|
+
# $drain.should include_entry 'status=500'
|
25
|
+
RSpec::Matchers.define :include_entry do |expected|
|
26
|
+
match do |actual|
|
27
|
+
actual.rewind
|
28
|
+
!!actual.detect { |l| l =~ /#{expected}/ }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples 'it logs' do |field, value|
|
33
|
+
it "logs #{field} = #{value}" do
|
34
|
+
drain.should include_entry "#{field}=#{value}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
shared_examples 'it does not log' do |field, value|
|
39
|
+
it "does not log #{field} = #{value}" do
|
40
|
+
drain.should_not include_entry "#{field}=#{value}"
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rack-key_value_logger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Alex Sharp
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-11-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: multi_json
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rack
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: Structured, key-value logging for your rack apps. Inspired by lograge.
|
47
|
+
email:
|
48
|
+
- ajsharp@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- HISTORY.md
|
56
|
+
- MIT-LICENSE
|
57
|
+
- README.md
|
58
|
+
- Rakefile
|
59
|
+
- lib/key_value_logger/version.rb
|
60
|
+
- lib/rack-key_value_logger.rb
|
61
|
+
- lib/rack/key_value_logger.rb
|
62
|
+
- rack-key_value_logger.gemspec
|
63
|
+
- spec/integration/sinatra_spec.rb
|
64
|
+
- spec/rack/key_value_logger_spec.rb
|
65
|
+
- spec/spec_helper.rb
|
66
|
+
homepage: https://github.com/zaarly/rack-key_value_logger
|
67
|
+
licenses: []
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project: rack-key_value_logger
|
86
|
+
rubygems_version: 1.8.24
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Structured, key-value logging for your rack apps.
|
90
|
+
test_files:
|
91
|
+
- spec/integration/sinatra_spec.rb
|
92
|
+
- spec/rack/key_value_logger_spec.rb
|
93
|
+
- spec/spec_helper.rb
|
94
|
+
has_rdoc:
|