lib/semantic_logger/appender/http.rb in semantic_logger-4.2.0 vs lib/semantic_logger/appender/http.rb in semantic_logger-4.2.1

- old
+ new

@@ -14,208 +14,212 @@ # Example: # SemanticLogger.add_appender( # appender: :http, # url: 'http://localhost:8088/path' # ) -class SemanticLogger::Appender::Http < SemanticLogger::Subscriber - attr_accessor :username, :compress, :header, - :open_timeout, :read_timeout, :continue_timeout - attr_reader :http, :url, :server, :port, :path, :ssl_options +module SemanticLogger + module Appender + class Http < SemanticLogger::Subscriber + attr_accessor :username, :compress, :header, + :open_timeout, :read_timeout, :continue_timeout + attr_reader :http, :url, :server, :port, :path, :ssl_options - # Create HTTP(S) log appender - # - # Parameters: - # url: [String] - # Valid URL to post to. - # Example: http://example.com/some_path - # To enable SSL include https in the URL. - # Example: https://example.com/some_path - # verify_mode will default: OpenSSL::SSL::VERIFY_PEER - # - # application: [String] - # Name of this application to appear in log messages. - # Default: SemanticLogger.application - # - # host: [String] - # Name of this host to appear in log messages. - # Default: SemanticLogger.host - # - # username: [String] - # User name for basic Authentication. - # Default: nil ( do not use basic auth ) - # - # password: [String] - # Password for basic Authentication. - # - # compress: [true|false] - # Whether to compress the JSON string with GZip. - # Default: false - # - # ssl: [Hash] - # Specific SSL options: For more details see NET::HTTP.start - # ca_file, ca_path, cert, cert_store, ciphers, key, ssl_timeout, - # ssl_version, verify_callback, verify_depth and verify_mode. - # - # level: [:trace | :debug | :info | :warn | :error | :fatal] - # Override the log level for this appender. - # Default: SemanticLogger.default_level - # - # formatter: [Object|Proc] - # An instance of a class that implements #call, or a Proc to be used to format - # the output from this appender - # Default: Use the built-in formatter (See: #call) - # - # filter: [Regexp|Proc] - # RegExp: Only include log messages where the class name matches the supplied. - # regular expression. All other messages will be ignored. - # Proc: Only include log messages where the supplied Proc returns true - # The Proc must return true or false. - # - # open_timeout: [Float] - # Default: 2.0 - # - # read_timeout: [Float] - # Default: 1.0 - # - # continue_timeout: [Float] - # Default: 1.0 - def initialize(url:, - compress: false, - ssl: {}, - username: nil, - password: nil, - open_timeout: 2.0, - read_timeout: 1.0, - continue_timeout: 1.0, - level: nil, - formatter: nil, - filter: nil, - application: nil, - host: nil, - &block) + # Create HTTP(S) log appender + # + # Parameters: + # url: [String] + # Valid URL to post to. + # Example: http://example.com/some_path + # To enable SSL include https in the URL. + # Example: https://example.com/some_path + # verify_mode will default: OpenSSL::SSL::VERIFY_PEER + # + # application: [String] + # Name of this application to appear in log messages. + # Default: SemanticLogger.application + # + # host: [String] + # Name of this host to appear in log messages. + # Default: SemanticLogger.host + # + # username: [String] + # User name for basic Authentication. + # Default: nil ( do not use basic auth ) + # + # password: [String] + # Password for basic Authentication. + # + # compress: [true|false] + # Whether to compress the JSON string with GZip. + # Default: false + # + # ssl: [Hash] + # Specific SSL options: For more details see NET::HTTP.start + # ca_file, ca_path, cert, cert_store, ciphers, key, ssl_timeout, + # ssl_version, verify_callback, verify_depth and verify_mode. + # + # level: [:trace | :debug | :info | :warn | :error | :fatal] + # Override the log level for this appender. + # Default: SemanticLogger.default_level + # + # formatter: [Object|Proc] + # An instance of a class that implements #call, or a Proc to be used to format + # the output from this appender + # Default: Use the built-in formatter (See: #call) + # + # filter: [Regexp|Proc] + # RegExp: Only include log messages where the class name matches the supplied. + # regular expression. All other messages will be ignored. + # Proc: Only include log messages where the supplied Proc returns true + # The Proc must return true or false. + # + # open_timeout: [Float] + # Default: 2.0 + # + # read_timeout: [Float] + # Default: 1.0 + # + # continue_timeout: [Float] + # Default: 1.0 + def initialize(url:, + compress: false, + ssl: {}, + username: nil, + password: nil, + open_timeout: 2.0, + read_timeout: 1.0, + continue_timeout: 1.0, + level: nil, + formatter: nil, + filter: nil, + application: nil, + host: nil, + &block) - @url = url - @ssl_options = ssl - @username = username - @password = password - @compress = compress - @open_timeout = open_timeout - @read_timeout = read_timeout - @continue_timeout = continue_timeout + @url = url + @ssl_options = ssl + @username = username + @password = password + @compress = compress + @open_timeout = open_timeout + @read_timeout = read_timeout + @continue_timeout = continue_timeout - # On Ruby v2.0 and greater, Net::HTTP.new already uses a persistent connection if the server allows it - @header = { - 'Accept' => 'application/json', - 'Content-Type' => 'application/json', - 'Connection' => 'keep-alive', - 'Keep-Alive' => '300' - } - @header['Content-Encoding'] = 'gzip' if @compress + # On Ruby v2.0 and greater, Net::HTTP.new already uses a persistent connection if the server allows it + @header = { + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'Connection' => 'keep-alive', + 'Keep-Alive' => '300' + } + @header['Content-Encoding'] = 'gzip' if @compress - uri = URI.parse(@url) - @server = uri.host - raise(ArgumentError, "Invalid format for :url: #{@url.inspect}. Should be similar to: 'http://hostname:port/path'") unless @server + uri = URI.parse(@url) + @server = uri.host + raise(ArgumentError, "Invalid format for :url: #{@url.inspect}. Should be similar to: 'http://hostname:port/path'") unless @server - @port = uri.port - @username = uri.user if !@username && uri.user - @password = uri.password if !@password && uri.password - @path = uri.path - # Path cannot be empty - @path = '/' if @path == '' + @port = uri.port + @username = uri.user if !@username && uri.user + @password = uri.password if !@password && uri.password + @path = uri.path + # Path cannot be empty + @path = '/' if @path == '' - if uri.scheme == 'https' - @ssl_options[:use_ssl] = true - @ssl_options[:verify_mode] ||= OpenSSL::SSL::VERIFY_PEER - @port ||= HTTP.https_default_port - else - @port ||= HTTP.http_default_port - end - @http = nil + if uri.scheme == 'https' + @ssl_options[:use_ssl] = true + @ssl_options[:verify_mode] ||= OpenSSL::SSL::VERIFY_PEER + @port ||= HTTP.https_default_port + else + @port ||= HTTP.http_default_port + end + @http = nil - super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block) - reopen - end + super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block) + reopen + end - # Re-open after process fork - def reopen - # Close open connection if any - begin - @http.finish if @http - rescue IOError - end + # Re-open after process fork + def reopen + # Close open connection if any + begin + @http&.finish + rescue IOError + nil + end - @http = Net::HTTP.new(server, port) + @http = Net::HTTP.new(server, port) - if @ssl_options - @http.methods.grep(/\A(\w+)=\z/) do |meth| - key = $1.to_sym - @ssl_options.key?(key) or next - @http.__send__(meth, @ssl_options[key]) + if @ssl_options + @http.methods.grep(/\A(\w+)=\z/) do |meth| + key = Regexp.last_match(1).to_sym + @ssl_options.key?(key) || next + @http.__send__(meth, @ssl_options[key]) + end + end + + @http.open_timeout = @open_timeout + @http.read_timeout = @read_timeout + @http.continue_timeout = @continue_timeout + @http.start end - end - @http.open_timeout = @open_timeout - @http.read_timeout = @read_timeout - @http.continue_timeout = @continue_timeout - @http.start - end + # Forward log messages to HTTP Server + def log(log) + message = formatter.call(log, self) + logger.trace(message) + post(message) + end - # Forward log messages to HTTP Server - def log(log) - message = formatter.call(log, self) - logger.trace(message) - post(message) - end + private - private + # Use JSON Formatter by default + def default_formatter + SemanticLogger::Formatters::Json.new + end - # Use JSON Formatter by default - def default_formatter - SemanticLogger::Formatters::Json.new - end + def compress_data(data) + str = StringIO.new + gz = Zlib::GzipWriter.new(str) + gz << data + gz.close + str.string + end - def compress_data(data) - str = StringIO.new - gz = Zlib::GzipWriter.new(str) - gz << data - gz.close - str.string - end + # HTTP Post + def post(body, request_uri = path) + request = Net::HTTP::Post.new(request_uri, @header) + process_request(request, body) + end - # HTTP Post - def post(body, request_uri = path) - request = Net::HTTP::Post.new(request_uri, @header) - process_request(request, body) - end + # HTTP Put + def put(body, request_uri = path) + request = Net::HTTP::Put.new(request_uri, @header) + process_request(request, body) + end - # HTTP Put - def put(body, request_uri = path) - request = Net::HTTP::Put.new(request_uri, @header) - process_request(request, body) - end + # HTTP Delete + def delete(request_uri = path) + request = Net::HTTP::Delete.new(request_uri, @header) + process_request(request) + end - # HTTP Delete - def delete(request_uri = path) - request = Net::HTTP::Delete.new(request_uri, @header) - process_request(request) - end - - # Process HTTP Request - def process_request(request, body = nil) - if body - request.body = compress ? compress_data(body) : body + # Process HTTP Request + def process_request(request, body = nil) + if body + request.body = compress ? compress_data(body) : body + end + request.basic_auth(@username, @password) if @username + response = @http.request(request) + if response.code == '200' || response.code == '201' + true + else + # Failures are logged to the global semantic logger failsafe logger (Usually stderr or file) + logger.error("Bad HTTP response from: #{url} code: #{response.code}, #{response.body}") + false + end + rescue RuntimeError => exc + reopen + raise exc + end end - request.basic_auth(@username, @password) if @username - response = @http.request(request) - if response.code == '200' || response.code == '201' - true - else - # Failures are logged to the global semantic logger failsafe logger (Usually stderr or file) - logger.error("Bad HTTP response from: #{url} code: #{response.code}, #{response.body}") - false - end - rescue RuntimeError => exc - reopen - raise exc end - end