Class: SavvyOpenrouter::ApiCallLogger
- Inherits:
-
Object
- Object
- SavvyOpenrouter::ApiCallLogger
- Defined in:
- lib/savvy_openrouter/api_call_logger.rb
Overview
Persists OpenRouter HTTP exchanges when configured via api_call_log (YAML or Client kwargs). Failures while saving never raise into application code.
Column map (columns hash): every source_key => db_column entry is a whitelist. If attrs includes source_key, its value is copied to the row (after optional coercion). This allows app-specific passthrough keys (e.g. bill_forward_event_id) without extending the gem enum.
Documented source keys populated by Connection/resources: method, path, status, http_status, duration_ms, request_body, response_body, error_class, error_message, streaming, endpoint, logical_model, generation_id, success, cost, usage, request_json, response_json.
Constant Summary collapse
- DEFAULT_MAX_BODY_BYTES =
65_536- RESERVED_CONFIG_KEYS =
Reserved keys in api_call_log YAML — never treated as column sources.
%w[model columns max_body_bytes chat_attempts responses_attempts].freeze
- CANONICAL_KEYS =
Keys the gem may set automatically (subset); full set is any key allowed in
columns. %w[ method path status http_status duration_ms request_body response_body error_class error_message streaming endpoint logical_model generation_id success cost usage request_json response_json ].freeze
Class Method Summary collapse
- .blank_to_nil(raw) ⇒ Object
- .cost_from_usage(usage) ⇒ Object
- .error_message_from_json_string(str) ⇒ Object
-
.error_message_from_response_body(body) ⇒ Object
Best-effort OpenRouter-style error.message for JSON error bodies (symbol or string keys).
- .format_body_for_log(obj, max_bytes: DEFAULT_MAX_BODY_BYTES) ⇒ Object
- .generation_id_from(response:, parsed_body:) ⇒ Object
Instance Method Summary collapse
- #chat_attempts_final? ⇒ Boolean
- #enabled? ⇒ Boolean
-
#initialize(config) ⇒ ApiCallLogger
constructor
A new instance of ApiCallLogger.
- #max_body_limit ⇒ Object
-
#record(attrs) ⇒ Object
attrsstring-keyed hashes; column mapping selects and renames fields forcreate!. - #responses_attempts_final? ⇒ Boolean
Constructor Details
#initialize(config) ⇒ ApiCallLogger
Returns a new instance of ApiCallLogger.
113 114 115 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 113 def initialize(config) @config = config.is_a?(Hash) ? Configuration.stringify_keys_static(config) : {} end |
Class Method Details
.blank_to_nil(raw) ⇒ Object
55 56 57 58 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 55 def blank_to_nil(raw) t = raw.to_s.strip t.empty? ? nil : t end |
.cost_from_usage(usage) ⇒ Object
85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 85 def cost_from_usage(usage) return unless usage.is_a?(Hash) u = usage.transform_keys(&:to_s) v = u["cost"] || u["total_cost"] return if v.nil? BigDecimal(v.to_s) rescue ArgumentError nil end |
.error_message_from_json_string(str) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 73 def (str) return unless str.is_a?(String) stripped = str.strip return unless stripped.start_with?("{", "[") parsed = JSON.parse(stripped, symbolize_names: true) (parsed) if parsed.is_a?(Hash) rescue JSON::ParserError nil end |
.error_message_from_response_body(body) ⇒ Object
Best-effort OpenRouter-style error.message for JSON error bodies (symbol or string keys).
61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 61 def (body) return unless body.is_a?(Hash) err = body[:error] || body["error"] case err when Hash blank_to_nil((err[:message] || err["message"]).to_s) when String blank_to_nil(err) end end |
.format_body_for_log(obj, max_bytes: DEFAULT_MAX_BODY_BYTES) ⇒ Object
31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 31 def format_body_for_log(obj, max_bytes: DEFAULT_MAX_BODY_BYTES) str = case obj when nil then +"" when String then obj.b else JSON.generate(obj) end str = redact_secrets(str) truncate_bytes(str, max_bytes) end |
.generation_id_from(response:, parsed_body:) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 43 def generation_id_from(response:, parsed_body:) h = response.respond_to?(:headers) ? response.headers : nil if h raw = h["x-generation-id"] || h["X-Generation-Id"] || h["X-GENERATION-ID"] gid = blank_to_nil(raw&.to_s) return gid if gid end return unless parsed_body.is_a?(Hash) blank_to_nil((parsed_body[:id] || parsed_body["id"]).to_s) end |
Instance Method Details
#chat_attempts_final? ⇒ Boolean
128 129 130 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 128 def chat_attempts_final? @config["chat_attempts"].to_s == "final" end |
#enabled? ⇒ Boolean
117 118 119 120 121 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 117 def enabled? m = @config["model"] !m.nil? && !m.to_s.strip.empty? && @config["columns"].is_a?(Hash) && !@config["columns"].empty? end |
#max_body_limit ⇒ Object
123 124 125 126 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 123 def max_body_limit n = @config["max_body_bytes"] n.is_a?(Integer) && n.positive? ? n : DEFAULT_MAX_BODY_BYTES end |
#record(attrs) ⇒ Object
attrs string-keyed hashes; column mapping selects and renames fields for create!.
137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 137 def record(attrs) return unless enabled? row = build_row(attrs) return if row.empty? constantize_model(@config["model"].to_s.strip).create!(row) rescue StandardError => e warn "[savvy_openrouter] api_call_log skipped: #{e.class}: #{e.}" if $VERBOSE nil end |
#responses_attempts_final? ⇒ Boolean
132 133 134 |
# File 'lib/savvy_openrouter/api_call_logger.rb', line 132 def responses_attempts_final? @config["responses_attempts"].to_s == "final" end |