akita-har_logger 0.1.0 → 0.2.4
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/.gitignore +1 -0
- data/Gemfile.lock +2 -4
- data/README.md +77 -21
- data/akita-har_logger.gemspec +0 -2
- data/lib/akita/har_logger.rb +69 -6
- data/lib/akita/har_logger/har_entry.rb +0 -1
- data/lib/akita/har_logger/har_utils.rb +23 -2
- data/lib/akita/har_logger/http_request.rb +41 -11
- data/lib/akita/har_logger/http_response.rb +11 -8
- data/lib/akita/har_logger/version.rb +1 -1
- data/lib/akita/har_logger/writer_thread.rb +0 -2
- metadata +4 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51fcd2da35cb17ecb365988d7975f746a717f12b002d3a20f1d0c3eab245b10c
|
4
|
+
data.tar.gz: 4972d841dfc7c8dcd41085137d45faedeb450836d46e6d3f14d584785d997a24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8fdffa25d122a573e8c72526934e5e713fbdc573c0d4ebfa13383d749ea856f9b01d2e5bc43e89b1c152df64f97cd965ad54bd5a4ebab924d222a1829bbbec60
|
7
|
+
data.tar.gz: 8ca06a98ce89ec4e9c3e6ac7f3b10ec98f73c44aacab1af0bac8c81649f342ec721017ee57ff6590c93a24234cce9d40c9b341aa8277a39a59bc86a3a9c3484e
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/pkg/
|
data/Gemfile.lock
CHANGED
@@ -1,14 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
akita-har_logger (0.
|
5
|
-
json (~> 2.3)
|
4
|
+
akita-har_logger (0.2.4)
|
6
5
|
|
7
6
|
GEM
|
8
7
|
remote: https://rubygems.org/
|
9
8
|
specs:
|
10
9
|
diff-lcs (1.4.4)
|
11
|
-
json (2.5.1)
|
12
10
|
rake (13.0.3)
|
13
11
|
rspec (3.10.0)
|
14
12
|
rspec-core (~> 3.10.0)
|
@@ -33,4 +31,4 @@ DEPENDENCIES
|
|
33
31
|
rspec (~> 3.10)
|
34
32
|
|
35
33
|
BUNDLED WITH
|
36
|
-
2.2.
|
34
|
+
2.2.23
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
# Akita HTTP Archive (HAR) logger for Rack applications
|
1
|
+
# Akita HTTP Archive (HAR) logger for Rack/Rails applications
|
2
2
|
|
3
|
-
This provides Rack middleware
|
4
|
-
file.
|
3
|
+
This provides Rack middleware and a Rails `ActionController` filter for logging
|
4
|
+
HTTP request–response pairs to a HAR file.
|
5
5
|
|
6
6
|
|
7
7
|
## Installation
|
@@ -23,24 +23,80 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
`
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
26
|
+
There are two options for instrumenting your Rack/Rails application. The first
|
27
|
+
is to use the HAR logger as Rack middleware. The second is to use it as a Rails
|
28
|
+
`ActionController` filter.
|
29
|
+
|
30
|
+
Depending on the framework you're using, one or both options may be available
|
31
|
+
to you. If you are interested in logging RSpec tests, the filter option will
|
32
|
+
capture traffic for both controller and request specs, whereas the middleware
|
33
|
+
option only captures request specs.
|
34
|
+
|
35
|
+
Once your application is instrumented, when you run the application, HTTP
|
36
|
+
requests and responses will be logged to the HAR file that you've specified.
|
37
|
+
You can then upload this HAR file to Akita for analysis.
|
38
|
+
|
39
|
+
### Middleware
|
40
|
+
|
41
|
+
To instrument with middleware, add `Akita::HarLogger::Middleware` to the top of
|
42
|
+
your middleware stack. For convenience, you can call
|
43
|
+
`Akita::HarLogger.instrument` to do this. We recommend adding this call to the
|
44
|
+
bottom of `config/environments/test.rb` to add the middleware just to your test
|
45
|
+
environment.
|
46
|
+
|
47
|
+
Here is a sample configuration for a test environment that just adds the
|
48
|
+
instrumentation.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
# config/environments/test.rb
|
52
|
+
|
53
|
+
Rails.application.configure.do
|
54
|
+
# Other configuration for the Rails application...
|
55
|
+
|
56
|
+
# Put the HAR logger at the top of the middleware stack, and optionally
|
57
|
+
# give an output HAR file to save your trace. If not specified, this defaults
|
58
|
+
# to `akita_trace_{timestamp}.har`.
|
59
|
+
Akita::HarLogger.instrument(config, "akita_trace.har")
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
### `ActionController` filter
|
64
|
+
|
65
|
+
To instrument with a filter, add an instance of `Akita::HarLogger::Filter` as
|
66
|
+
an `around_action` filter to your `ActionController` implementation.
|
67
|
+
|
68
|
+
For convenience, you can call `Akita::HarLogger::Filter.install` to do this for
|
69
|
+
all `ActionController`s in your application. We recommend adding this call to a
|
70
|
+
configuration initializer. For example, this initializer adds the filter only
|
71
|
+
in the test environment:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# config/initializers/har_logging.rb
|
75
|
+
|
76
|
+
# Add the HAR logger as an `around_action` filter to all `ActionControllers`
|
77
|
+
# that are loaded by the application in the test environment. Optionally give
|
78
|
+
# an output HAR file to save your trace. If not specified, this defaults to
|
79
|
+
# `akita_trace_{timestamp}.har`.
|
80
|
+
Akita::HarLogger::Filter.install("akita_trace.har") if Rails.env.test?
|
81
|
+
```
|
82
|
+
|
83
|
+
You can also selectively instrument your `ActionController` implementations by
|
84
|
+
adding the filter manually. Here is a bare-bones `ActionController`
|
85
|
+
implementation that adds the filter only in the test environment.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
# app/controllers/application_controller.rb
|
89
|
+
|
90
|
+
class ApplicationController < ActionController::API
|
91
|
+
include Response
|
92
|
+
include ExceptionHandler
|
93
|
+
|
94
|
+
# Add the HAR logger as an `around_action` filter. Optionally give an output
|
95
|
+
# HAR file to save your trace. If not specified, this defaults to
|
96
|
+
# `akita_trace_{timestamp}.har`.
|
97
|
+
around_action Akita::HarLogger::Filter.new("akita_trace.har") if Rails.env.test?
|
98
|
+
end
|
99
|
+
```
|
44
100
|
|
45
101
|
|
46
102
|
## Development
|
data/akita-har_logger.gemspec
CHANGED
data/lib/akita/har_logger.rb
CHANGED
@@ -29,14 +29,10 @@ module Akita
|
|
29
29
|
@app = app
|
30
30
|
|
31
31
|
if out_file_name == nil then
|
32
|
-
out_file_name =
|
32
|
+
out_file_name = HarLogger.default_file_name
|
33
33
|
end
|
34
34
|
|
35
|
-
|
36
|
-
# main thread will enqueue HarEntry objects. The HAR writer thread
|
37
|
-
# below dequeues these objects and writes them to the output file.
|
38
|
-
@entry_queue = Queue.new
|
39
|
-
WriterThread.new out_file_name, @entry_queue
|
35
|
+
@entry_queue = HarLogger.get_queue(out_file_name)
|
40
36
|
end
|
41
37
|
|
42
38
|
def call(env)
|
@@ -52,5 +48,72 @@ module Akita
|
|
52
48
|
[ status, headers, body ]
|
53
49
|
end
|
54
50
|
end
|
51
|
+
|
52
|
+
# Logging filter for `ActionController`s.
|
53
|
+
# TODO: Some amount of code duplication here. Should refactor.
|
54
|
+
class Filter
|
55
|
+
def initialize(out_file_name = nil)
|
56
|
+
if out_file_name == nil then
|
57
|
+
out_file_name = HarLogger.default_file_name
|
58
|
+
end
|
59
|
+
|
60
|
+
@entry_queue = HarLogger.get_queue(out_file_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Registers an `on_load` initializer to add a logging filter to any
|
64
|
+
# ActionController that is created.
|
65
|
+
def self.install(out_file_name = nil, hook_name = :action_controller)
|
66
|
+
ActiveSupport.on_load(hook_name) do
|
67
|
+
around_action Filter.new(out_file_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Implements the actual `around` filter.
|
72
|
+
def around(controller)
|
73
|
+
start_time = Time.now
|
74
|
+
|
75
|
+
yield
|
76
|
+
|
77
|
+
end_time = Time.now
|
78
|
+
wait_time_ms = ((end_time.to_f - start_time.to_f) * 1000).round
|
79
|
+
|
80
|
+
response = controller.response
|
81
|
+
request = response.request
|
82
|
+
|
83
|
+
@entry_queue << (HarEntry.new start_time, wait_time_ms, request.env,
|
84
|
+
response.status, response.headers,
|
85
|
+
[response.body])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@@default_file_name = "akita_trace_#{Time.now.to_i}.har"
|
90
|
+
def self.default_file_name
|
91
|
+
@@default_file_name
|
92
|
+
end
|
93
|
+
|
94
|
+
# Maps the name of each output file to a queue of entries to be logged to
|
95
|
+
# that file. The queue is used to ensure that event logging is thread-safe.
|
96
|
+
# The main thread will enqueue HarEntry objects. A HAR writer thread
|
97
|
+
# dequeues these objects and writes them to the output file.
|
98
|
+
@@entry_queues = {}
|
99
|
+
@@entry_queues_mutex = Mutex.new
|
100
|
+
|
101
|
+
# Returns the entry queue for the given file. If an entry queue doesn't
|
102
|
+
# already exist, one is created and a HAR writer thread is started for the
|
103
|
+
# queue.
|
104
|
+
def self.get_queue(out_file_name)
|
105
|
+
queue = nil
|
106
|
+
@@entry_queues_mutex.synchronize {
|
107
|
+
if @@entry_queues.has_key?(out_file_name) then
|
108
|
+
return @@entry_queues[out_file_name]
|
109
|
+
end
|
110
|
+
|
111
|
+
queue = Queue.new
|
112
|
+
@@entry_queues[out_file_name] = queue
|
113
|
+
}
|
114
|
+
|
115
|
+
WriterThread.new out_file_name, queue
|
116
|
+
return queue
|
117
|
+
end
|
55
118
|
end
|
56
119
|
end
|
@@ -3,17 +3,38 @@
|
|
3
3
|
module Akita
|
4
4
|
module HarLogger
|
5
5
|
class HarUtils
|
6
|
+
# Rack apparently uses 8-bit ASCII for everything, even when the string
|
7
|
+
# is not 8-bit ASCII. This reinterprets 8-bit ASCII strings as UTF-8.
|
8
|
+
def self.fixEncoding(v)
|
9
|
+
if v == nil || v.encoding != Encoding::ASCII_8BIT then
|
10
|
+
v
|
11
|
+
else
|
12
|
+
String.new(v).force_encoding(Encoding::UTF_8)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
6
16
|
# Converts a Hash into a list of Hash objects. Each entry in the given
|
7
17
|
# Hash will be represented in the output by a Hash object that maps
|
8
18
|
# 'name' to the entry's key and 'value' to the entry's value.
|
9
19
|
def self.hashToList(hash)
|
10
20
|
hash.reduce([]) { |accum, (k, v)|
|
11
21
|
accum.append({
|
12
|
-
name: k,
|
13
|
-
value: v,
|
22
|
+
name: fixEncoding(k),
|
23
|
+
value: fixEncoding(v),
|
14
24
|
})
|
15
25
|
}
|
16
26
|
end
|
27
|
+
|
28
|
+
# Determines whether all values in a Hash are strings.
|
29
|
+
def self.allValuesAreStrings(hash)
|
30
|
+
hash.each do |_, value|
|
31
|
+
if !(value.is_a? String) then
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return true
|
37
|
+
end
|
17
38
|
end
|
18
39
|
end
|
19
40
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'json'
|
4
3
|
require_relative 'har_utils'
|
5
4
|
|
6
5
|
module Akita
|
@@ -12,7 +11,7 @@ module Akita
|
|
12
11
|
|
13
12
|
@self = {
|
14
13
|
method: getMethod(env),
|
15
|
-
url: req.url,
|
14
|
+
url: HarUtils.fixEncoding(req.url),
|
16
15
|
httpVersion: getHttpVersion(env),
|
17
16
|
cookies: getCookies(env),
|
18
17
|
headers: getHeaders(env),
|
@@ -34,7 +33,7 @@ module Akita
|
|
34
33
|
|
35
34
|
# Obtains the client's request method from an HTTP environment.
|
36
35
|
def getMethod(env)
|
37
|
-
(Rack::Request.new env).request_method
|
36
|
+
HarUtils.fixEncoding (Rack::Request.new env).request_method
|
38
37
|
end
|
39
38
|
|
40
39
|
# Obtains the client-requested HTTP version from an HTTP environment.
|
@@ -42,7 +41,9 @@ module Akita
|
|
42
41
|
# The environment doesn't have HTTP_VERSION when running with `rspec`;
|
43
42
|
# assume HTTP/1.1 when this happens. We don't return nil, so we can
|
44
43
|
# calculate the size of the headers.
|
45
|
-
env.key?('HTTP_VERSION') ?
|
44
|
+
env.key?('HTTP_VERSION') ?
|
45
|
+
HarUtils.fixEncoding(env['HTTP_VERSION']) :
|
46
|
+
'HTTP/1.1'
|
46
47
|
end
|
47
48
|
|
48
49
|
# Builds a list of cookie objects from an HTTP environment.
|
@@ -72,23 +73,52 @@ module Akita
|
|
72
73
|
HarUtils.hashToList paramMap
|
73
74
|
end
|
74
75
|
|
76
|
+
# Obtains the character set of the posted data from an HTTP environment.
|
77
|
+
def getPostDataCharSet(env)
|
78
|
+
req = Rack::Request.new env
|
79
|
+
if req.content_charset != nil then
|
80
|
+
return req.content_charset
|
81
|
+
end
|
82
|
+
|
83
|
+
# RFC 2616 says that "text/*" defaults to ISO-8859-1.
|
84
|
+
if env['CONTENT_TYPE'].start_with?('text/') then
|
85
|
+
return Encoding::ISO_8859_1
|
86
|
+
end
|
87
|
+
|
88
|
+
Encoding.default_external
|
89
|
+
end
|
90
|
+
|
75
91
|
# Obtains the posted data from an HTTP environment.
|
76
92
|
def getPostData(env)
|
77
93
|
if env.key?('CONTENT_TYPE') && env['CONTENT_TYPE'] then
|
78
94
|
result = { mimeType: env['CONTENT_TYPE'] }
|
79
95
|
|
80
96
|
# Populate 'params' if we have URL-encoded parameters. Otherwise,
|
81
|
-
# populate 'text.
|
97
|
+
# populate 'text'.
|
82
98
|
req = Rack::Request.new env
|
83
99
|
if env['CONTENT_TYPE'] == 'application/x-www-form-urlencoded' then
|
84
|
-
# Decoded parameters can be found as a map in req.params.
|
85
|
-
# this map into an array.
|
100
|
+
# Decoded parameters can be found as a map in req.params.
|
86
101
|
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
|
102
|
+
# Requests originating from specs can be malformed: the values in
|
103
|
+
# req.params are not necessarily strings. Encode all of req.params
|
104
|
+
# in JSON and pretend the content type was "application/json".
|
105
|
+
if HarUtils.allValuesAreStrings req.params then
|
106
|
+
# Convert req.params into an array.
|
107
|
+
#
|
108
|
+
# XXX Spec has space for files, but are file uploads ever
|
109
|
+
# URL-encoded?
|
110
|
+
result[:params] = HarUtils.hashToList req.params
|
111
|
+
else
|
112
|
+
result[:mimeType] = 'application/json'
|
113
|
+
result[:text] = req.params.to_json
|
114
|
+
end
|
90
115
|
else
|
91
|
-
|
116
|
+
# Rack has been observed to use ASCII-8BIT encoding for the request
|
117
|
+
# body when the request specifies UTF-8. Reinterpret the content
|
118
|
+
# body according to what the request says it is, and re-encode into
|
119
|
+
# UTF-8.
|
120
|
+
result[:text] = req.body.string.encode(Encoding::UTF_8,
|
121
|
+
getPostDataCharSet(env))
|
92
122
|
end
|
93
123
|
|
94
124
|
result
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'json'
|
4
3
|
require_relative 'har_utils'
|
5
4
|
|
6
5
|
module Akita
|
@@ -26,7 +25,7 @@ module Akita
|
|
26
25
|
|
27
26
|
# Obtains the status text corresponding to a status code.
|
28
27
|
def getStatusText(status)
|
29
|
-
Rack::Utils::HTTP_STATUS_CODES[status]
|
28
|
+
HarUtils.fixEncoding(Rack::Utils::HTTP_STATUS_CODES[status])
|
30
29
|
end
|
31
30
|
|
32
31
|
# Obtains the HTTP version in the response.
|
@@ -37,7 +36,9 @@ module Akita
|
|
37
36
|
# The environment doesn't have HTTP_VERSION when running with `rspec`;
|
38
37
|
# assume HTTP/1.1 when this happens. We don't return nil, so we can
|
39
38
|
# calculate the size of the headers.
|
40
|
-
env.key?('HTTP_VERSION') ?
|
39
|
+
env.key?('HTTP_VERSION') ?
|
40
|
+
HarUtils.fixEncoding(env['HTTP_VERSION']) :
|
41
|
+
'HTTP/1.1'
|
41
42
|
end
|
42
43
|
|
43
44
|
def getCookies(headers)
|
@@ -64,8 +65,8 @@ module Akita
|
|
64
65
|
if match then cookie_value = match[1] end
|
65
66
|
|
66
67
|
result << {
|
67
|
-
name: cookie_name,
|
68
|
-
value: cookie_value,
|
68
|
+
name: HarUtils.fixEncoding(cookie_name),
|
69
|
+
value: HarUtils.fixEncoding(cookie_value),
|
69
70
|
}
|
70
71
|
}
|
71
72
|
|
@@ -78,14 +79,14 @@ module Akita
|
|
78
79
|
text = +""
|
79
80
|
body.each { |part|
|
80
81
|
# XXX Figure out how to join together multi-part bodies.
|
81
|
-
text << part;
|
82
|
+
text << (HarUtils.fixEncoding part);
|
82
83
|
}
|
83
84
|
|
84
85
|
{
|
85
86
|
size: getBodySize(body),
|
86
87
|
|
87
88
|
# XXX What to use when no Content-Type is given?
|
88
|
-
mimeType: headers['Content-Type'],
|
89
|
+
mimeType: HarUtils.fixEncoding(headers['Content-Type']),
|
89
90
|
|
90
91
|
text: text,
|
91
92
|
}
|
@@ -94,7 +95,9 @@ module Akita
|
|
94
95
|
def getRedirectUrl(headers)
|
95
96
|
# Use the "Location" header if it exists. Otherwise, based on some HAR
|
96
97
|
# examples found online, it looks like an empty string is used.
|
97
|
-
headers.key?('Location') ?
|
98
|
+
headers.key?('Location') ?
|
99
|
+
HarUtils.fixEncoding(headers['Location']) :
|
100
|
+
''
|
98
101
|
end
|
99
102
|
|
100
103
|
def getHeadersSize(env, status, headers)
|
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: akita-har_logger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jed Liu
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-07-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: json
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '2.3'
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '2.3'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rspec
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -46,6 +32,7 @@ executables: []
|
|
46
32
|
extensions: []
|
47
33
|
extra_rdoc_files: []
|
48
34
|
files:
|
35
|
+
- ".gitignore"
|
49
36
|
- Gemfile
|
50
37
|
- Gemfile.lock
|
51
38
|
- LICENSE
|
@@ -82,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
82
69
|
- !ruby/object:Gem::Version
|
83
70
|
version: '0'
|
84
71
|
requirements: []
|
85
|
-
rubygems_version: 3.2.
|
72
|
+
rubygems_version: 3.2.21
|
86
73
|
signing_key:
|
87
74
|
specification_version: 4
|
88
75
|
summary: Rails middleware for HAR logging
|