From cb111a8e74dff8f575eda28894fb5921f69cfaad Mon Sep 17 00:00:00 2001 From: Brandur Date: Fri, 11 Aug 2017 11:16:29 -0700 Subject: [PATCH] Add support for setting a logger Adds support for setting `Stripe.logger` to a logger that's compatible with `Logger` from Ruby's standard library. In set, the library will no longer log to stdout, and instead emit straight to the logger and defer decision on what log level to print to it. Addresses a request in #566. --- lib/stripe.rb | 19 +++++++++++++++ lib/stripe/util.rb | 25 ++++++++++++++------ test/stripe/util_test.rb | 50 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/lib/stripe.rb b/lib/stripe.rb index 3a12f8c1..1a833eaf 100644 --- a/lib/stripe.rb +++ b/lib/stripe.rb @@ -84,6 +84,7 @@ module Stripe @uploads_base = 'https://uploads.stripe.com' @log_level = nil + @logger = nil @max_network_retries = 0 @max_network_retry_delay = 2 @@ -151,6 +152,9 @@ module Stripe # what it's doing. For example, it'll produce information about requests, # responses, and errors that are received. Valid log levels are `debug` and # `info`, with `debug` being a little more verbose in places. + # + # Use of this configuration is only useful when `.logger` is _not_ set. When + # it is, the decision what levels to print is entirely deferred to the logger. def self.log_level @log_level end @@ -162,6 +166,21 @@ module Stripe @log_level = val end + # Sets a logger to which logging output will be sent. The logger should + # support the same interface as the `Logger` class that's part of Ruby's + # standard library (hint, anything in `Rails.logger` will likely be + # suitable). + # + # If `.logger` is set, the value of `.log_level` is ignored. The decision on + # what levels to print is entirely deferred to the logger. + def self.logger + @logger + end + + def self.logger=(val) + @logger = val + end + def self.max_network_retries @max_network_retries end diff --git a/lib/stripe/util.rb b/lib/stripe/util.rb index bc3b42e0..883b1242 100644 --- a/lib/stripe/util.rb +++ b/lib/stripe/util.rb @@ -90,16 +90,19 @@ module Stripe end def self.log_info(message, data = {}) - if Stripe.log_level == Stripe::LEVEL_DEBUG ||Stripe.log_level == Stripe::LEVEL_INFO + if !Stripe.logger.nil? || + Stripe.log_level == Stripe::LEVEL_DEBUG || + Stripe.log_level == Stripe::LEVEL_INFO log_internal(message, data, color: :cyan, - level: Stripe::LEVEL_INFO, out: $stdout) + level: Stripe::LEVEL_INFO, logger: Stripe.logger, out: $stdout) end end def self.log_debug(message, data = {}) - if Stripe.log_level == Stripe::LEVEL_DEBUG + if !Stripe.logger.nil? || + Stripe.log_level == Stripe::LEVEL_DEBUG log_internal(message, data, color: :blue, - level: Stripe::LEVEL_DEBUG, out: $stdout) + level: Stripe::LEVEL_DEBUG, logger: Stripe.logger, out: $stdout) end end @@ -340,16 +343,24 @@ module Stripe # TODO: Make these named required arguments when we drop support for Ruby # 2.0. - def self.log_internal(message, data = {}, color: nil, level: nil, out: nil) + def self.log_internal(message, data = {}, color: nil, level: nil, logger: nil, out: nil) data_str = data.select { |k,v| !v.nil? }. map { |(k,v)| "%s=%s" % [ - colorize(k, color, out.isatty), + colorize(k, color, !out.nil? && out.isatty), wrap_logfmt_value(v) ] }.join(" ") - if out.isatty + if !logger.nil? + str = "message=%s %s" % [wrap_logfmt_value(message), data_str] + case level + when Stripe::LEVEL_DEBUG + logger.debug(str) + else # Stripe::LEVEL_INFO (there should be no other values) + logger.info(str) + end + elsif out.isatty out.puts "%s %s %s" % [colorize(level[0, 4].upcase, color, out.isatty), message, data_str] else diff --git a/test/stripe/util_test.rb b/test/stripe/util_test.rb index d8ddcddf..35127c0b 100644 --- a/test/stripe/util_test.rb +++ b/test/stripe/util_test.rb @@ -1,3 +1,4 @@ +require "logger" require File.expand_path('../../test_helper', __FILE__) module Stripe @@ -216,6 +217,35 @@ module Stripe end end + context ".log_* with a logger" do + setup do + @out = StringIO.new + logger = ::Logger.new(@out) + + # Set a really simple formatter to make matching output as easy as + # possible. + logger.formatter = proc { |_severity, _datetime, _progname, message| + message + } + + Stripe.logger = logger + end + + context ".log_debug" do + should "log to the logger" do + Util.log_debug("foo") + assert_equal "message=foo ", @out.string + end + end + + context ".log_info" do + should "log to the logger" do + Util.log_info("foo") + assert_equal "message=foo ", @out.string + end + end + end + context ".normalize_headers" do should "normalize the format of a header key" do assert_equal({ "Request-Id" => nil }, @@ -267,7 +297,7 @@ module Stripe end Util.send(:log_internal, "message", { foo: "bar" }, - color: :green, level: Stripe::LEVEL_DEBUG, out: out) + color: :green, level: Stripe::LEVEL_DEBUG, logger: nil, out: out) assert_equal "\e[0;32;49mDEBU\e[0m message \e[0;32;49mfoo\e[0m=bar\n", out.string end @@ -275,10 +305,26 @@ module Stripe should "log in a data friendly way" do out = StringIO.new Util.send(:log_internal, "message", { foo: "bar" }, - color: :green, level: Stripe::LEVEL_DEBUG, out: out) + color: :green, level: Stripe::LEVEL_DEBUG, logger: nil, out: out) assert_equal "message=message level=debug foo=bar\n", out.string end + + should "log to a logger if set" do + out = StringIO.new + logger = ::Logger.new(out) + + # Set a really simple formatter to make matching output as easy as + # possible. + logger.formatter = proc { |_severity, _datetime, _progname, message| + message + } + + Util.send(:log_internal, "message", { foo: "bar" }, + color: :green, level: Stripe::LEVEL_DEBUG, logger: logger, out: nil) + assert_equal "message=message foo=bar", + out.string + end end context ".wrap_logfmt_value" do