rack-logjam 0.1.0 → 0.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.
- checksums.yaml +4 -4
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/README.markdown +142 -7
- data/lib/rack/logjam/ansi.rb +0 -4
- data/lib/rack/logjam/configuration.rb +19 -0
- data/lib/rack/logjam/filters/base.rb +23 -0
- data/lib/rack/logjam/filters/json.rb +55 -0
- data/lib/rack/logjam/filters/nil.rb +13 -0
- data/lib/rack/logjam/filters.rb +26 -0
- data/lib/rack/logjam/formatters/array.rb +13 -0
- data/lib/rack/logjam/formatters/base.rb +23 -0
- data/lib/rack/logjam/formatters/empty.rb +13 -0
- data/lib/rack/logjam/formatters/form_urlencoded.rb +13 -0
- data/lib/rack/logjam/formatters/json.rb +16 -0
- data/lib/rack/logjam/formatters/nil.rb +13 -0
- data/lib/rack/logjam/formatters/text_plain.rb +13 -0
- data/lib/rack/logjam/formatters/xml.rb +17 -0
- data/lib/rack/logjam/formatters.rb +33 -0
- data/lib/rack/logjam/grape/middleware.rb +88 -0
- data/lib/rack/logjam/grape.rb +9 -0
- data/lib/rack/logjam/logger.rb +94 -0
- data/lib/rack/logjam/rails/middleware.rb +52 -0
- data/lib/rack/logjam/rails.rb +9 -0
- data/lib/rack/logjam/version.rb +1 -1
- data/lib/rack/logjam.rb +29 -5
- data/rack-logjam.gemspec +3 -3
- metadata +35 -15
- data/lib/rack/logjam/middleware.rb +0 -149
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 100c480a6bdd47bb50ebbacbde5f69019c7c386c
|
|
4
|
+
data.tar.gz: 4e737fc841d55db1bdd4c06b2968890e746ca230
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7d2b976933f0ad9827c1fb5521813b79b0d8b852db8becfb9a03d87d839e8c0cc74406ab1d8263832ec78909d5b5a5ac5109522b4385b594666d071ea182b15e
|
|
7
|
+
data.tar.gz: d5422d0782d60ea1181119862976e92488244bfdeac02ad3129821e137cf2dd52ff5f1ee5b391c2b966840f15f5bb9d5739ef78112db30589f5be5224c2606ae
|
data/.ruby-gemset
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rack-logjam
|
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ruby-2.1.0
|
data/README.markdown
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Logs helpful HTTP information on Rack requests.
|
|
4
4
|
|
|
5
|
+
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
7
8
|
Add this line to your application's Gemfile:
|
|
@@ -16,14 +17,148 @@ Or install it yourself as:
|
|
|
16
17
|
|
|
17
18
|
$ gem install rack-logjam
|
|
18
19
|
|
|
20
|
+
|
|
19
21
|
## Usage
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
### Including the Middleware
|
|
24
|
+
|
|
25
|
+
#### Grape
|
|
26
|
+
|
|
27
|
+
Include the middleware in your API classes with the `use` statement
|
|
28
|
+
|
|
29
|
+
module AwesomeApp
|
|
30
|
+
class ApiV1 < Grape::API
|
|
31
|
+
use Rack::Logjam::Grape::Middleware
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
#### Rails
|
|
37
|
+
|
|
38
|
+
Require the middleware in `config/application.rb`
|
|
39
|
+
|
|
40
|
+
config.middleware.use( 'Rack::Logjam::Middleware' )
|
|
41
|
+
|
|
42
|
+
### Configuration
|
|
43
|
+
|
|
44
|
+
At a minimum one must specify the logger that rack-logjam will use.
|
|
45
|
+
|
|
46
|
+
Rack::Logjam.configure |c|
|
|
47
|
+
c.logger = Rails.logger
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#### Formatters
|
|
51
|
+
|
|
52
|
+
Rack::Logjam includes several default formatters that are registered by default: `Array, FormUrlencoded, Json, TextPlain, and Xml`. Formatters
|
|
53
|
+
are registered and selected by mime-types. If a the mime-type of a response does not have a regostered formatter, it will output a message to
|
|
54
|
+
the log in place of the body specifying the lack of formatter and mime-type.
|
|
55
|
+
|
|
56
|
+
Additionally, you can configure custom mime-types with formatters in the configuration file.
|
|
57
|
+
|
|
58
|
+
Rack::Logjam.configure |c|
|
|
59
|
+
# register custom mime-types with inlcuded formatters
|
|
60
|
+
c.register_formatter 'application/vnd.awesome-app-v1+x-www-form-urlencoded', :FormUrlencoded
|
|
61
|
+
c.register_formatter 'application/vnd.awesome-app-v1+json', :Json
|
|
62
|
+
c.register_formatter 'application/vnd.awesome-app-v1+text', :TextPlain
|
|
63
|
+
c.register_formatter 'application/vnd.awesome-app-v1+xml', :Xml
|
|
64
|
+
|
|
65
|
+
# register custom mime-type with custom formatter
|
|
66
|
+
c.register_formatter 'application/vnd.awesome-app-v1+csv', AwesomeApp::LogFormatter::Csv
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
#### Filters
|
|
70
|
+
|
|
71
|
+
Filters can fully fully redact or truncate data within data structures. Filters are registered and selected by mime-types. No filters are
|
|
72
|
+
registered by default. However, Rack::Logjam includes a `Json` filter. The Json filter uses [JsonPath](https://github.com/joshbuddy/jsonpath)
|
|
73
|
+
to specify which attributes are filtered.
|
|
74
|
+
|
|
75
|
+
Additionally, you can configure custom mime-types with filters in the configuration file.
|
|
76
|
+
|
|
77
|
+
Rack::Logjam.configure |c|
|
|
78
|
+
# configure a mime-type with the built in Json filter
|
|
79
|
+
# this filter will truncate all image_as_base64 attributes
|
|
80
|
+
# at any level of nesting and redact the subject/date_of_birth
|
|
81
|
+
c.register_filter 'application/vnd.ncite-vetting-v1+json', :Json, [
|
|
82
|
+
['$..image_as_base64', :truncate, 10],
|
|
83
|
+
['$.subject_attributes.date_of_birth', :redact]
|
|
84
|
+
]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
#### Custom Formatters and Filters
|
|
88
|
+
|
|
89
|
+
##### Writing a Custom Formatter
|
|
90
|
+
|
|
91
|
+
A formatter is a simply a class that accepts content in an initializer and implements a #render method with no parameters. Folowing
|
|
92
|
+
is an implementation of a CSV formatter.
|
|
93
|
+
|
|
94
|
+
module AwesomeApp
|
|
95
|
+
module Formatter
|
|
96
|
+
class Csv
|
|
97
|
+
def initialize( content )
|
|
98
|
+
@content = content
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def render
|
|
102
|
+
content
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
protected
|
|
106
|
+
|
|
107
|
+
attr_reader :content
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
The formatter must also be registered with a mime-type.
|
|
113
|
+
|
|
114
|
+
c.register_formatter 'application/vnd.awesome-app-v1+csv', AwesomeApp::Formatter::Csv
|
|
115
|
+
|
|
116
|
+
##### Writing a Custom Filter
|
|
117
|
+
|
|
118
|
+
A filter is a simply a class that accepts content and filters in an initializer and implements a #render method with no parameters. Folowing
|
|
119
|
+
is a naive implementation of a CSV filter.
|
|
120
|
+
|
|
121
|
+
require 'csv'
|
|
122
|
+
|
|
123
|
+
module AwesomeApp
|
|
124
|
+
module Filters
|
|
125
|
+
class Csv
|
|
126
|
+
def initialize( content, filters )
|
|
127
|
+
@content = content
|
|
128
|
+
@filters = filters
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def render
|
|
132
|
+
CSV.parse("some long bit of text",data,123-45-6789,data") do |row|
|
|
133
|
+
filters.each do |column, action, length|
|
|
134
|
+
row[column] = send( action, row[column], length )
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
protected
|
|
140
|
+
|
|
141
|
+
def redact( val, *args )
|
|
142
|
+
(val.nil? || val.empty?) ?
|
|
143
|
+
val :
|
|
144
|
+
"[REDACTED]"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def truncate( val, length )
|
|
148
|
+
(val.nil? || val.empty?) ?
|
|
149
|
+
val :
|
|
150
|
+
"#{val[0..length]}...[TRUNCATED]..."
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
attr_reader :content,
|
|
154
|
+
:filters
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
22
158
|
|
|
23
|
-
|
|
159
|
+
The filter must also be registered with a mime-type.
|
|
24
160
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
5. Create new Pull Request
|
|
161
|
+
c.register_filter 'application/vnd.awesome-app-v1+xml', AwesomeApp::Filters::Csv, [
|
|
162
|
+
[0, :truncate, 10],
|
|
163
|
+
[2, :redact]
|
|
164
|
+
]
|
data/lib/rack/logjam/ansi.rb
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
module Logjam
|
|
3
|
+
class Configuration
|
|
4
|
+
|
|
5
|
+
def logger=( logger )
|
|
6
|
+
Rack::Logjam::logger = logger
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def register_filter( mime_type, filter_symbol_or_constant, filters )
|
|
10
|
+
Rack::Logjam::Filters.register( mime_type, filter_symbol_or_constant, filters )
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def register_formatter( mime_type, formatter_symbol_or_constant )
|
|
14
|
+
Rack::Logjam::Formatters.register( mime_type, formatter_symbol_or_constant )
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
module Logjam
|
|
3
|
+
module Filters
|
|
4
|
+
class Base
|
|
5
|
+
|
|
6
|
+
def initialize( content, filters )
|
|
7
|
+
@content = content
|
|
8
|
+
@filters = filters
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render
|
|
12
|
+
raise NotImplementedError
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
attr_reader :content,
|
|
18
|
+
:filters
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'jsonpath'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
module Logjam
|
|
5
|
+
module Filters
|
|
6
|
+
class Json < Rack::Logjam::Filters::Base
|
|
7
|
+
|
|
8
|
+
def render
|
|
9
|
+
apply_filters
|
|
10
|
+
json_path.to_json
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
protected
|
|
14
|
+
|
|
15
|
+
def apply_filters
|
|
16
|
+
filters.each do |j_path, action, length|
|
|
17
|
+
json_path.gsub!( j_path ) do |val|
|
|
18
|
+
method_name = "#{action}_#{val.class.name.downcase}"
|
|
19
|
+
if respond_to?( method_name, true )
|
|
20
|
+
send( method_name, val, length )
|
|
21
|
+
else
|
|
22
|
+
"[#{action.upcase}ED: #{val.class.name}]"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def redact_nilclass( val, *args )
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def truncate_nilclass( val, length )
|
|
33
|
+
nil
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def redact_string( val, *args )
|
|
37
|
+
(val.nil? || val.empty?) ?
|
|
38
|
+
val :
|
|
39
|
+
"[REDACTED]"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def truncate_string( val, length )
|
|
43
|
+
(val.nil? || val.empty?) ?
|
|
44
|
+
val :
|
|
45
|
+
"#{val[0..length]}...[TRUNCATED]..."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def json_path
|
|
49
|
+
@json_path ||= ::JsonPath.for( content )
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
module Logjam
|
|
3
|
+
module Filters
|
|
4
|
+
|
|
5
|
+
autoload :Base, 'rack/logjam/filters/base'
|
|
6
|
+
autoload :Json, 'rack/logjam/filters/json'
|
|
7
|
+
autoload :Nil, 'rack/logjam/filters/nil'
|
|
8
|
+
|
|
9
|
+
@registry = {}
|
|
10
|
+
|
|
11
|
+
def self.registry
|
|
12
|
+
@registry
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.register( mime_type, formatter_klass_name, filters )
|
|
16
|
+
registry.merge!( mime_type => [formatter_klass_name, filters] )
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.get( mime_type )
|
|
20
|
+
const_name, filters = registry.fetch( mime_type, [:Nil, []] )
|
|
21
|
+
return self::const_get( const_name ), filters
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
module Logjam
|
|
3
|
+
module Formatters
|
|
4
|
+
class Base
|
|
5
|
+
|
|
6
|
+
def initialize( content, format=nil )
|
|
7
|
+
@content = content
|
|
8
|
+
@format = format
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render
|
|
12
|
+
"You must implement #{self.class.name}#render in order to render a #{format} response"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
protected
|
|
16
|
+
|
|
17
|
+
attr_reader :content,
|
|
18
|
+
:format
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'nokogiri'
|
|
2
|
+
|
|
3
|
+
module Rack
|
|
4
|
+
module Logjam
|
|
5
|
+
module Formatters
|
|
6
|
+
class Xml < ::Rack::Logjam::Formatters::Base
|
|
7
|
+
|
|
8
|
+
def render
|
|
9
|
+
::Nokogiri.XML( content ) do |config|
|
|
10
|
+
config.default_xml.noblanks
|
|
11
|
+
end.to_xml( indent: 2 )
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Rack
|
|
2
|
+
module Logjam
|
|
3
|
+
module Formatters
|
|
4
|
+
|
|
5
|
+
autoload :Array, 'rack/logjam/formatters/array'
|
|
6
|
+
autoload :Base, 'rack/logjam/formatters/base'
|
|
7
|
+
autoload :Empty, 'rack/logjam/formatters/empty'
|
|
8
|
+
autoload :FormUrlencoded, 'rack/logjam/formatters/form_urlencoded'
|
|
9
|
+
autoload :Json, 'rack/logjam/formatters/json'
|
|
10
|
+
autoload :Nil, 'rack/logjam/formatters/nil'
|
|
11
|
+
autoload :TextPlain, 'rack/logjam/formatters/text_plain'
|
|
12
|
+
autoload :Xml, 'rack/logjam/formatters/xml'
|
|
13
|
+
|
|
14
|
+
@registry = {}
|
|
15
|
+
|
|
16
|
+
def self.registry
|
|
17
|
+
@registry
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.register( mime_type, formatter_klass_name )
|
|
21
|
+
registry.merge!( mime_type => formatter_klass_name )
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.get( mime_type )
|
|
25
|
+
const_name = registry.fetch( mime_type, :Nil )
|
|
26
|
+
const_name.is_a?( Class ) ?
|
|
27
|
+
const_name :
|
|
28
|
+
self::const_get( const_name )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#require 'json'
|
|
2
|
+
require 'grape/middleware/base'
|
|
3
|
+
|
|
4
|
+
module Rack
|
|
5
|
+
module Logjam
|
|
6
|
+
module Grape
|
|
7
|
+
class Middleware < ::Grape::Middleware::Base
|
|
8
|
+
|
|
9
|
+
def before
|
|
10
|
+
return unless api_request?( env )
|
|
11
|
+
|
|
12
|
+
logger.log_request( env )
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def after
|
|
16
|
+
return unless api_request?( env )
|
|
17
|
+
|
|
18
|
+
status = @app_response.first
|
|
19
|
+
headers = @app_response[1]
|
|
20
|
+
body = @app_response.last.body.last
|
|
21
|
+
|
|
22
|
+
#logger.log_response( env, status, headers, response )
|
|
23
|
+
logger.log_response( env, status, headers, body )
|
|
24
|
+
|
|
25
|
+
@app_response
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
protected
|
|
29
|
+
|
|
30
|
+
def api_request?( env )
|
|
31
|
+
true
|
|
32
|
+
#path_info( env ) =~ /^\/api\//
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def path_info( env )
|
|
36
|
+
env['PATH_INFO']
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def logger
|
|
40
|
+
Rack::Logjam::Logger.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def request_log_data
|
|
44
|
+
request_data = {
|
|
45
|
+
auth_token: env['HTTP_X_NCITE_AUTH_TOKEN'],
|
|
46
|
+
content_type: content_type,
|
|
47
|
+
content_length: env['CONTENT_LENGTH'],
|
|
48
|
+
accept: env['HTTP_ACCEPT'],
|
|
49
|
+
accept_version: env['HTTP_ACCEPT_VERSION'],
|
|
50
|
+
method: env['REQUEST_METHOD'],
|
|
51
|
+
path: env['PATH_INFO'],
|
|
52
|
+
query: env['QUERY_STRING']
|
|
53
|
+
}
|
|
54
|
+
#request_data[:user_id] = current_user.id if current_user
|
|
55
|
+
request_data
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def response_log_data
|
|
59
|
+
{
|
|
60
|
+
description: env['api.endpoint'].options[:route_options][:description],
|
|
61
|
+
source_file: env['api.endpoint'].block.source_location[0][(::Rails.root.to_s.length+1)..-1],
|
|
62
|
+
source_line: env['api.endpoint'].block.source_location[1]
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def content_type
|
|
67
|
+
env['CONTENT_TYPE']
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def formatted_request_body
|
|
71
|
+
body = env['rack.input'].read
|
|
72
|
+
return ANSI.yellow { 'EMPTY' } if body.blank?
|
|
73
|
+
|
|
74
|
+
if content_type == Mime::JSON.to_s
|
|
75
|
+
return ANSI.cyan { JSON.pretty_generate( JSON.parse( body )) }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def formatted_response_body
|
|
82
|
+
ANSI.cyan { JSON.pretty_generate(JSON.parse( @app_response.last.body.last )) }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require 'jsonpath'
|
|
2
|
+
require 'mime/types'
|
|
3
|
+
|
|
4
|
+
module Rack
|
|
5
|
+
module Logjam
|
|
6
|
+
class Logger
|
|
7
|
+
|
|
8
|
+
def log_request( env )
|
|
9
|
+
_logger.info <<-end_info
|
|
10
|
+
[#{ANSI.green { 'api' }}] #{ANSI.cyan { '--- Request Env ---' }}
|
|
11
|
+
#{ANSI.magenta { JSON.pretty_generate( request_log_data( env )) }}
|
|
12
|
+
[#{ANSI.green { 'api' }}] #{ANSI.cyan { '--- Request Body ---' }}
|
|
13
|
+
#{ANSI.cyan { formatted_request_body( env ) }}
|
|
14
|
+
end_info
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def log_response( env, status, headers, response )
|
|
18
|
+
_logger.info <<-end_info
|
|
19
|
+
[#{ANSI.green { 'api' }}] #{ANSI.cyan { '--- Response ---' }}
|
|
20
|
+
Status: #{status}
|
|
21
|
+
Headers: #{headers.inspect}
|
|
22
|
+
Body:
|
|
23
|
+
#{ANSI.cyan { format_body( (response.body rescue response), accept( env ), env ) }}
|
|
24
|
+
end_info
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
protected
|
|
28
|
+
|
|
29
|
+
def request_log_data( env )
|
|
30
|
+
request_data = {
|
|
31
|
+
content_type: content_type( env ),
|
|
32
|
+
content_length: env['CONTENT_LENGTH'],
|
|
33
|
+
accept: accept( env ),
|
|
34
|
+
accept_version: env['HTTP_ACCEPT_VERSION'],
|
|
35
|
+
method: env['REQUEST_METHOD'],
|
|
36
|
+
path: path_info( env ),
|
|
37
|
+
query: query( env )
|
|
38
|
+
}
|
|
39
|
+
#request_data[:user_id] = current_user.id if current_user
|
|
40
|
+
request_data
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def content_type( env )
|
|
44
|
+
env['CONTENT_TYPE']
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def accept( env )
|
|
48
|
+
env['HTTP_ACCEPT']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def path_info( env )
|
|
52
|
+
env['PATH_INFO']
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def query( env )
|
|
56
|
+
URI.unescape( env['QUERY_STRING'] )
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def formatted_request_body( env )
|
|
60
|
+
format_body( rack_input_content( env ), content_type( env ), env )
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def format_body( body, format, env )
|
|
64
|
+
filter = fetch_filter( format, body )
|
|
65
|
+
filtered = filter.render
|
|
66
|
+
|
|
67
|
+
formatter = fetch_formatter( format, body ).new( filtered, format )
|
|
68
|
+
formatter.render
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def fetch_filter( format, body )
|
|
72
|
+
klass, filters = Rack::Logjam::Filters.get( format )
|
|
73
|
+
klass.new( body, filters )
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def fetch_formatter( format, body )
|
|
77
|
+
return Rack::Logjam::Array if body.is_a?( Array )
|
|
78
|
+
return Rack::Logjam::Empty if body.strip.nil? || body.strip.empty?
|
|
79
|
+
Rack::Logjam::Formatters.get( format )
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def rack_input_content( env )
|
|
83
|
+
( rack_input = env['rack.input'] ).read.tap do |content|
|
|
84
|
+
rack_input.rewind
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def _logger
|
|
89
|
+
::Rack::Logjam::logger
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'action_dispatch/http/mime_type'
|
|
2
|
+
require 'jsonpath'
|
|
3
|
+
|
|
4
|
+
module Rack
|
|
5
|
+
module Logjam
|
|
6
|
+
module Rails
|
|
7
|
+
class Middleware
|
|
8
|
+
|
|
9
|
+
def initialize( app )
|
|
10
|
+
@app = app
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call( env )
|
|
14
|
+
before env
|
|
15
|
+
|
|
16
|
+
app.call( env ).tap do |rack_response|
|
|
17
|
+
after env, *rack_response
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
|
|
23
|
+
attr_reader :app
|
|
24
|
+
|
|
25
|
+
def before( env )
|
|
26
|
+
return unless api_request?( env )
|
|
27
|
+
|
|
28
|
+
logger.log_request( env )
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def after( env, status, headers, response )
|
|
32
|
+
return unless api_request?( env )
|
|
33
|
+
|
|
34
|
+
logger.log_response( env, status, headers, response )
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def api_request?( env )
|
|
38
|
+
path_info( env ) =~ /^\/api\//
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def path_info( env )
|
|
42
|
+
env['PATH_INFO']
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def logger
|
|
46
|
+
Rack::Logjam::Logger.new
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/rack/logjam/version.rb
CHANGED
data/lib/rack/logjam.rb
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
module Rack
|
|
2
|
-
|
|
3
2
|
module Logjam
|
|
4
3
|
|
|
5
|
-
autoload :
|
|
6
|
-
autoload :
|
|
7
|
-
autoload :
|
|
4
|
+
autoload :ANSI, 'rack/logjam/ansi'
|
|
5
|
+
autoload :Configuration, 'rack/logjam/configuration'
|
|
6
|
+
autoload :Filters, 'rack/logjam/filters'
|
|
7
|
+
autoload :Formatters, 'rack/logjam/formatters'
|
|
8
|
+
autoload :Grape, 'rack/logjam/grape'
|
|
9
|
+
autoload :Logger, 'rack/logjam/logger'
|
|
10
|
+
autoload :Rails, 'rack/logjam/rails'
|
|
11
|
+
autoload :VERSION, 'rack/logjam/version'
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
def self.configuration
|
|
14
|
+
@configuration ||= Configuration.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.configuration=( configuration )
|
|
18
|
+
@configuration = configuration
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.configure
|
|
22
|
+
yield( configuration ) if block_given?
|
|
23
|
+
end
|
|
10
24
|
|
|
25
|
+
class << self
|
|
26
|
+
attr_accessor :logger
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
11
30
|
end
|
|
31
|
+
|
|
32
|
+
Rack::Logjam::Formatters.register 'application/x-www-form-urlencoded', :FormUrlencoded
|
|
33
|
+
Rack::Logjam::Formatters.register 'application/json', :Json
|
|
34
|
+
Rack::Logjam::Formatters.register 'text/plain', :TextPlain
|
|
35
|
+
Rack::Logjam::Formatters.register 'application/xml', :Xml
|
data/rack-logjam.gemspec
CHANGED
|
@@ -20,9 +20,9 @@ Gem::Specification.new do |spec|
|
|
|
20
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
21
21
|
spec.require_paths = ['lib']
|
|
22
22
|
|
|
23
|
-
spec.add_dependency '
|
|
24
|
-
spec.add_dependency '
|
|
25
|
-
spec.add_dependency 'nokogiri'
|
|
23
|
+
spec.add_dependency 'jsonpath', '~> 0.5'
|
|
24
|
+
spec.add_dependency 'multi_json'
|
|
25
|
+
spec.add_dependency 'nokogiri'
|
|
26
26
|
|
|
27
27
|
spec.add_development_dependency 'bundler', '~> 1.5'
|
|
28
28
|
spec.add_development_dependency 'rake'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rack-logjam
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jason Harrelson
|
|
@@ -9,50 +9,50 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2014-10-
|
|
12
|
+
date: 2014-10-17 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
|
-
name:
|
|
15
|
+
name: jsonpath
|
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
|
17
17
|
requirements:
|
|
18
18
|
- - "~>"
|
|
19
19
|
- !ruby/object:Gem::Version
|
|
20
|
-
version: '
|
|
20
|
+
version: '0.5'
|
|
21
21
|
type: :runtime
|
|
22
22
|
prerelease: false
|
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
|
24
24
|
requirements:
|
|
25
25
|
- - "~>"
|
|
26
26
|
- !ruby/object:Gem::Version
|
|
27
|
-
version: '
|
|
27
|
+
version: '0.5'
|
|
28
28
|
- !ruby/object:Gem::Dependency
|
|
29
|
-
name:
|
|
29
|
+
name: multi_json
|
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
|
31
31
|
requirements:
|
|
32
|
-
- - "
|
|
32
|
+
- - ">="
|
|
33
33
|
- !ruby/object:Gem::Version
|
|
34
|
-
version: '0
|
|
34
|
+
version: '0'
|
|
35
35
|
type: :runtime
|
|
36
36
|
prerelease: false
|
|
37
37
|
version_requirements: !ruby/object:Gem::Requirement
|
|
38
38
|
requirements:
|
|
39
|
-
- - "
|
|
39
|
+
- - ">="
|
|
40
40
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: '0
|
|
41
|
+
version: '0'
|
|
42
42
|
- !ruby/object:Gem::Dependency
|
|
43
43
|
name: nokogiri
|
|
44
44
|
requirement: !ruby/object:Gem::Requirement
|
|
45
45
|
requirements:
|
|
46
|
-
- - "
|
|
46
|
+
- - ">="
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '
|
|
48
|
+
version: '0'
|
|
49
49
|
type: :runtime
|
|
50
50
|
prerelease: false
|
|
51
51
|
version_requirements: !ruby/object:Gem::Requirement
|
|
52
52
|
requirements:
|
|
53
|
-
- - "
|
|
53
|
+
- - ">="
|
|
54
54
|
- !ruby/object:Gem::Version
|
|
55
|
-
version: '
|
|
55
|
+
version: '0'
|
|
56
56
|
- !ruby/object:Gem::Dependency
|
|
57
57
|
name: bundler
|
|
58
58
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -89,6 +89,8 @@ extensions: []
|
|
|
89
89
|
extra_rdoc_files: []
|
|
90
90
|
files:
|
|
91
91
|
- ".gitignore"
|
|
92
|
+
- ".ruby-gemset"
|
|
93
|
+
- ".ruby-version"
|
|
92
94
|
- Gemfile
|
|
93
95
|
- License.markdown
|
|
94
96
|
- README.markdown
|
|
@@ -96,7 +98,25 @@ files:
|
|
|
96
98
|
- lib/rack-logjam.rb
|
|
97
99
|
- lib/rack/logjam.rb
|
|
98
100
|
- lib/rack/logjam/ansi.rb
|
|
99
|
-
- lib/rack/logjam/
|
|
101
|
+
- lib/rack/logjam/configuration.rb
|
|
102
|
+
- lib/rack/logjam/filters.rb
|
|
103
|
+
- lib/rack/logjam/filters/base.rb
|
|
104
|
+
- lib/rack/logjam/filters/json.rb
|
|
105
|
+
- lib/rack/logjam/filters/nil.rb
|
|
106
|
+
- lib/rack/logjam/formatters.rb
|
|
107
|
+
- lib/rack/logjam/formatters/array.rb
|
|
108
|
+
- lib/rack/logjam/formatters/base.rb
|
|
109
|
+
- lib/rack/logjam/formatters/empty.rb
|
|
110
|
+
- lib/rack/logjam/formatters/form_urlencoded.rb
|
|
111
|
+
- lib/rack/logjam/formatters/json.rb
|
|
112
|
+
- lib/rack/logjam/formatters/nil.rb
|
|
113
|
+
- lib/rack/logjam/formatters/text_plain.rb
|
|
114
|
+
- lib/rack/logjam/formatters/xml.rb
|
|
115
|
+
- lib/rack/logjam/grape.rb
|
|
116
|
+
- lib/rack/logjam/grape/middleware.rb
|
|
117
|
+
- lib/rack/logjam/logger.rb
|
|
118
|
+
- lib/rack/logjam/rails.rb
|
|
119
|
+
- lib/rack/logjam/rails/middleware.rb
|
|
100
120
|
- lib/rack/logjam/version.rb
|
|
101
121
|
- rack-logjam.gemspec
|
|
102
122
|
homepage: ''
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
require 'action_dispatch/http/mime_type'
|
|
2
|
-
require 'jsonpath'
|
|
3
|
-
|
|
4
|
-
module Rack
|
|
5
|
-
|
|
6
|
-
module Logjam
|
|
7
|
-
|
|
8
|
-
class Middleware
|
|
9
|
-
|
|
10
|
-
def initialize( app )
|
|
11
|
-
@app = app
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def call( env )
|
|
15
|
-
before env
|
|
16
|
-
app.call( env ).tap do |rack_response|
|
|
17
|
-
after env, *rack_response
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
protected
|
|
22
|
-
|
|
23
|
-
attr_reader :app
|
|
24
|
-
|
|
25
|
-
def before( env )
|
|
26
|
-
return unless api_request?( env )
|
|
27
|
-
|
|
28
|
-
Rails.logger.info <<-end_info
|
|
29
|
-
[#{ANSI.green { 'api' }}] #{ANSI.cyan { '--- Request Env ---' }}
|
|
30
|
-
#{ANSI.magenta { JSON.pretty_generate( request_log_data( env )) }}
|
|
31
|
-
[#{ANSI.green { 'api' }}] #{ANSI.cyan { '--- Request Body ---' }}
|
|
32
|
-
#{ANSI.cyan { formatted_request_body( env ) }}
|
|
33
|
-
end_info
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def after( env, status, headers, response )
|
|
37
|
-
return unless api_request?( env )
|
|
38
|
-
|
|
39
|
-
Rails.logger.info <<-end_info
|
|
40
|
-
[#{ANSI.green { 'api' }}] #{ANSI.cyan { '--- Response ---' }}
|
|
41
|
-
Status: #{status}
|
|
42
|
-
Headers: #{headers.inspect}
|
|
43
|
-
Body:
|
|
44
|
-
#{ANSI.cyan { format_body( (response.body rescue response), accept( env ), env ) }}
|
|
45
|
-
end_info
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def request_log_data( env )
|
|
49
|
-
request_data = {
|
|
50
|
-
auth_token: env['HTTP_X_NCITE_AUTH_TOKEN'],
|
|
51
|
-
content_type: content_type( env ),
|
|
52
|
-
content_length: env['CONTENT_LENGTH'],
|
|
53
|
-
accept: accept( env ),
|
|
54
|
-
accept_version: env['HTTP_ACCEPT_VERSION'],
|
|
55
|
-
method: env['REQUEST_METHOD'],
|
|
56
|
-
path: path_info( env ),
|
|
57
|
-
query: query( env )
|
|
58
|
-
}
|
|
59
|
-
#request_data[:user_id] = current_user.id if current_user
|
|
60
|
-
request_data
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def api_request?( env )
|
|
64
|
-
path_info( env ) =~ /^\/api\//
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def content_type( env )
|
|
68
|
-
env['CONTENT_TYPE']
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def accept( env )
|
|
72
|
-
env['HTTP_ACCEPT']
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def path_info( env )
|
|
76
|
-
env['PATH_INFO']
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def query( env )
|
|
80
|
-
URI.unescape( env['QUERY_STRING'] )
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def formatted_request_body( env )
|
|
84
|
-
format_body( rack_input_content( env ), content_type( env ), env )
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
def format_body( body, format, env )
|
|
88
|
-
return body.inspect if body.is_a?( Array )
|
|
89
|
-
return body if body.strip.nil? || body.strip.empty?
|
|
90
|
-
|
|
91
|
-
if format == ::Mime::JSON.to_s
|
|
92
|
-
hash = truncate_json_attributes( body )
|
|
93
|
-
return ::JSON.pretty_generate( hash )
|
|
94
|
-
elsif format == ::Mime::XML.to_s
|
|
95
|
-
return ::Nokogiri.XML( body ) do |config|
|
|
96
|
-
config.default_xml.noblanks
|
|
97
|
-
end.to_xml( indent: 2 )
|
|
98
|
-
elsif format == ::Mime::URL_ENCODED_FORM.to_s
|
|
99
|
-
return URI.unescape( body )
|
|
100
|
-
elsif format == ::Mime::OCTET_STREAM.to_s
|
|
101
|
-
return "no body b/c content type is #{::Mime::OCTET_STREAM}"
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
body
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
def rack_input_content( env )
|
|
108
|
-
( rack_input = env['rack.input'] ).read.tap do |content|
|
|
109
|
-
rack_input.rewind
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Can currently use the following xpath expressions that will translate to json_path:
|
|
114
|
-
# /some/path -> $.some.path (search from root)
|
|
115
|
-
# some/path -> some.path (search for any sub-path that matches this, not bound to root)
|
|
116
|
-
# //path -> $..path (recursive search)
|
|
117
|
-
#
|
|
118
|
-
def truncated_attributes_as_xpath
|
|
119
|
-
%w(
|
|
120
|
-
//image_as_base64
|
|
121
|
-
//fingerprint_image_as_base64
|
|
122
|
-
)
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
def truncate_json_attributes( json )
|
|
126
|
-
json_path = ::JsonPath.for( json )
|
|
127
|
-
truncated_attributes_as_json_paths.each do |j_path|
|
|
128
|
-
json_path.gsub!( j_path ) { |val| (val.nil? || val.empty?) ? val : "#{val[0..25]} ... [TRUNCATED] ..." }
|
|
129
|
-
end
|
|
130
|
-
json_path.to_hash
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def truncated_attributes_as_json_paths
|
|
134
|
-
truncated_attributes_as_xpath.map do |xpath|
|
|
135
|
-
if xpath.start_with?( '//' )
|
|
136
|
-
xpath.gsub( '//', '$..' )
|
|
137
|
-
else
|
|
138
|
-
parts = xpath.split( '/' )
|
|
139
|
-
parts[0] = '$' if parts[0] == ''
|
|
140
|
-
parts.join( '.' )
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
end
|