Class: HTTP::Features::Logging

Inherits:
HTTP::Feature show all
Defined in:
lib/http/features/logging.rb,
sig/http.rbs

Overview

Log requests and responses. Request verb and uri, and Response status are logged at info, and the headers and bodies of both are logged at debug. Be sure to specify the logger when enabling the feature:

HTTP.use(logging: {logger: Logger.new(STDOUT)}).get("https://example.com/")

Binary bodies (IO/Enumerable request sources and binary-encoded responses) are formatted using the binary_formatter option instead of being dumped raw. Available formatters:

  • :stats (default) — logs BINARY DATA (N bytes)
  • :base64 — logs BINARY DATA (N bytes)\n
  • Proc — calls the proc with the raw binary string

Examples:

Custom binary formatter

HTTP.use(logging: {logger: Logger.new(STDOUT), binary_formatter: :base64})

Defined Under Namespace

Classes: BodyLogger, NullLogger

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods inherited from HTTP::Feature

#around_request, #on_error, #on_request

Constructor Details

#initialize(logger: NullLogger.new, binary_formatter: :stats) ⇒ Logging

Initializes the Logging feature

Examples:

Logging.new(logger: Logger.new(STDOUT))

With binary formatter

Logging.new(logger: Logger.new(STDOUT), binary_formatter: :base64)

Parameters:

  • logger (#info, #debug) (defaults to: NullLogger.new)

    logger instance

  • binary_formatter (:stats, :base64, #call) (defaults to: :stats)

    how to log binary bodies

  • logger: (Object) (defaults to: NullLogger.new)
  • binary_formatter: (:stats, :base64, ^(String) -> String) (defaults to: :stats)


60
61
62
63
64
# File 'lib/http/features/logging.rb', line 60

def initialize(logger: NullLogger.new, binary_formatter: :stats)
  super()
  @logger = logger
  @binary_formatter = validate_binary_formatter!(binary_formatter)
end

Instance Attribute Details

#logger#info, #debug (readonly)

The logger instance

Examples:

feature.logger

Returns:

  • (#info, #debug)

    the logger instance



46
47
48
# File 'lib/http/features/logging.rb', line 46

def logger
  @logger
end

Instance Method Details

#format_binary(data) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Format binary data according to the configured binary_formatter

Parameters:

  • data (String)

Returns:

  • (String)


169
170
171
172
173
174
175
176
177
178
# File 'lib/http/features/logging.rb', line 169

def format_binary(data)
  case @binary_formatter
  when :stats
    format("BINARY DATA (%d bytes)", data.bytesize)
  when :base64
    format("BINARY DATA (%d bytes)\n%s", data.bytesize, [data].pack("m0"))
  else
    @binary_formatter.call(data) # steep:ignore
  end
end

#log_request_details(request) ⇒ void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Log request headers and body (when loggable)

Parameters:



117
118
119
120
121
122
123
124
125
126
# File 'lib/http/features/logging.rb', line 117

def log_request_details(request)
  headers = stringify_headers(request.headers)
  if request.body.loggable?
    source = request.body.source
    body = source.encoding.eql?(Encoding::BINARY) ? format_binary(source) : source
    logger.debug { "#{headers}\n\n#{body}" }
  else
    logger.debug { headers }
  end
end

#log_response_body_inline(response) ⇒ HTTP::Response

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Log response with body inline (for non-streaming string bodies)

Parameters:

Returns:



131
132
133
134
135
136
137
138
139
140
# File 'lib/http/features/logging.rb', line 131

def log_response_body_inline(response)
  body    = response.body
  headers = stringify_headers(response.headers)
  if body.respond_to?(:encoding) && body.encoding.eql?(Encoding::BINARY)
    logger.debug { "#{headers}\n\n#{format_binary(body)}" } # steep:ignore
  else
    logger.debug { "#{headers}\n\n#{body}" }
  end
  response
end

#logged_body(body) ⇒ HTTP::Response::Body

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Wrap a response body with a logging stream

Parameters:

Returns:



160
161
162
163
164
# File 'lib/http/features/logging.rb', line 160

def logged_body(body)
  formatter = (method(:format_binary) unless body.loggable?)
  stream = BodyLogger.new(body.instance_variable_get(:@stream), logger, formatter: formatter) # steep:ignore
  Response::Body.new(stream, encoding: body.encoding)
end

#logged_response_options(response) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Build options hash for a response with body logging

Parameters:

Returns:

  • (Hash)


145
146
147
148
149
150
151
152
153
154
155
# File 'lib/http/features/logging.rb', line 145

def logged_response_options(response)
  {
    status:        response.status,
    version:       response.version,
    headers:       response.headers,
    proxy_headers: response.proxy_headers,
    connection:    response.connection,
    body:          logged_body(response.body),
    request:       response.request
  }
end

#stringify_headers(headers) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Convert headers to a string representation

Parameters:

Returns:

  • (String)


183
184
185
# File 'lib/http/features/logging.rb', line 183

def stringify_headers(headers)
  headers.map { |name, value| "#{name}: #{value}" }.join("\n")
end

#validate_binary_formatter!(formatter) ⇒ :stats, ...

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Validate and return the binary_formatter option

Parameters:

  • formatter (:stats, :base64, ^(String) -> String)

Returns:

  • (:stats, :base64, #call)

Raises:

  • (ArgumentError)

    if the formatter is not a valid option



106
107
108
109
110
111
112
# File 'lib/http/features/logging.rb', line 106

def validate_binary_formatter!(formatter)
  return formatter if formatter.eql?(:stats) || formatter.eql?(:base64) || formatter.respond_to?(:call)

  raise ArgumentError,
        "binary_formatter must be :stats, :base64, or a callable " \
        "(got #{formatter.inspect})"
end

#wrap_request(request) ⇒ HTTP::Request

Logs and returns the request

Examples:

feature.wrap_request(request)

Parameters:

Returns:



74
75
76
77
78
79
# File 'lib/http/features/logging.rb', line 74

def wrap_request(request)
  logger.info { format("> %s %s", String(request.verb).upcase, request.uri) }
  log_request_details(request)

  request
end

#wrap_response(response) ⇒ HTTP::Response

Logs and returns the response

Examples:

feature.wrap_response(response)

Parameters:

Returns:



89
90
91
92
93
94
95
96
97
98
# File 'lib/http/features/logging.rb', line 89

def wrap_response(response)
  logger.info { "< #{response.status}" }

  return log_response_body_inline(response) unless response.body.is_a?(Response::Body)

  logger.debug { stringify_headers(response.headers) }
  return response unless logger.debug?

  Response.new(**logged_response_options(response)) # steep:ignore
end