Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Graylog adaptation #83

Merged
merged 3 commits into from
Nov 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ HttpLog.configure do |config|
# You can also log in JSON format
config.json_log = false

# For Graylog you can set this to `true`
config.graylog = false

# Prettify the output - see below
config.color = false

Expand Down Expand Up @@ -118,6 +121,18 @@ HttpLog.configure do |config|
end
```

If you use Graylog and want to use its search features such as "rounded_benchmark:>1 AND method:PUT",
you can use this configuration:

```ruby
HttpLog.configure do |config|
config.logger = <your GELF::Logger>
config.logger_method = :add
config.severity = GELF::Levels::DEBUG
config.graylog = true
end
```

For more color options please refer to the [rainbow documentation](https://github.com/sickill/rainbow)

### Compact logging
Expand Down
2 changes: 2 additions & 0 deletions lib/httplog/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class Configuration
attr_accessor :enabled,
:compact_log,
:json_log,
:graylog,
:logger,
:logger_method,
:severity,
Expand All @@ -28,6 +29,7 @@ def initialize
@enabled = true
@compact_log = false
@json_log = false
@graylog = false
@logger = Logger.new($stdout)
@logger_method = :log
@severity = Logger::Severity::DEBUG
Expand Down
80 changes: 47 additions & 33 deletions lib/httplog/http_log.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def configure
def call(options = {})
if config.json_log
log_json(options)
elsif config.graylog
log_graylog(options)
elsif config.compact_log
log_compact(options[:method], options[:url], options[:response_code], options[:benchmark])
else
Expand Down Expand Up @@ -111,7 +113,7 @@ def parse_body(body, encoding, content_type)

if body.is_a?(Net::ReadAdapter)
# open-uri wraps the response in a Net::ReadAdapter that defers reading
# the content, so the reponse body is not available here.
# the content, so the response body is not available here.
raise BodyParsingError, '(not available yet)'
end

Expand Down Expand Up @@ -147,38 +149,6 @@ def log_compact(method, uri, status, seconds)
log("#{method.to_s.upcase} #{masked(uri)} completed with status code #{status} in #{seconds.to_f.round(6)} seconds")
end

def log_json(data = {})
return unless config.json_log

data[:response_code] = transform_response_code(data[:response_code]) if data[:response_code].is_a?(Symbol)

parsed_body = begin
parse_body(data[:response_body], data[:encoding], data[:content_type])
rescue BodyParsingError => e
e.message
end

if config.compact_log
log({
method: data[:method].to_s.upcase,
url: masked(data[:url]),
response_code: data[:response_code].to_i,
benchmark: data[:benchmark]
}.to_json)
else
log({
method: data[:method].to_s.upcase,
url: masked(data[:url]),
request_body: masked(data[:request_body]),
request_headers: masked(data[:request_headers].to_h),
response_code: data[:response_code].to_i,
response_body: parsed_body,
response_headers: data[:response_headers].to_h,
benchmark: data[:benchmark]
}.to_json)
end
end

def transform_response_code(response_code_name)
Rack::Utils::HTTP_STATUS_CODES.detect { |_k, v| v.to_s.casecmp(response_code_name.to_s).zero? }.first
end
Expand All @@ -199,6 +169,50 @@ def colorize(msg)

private

def log_json(data = {})
return unless config.json_log

log(json_payload(data).to_json)
end

def log_graylog(data = {})
result = json_payload(data)

result[:rounded_benchmark] = data[:benchmark].round
result[:short_message] = result.delete(:url)
config.logger.public_send(config.logger_method, config.severity, result)
end

def json_payload(data = {})
data[:response_code] = transform_response_code(data[:response_code]) if data[:response_code].is_a?(Symbol)

parsed_body = begin
parse_body(data[:response_body], data[:encoding], data[:content_type])
rescue BodyParsingError => e
e.message
end

if config.compact_log
{
method: data[:method].to_s.upcase,
url: masked(data[:url]),
response_code: data[:response_code].to_i,
benchmark: data[:benchmark]
}
else
{
method: data[:method].to_s.upcase,
url: masked(data[:url]),
request_body: masked(data[:request_body]),
request_headers: masked(data[:request_headers].to_h),
response_code: data[:response_code].to_i,
response_body: parsed_body,
response_headers: data[:response_headers].to_h,
benchmark: data[:benchmark]
}
end
end

def masked(msg, key=nil)
return msg if config.filter_parameters.empty?
return msg if msg.nil?
Expand Down
1 change: 1 addition & 0 deletions spec/lib/http_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

describe HTTPClient do
let(:client) { HTTPClient.new }
before { HttpLog.configure { |c| c.logger = Logger.new @log } }
trusche marked this conversation as resolved.
Show resolved Hide resolved

it 'works with transparent_gzip_decompression' do
client.transparent_gzip_decompression = true
Expand Down
33 changes: 11 additions & 22 deletions spec/lib/http_log_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
let(:params) { { 'foo' => secret, 'bar' => 'foo:form-data' } }
let(:html) { File.read('./spec/support/index.html') }
let(:json) { JSON.parse(log.match(/\[httplog\]\s(.*)/).captures.first) }
let(:gray_log) { JSON.parse("{#{log.match(/{(.*)/).captures.first}") }

# Default configuration
let(:logger) { Logger.new @log }
let(:enabled) { HttpLog.configuration.enabled }
let(:severity) { HttpLog.configuration.severity }
let(:log_headers) { HttpLog.configuration.log_headers }
Expand All @@ -29,13 +31,15 @@
let(:prefix_response_lines) { HttpLog.configuration.prefix_response_lines }
let(:prefix_line_numbers) { HttpLog.configuration.prefix_line_numbers }
let(:json_log) { HttpLog.configuration.json_log }
let(:graylog) { HttpLog.configuration.graylog }
let(:compact_log) { HttpLog.configuration.compact_log }
let(:url_blacklist_pattern) { HttpLog.configuration.url_blacklist_pattern }
let(:url_whitelist_pattern) { HttpLog.configuration.url_whitelist_pattern }
let(:filter_parameters) { HttpLog.configuration.filter_parameters }

def configure
HttpLog.configure do |c|
c.logger = logger
c.enabled = enabled
c.severity = severity
c.log_headers = log_headers
Expand All @@ -49,6 +53,7 @@ def configure
c.prefix_response_lines = prefix_response_lines
c.prefix_line_numbers = prefix_line_numbers
c.json_log = json_log
c.graylog = graylog
c.compact_log = compact_log
c.url_blacklist_pattern = url_blacklist_pattern
c.url_whitelist_pattern = url_whitelist_pattern
Expand Down Expand Up @@ -290,30 +295,14 @@ def configure
context 'with JSON config' do
let(:json_log) { true }

if adapter_class.method_defined? :send_post_request
before { adapter.send_post_request }

it { expect(json['method']).to eq('POST') }
it { expect(json['request_body']).to eq(data) }
it { expect(json['request_headers']).to be_a(Hash) }
it { expect(json['response_headers']).to be_a(Hash) }
it { expect(json['response_code']).to eq(200) }
it { expect(json['response_body']).to eq(html) }
it { expect(json['benchmark']).to be_a(Numeric) }
it_behaves_like 'filtered parameters'
it_behaves_like 'logs JSON', adapter_class, false
end

context 'and compact config' do
let(:compact_log) { true }
context 'with Graylog config' do
let(:graylog) { true }
let(:logger) { GelfMock.new @log }

it { expect(json['method']).to eq('POST') }
it { expect(json['request_body']).to be_nil }
it { expect(json['request_headers']).to be_nil }
it { expect(json['response_headers']).to be_nil }
it { expect(json['response_code']).to eq(200) }
it { expect(json['response_body']).to be_nil }
it { expect(json['benchmark']).to be_a(Numeric) }
end
end
it_behaves_like 'logs JSON', adapter_class, true
end
end
end
Expand Down
15 changes: 15 additions & 0 deletions spec/loggers/gelf_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class GelfMock < Logger
def log(severity, message = nil, progname = nil)
message ||= {}
if message.is_a?(Hash)
message[:short_message] = message[:short_message].to_s

message = message.each_with_object({}) do |(key, value), obj|
key_s = key.to_s

obj[key_s] = value
end.to_json
end
super(severity, message, progname)
end
end
4 changes: 1 addition & 3 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

require 'httplog'

require 'loggers/gelf_mock'
require 'adapters/http_base_adapter'
Dir[File.dirname(__FILE__) + '/adapters/*.rb'].each { |f| require f }
Dir['./spec/support/**/*.rb'].each { |f| require f }
Expand All @@ -30,9 +31,6 @@
require 'stringio'

@log = StringIO.new
@logger = Logger.new @log

HttpLog.configure { |c| c.logger = @logger }
end

config.after(:each) do
Expand Down
32 changes: 32 additions & 0 deletions spec/support/shared_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,35 @@
is_expected.to_not include('secret')
end
end

RSpec.shared_examples 'logs JSON' do |adapter_class, gray|
if adapter_class.method_defined? :send_post_request
before { adapter.send_post_request }
let(:result) { gray ? gray_log : json }

it { expect(result['method']).to eq('POST') }
it { expect(result['request_body']).to eq(data) }
it { expect(result['request_headers']).to be_a(Hash) }
it { expect(result['response_headers']).to be_a(Hash) }
it { expect(result['response_code']).to eq(200) }
it { expect(result['response_body']).to eq(html) }
it { expect(result['benchmark']).to be_a(Numeric) }
if gray
it { expect(result['rounded_benchmark']).to be_a(Integer) }
it { expect(result['short_message']).to be_a(String) }
end
it_behaves_like 'filtered parameters'

context 'and compact config' do
let(:compact_log) { true }

it { expect(result['method']).to eq('POST') }
it { expect(result['request_body']).to be_nil }
it { expect(result['request_headers']).to be_nil }
it { expect(result['response_headers']).to be_nil }
it { expect(result['response_code']).to eq(200) }
it { expect(result['response_body']).to be_nil }
it { expect(result['benchmark']).to be_a(Numeric) }
end
end
end