Class: Rubino::Logger

Inherits:
Object
  • Object
show all
Defined in:
lib/rubino/logger.rb

Overview

Structured JSON-line logger with built-in redaction of sensitive fields.

Rubino.logger.info(event: "api.server.starting", port: 4820)
#=> {"ts":"2026-05-31T...","level":"info","event":"api.server.starting","port":4820}

Each level method (#debug, #info, #warn, #error, #fatal) takes **fields and emits one structured line per call.

Configuration via environment:

RUBINO_LOG_LEVEL  — debug|info|warn|error|fatal (default: info)
RUBINO_LOG_FORMAT — json|pretty                  (default: json)

Redaction: any key whose name (case-insensitive) appears in REDACT_KEYS is replaced with REDACTED at any nesting depth before the line is serialized. This covers tokens, secrets, and raw Authorization headers passing through middleware logs.

Constant Summary collapse

LEVELS =
{ debug: ::Logger::DEBUG, info: ::Logger::INFO, warn: ::Logger::WARN, error: ::Logger::ERROR,
fatal: ::Logger::FATAL }.freeze
REDACT_KEYS =

Keys (matched case-insensitively against String form) whose values are replaced with REDACTED before logging. Recursive — applies at any depth.

%w[
  access_token refresh_token id_token
  client_secret api_key password secret bearer
  authorization http_authorization
].freeze
REDACTED =

Replacement string written in place of redacted values.

"[REDACTED]"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io: $stdout, level: ENV.fetch("RUBINO_LOG_LEVEL", "info"), format: ENV.fetch("RUBINO_LOG_FORMAT", "json")) ⇒ Logger

Returns a new instance of Logger.



38
39
40
41
42
43
44
# File 'lib/rubino/logger.rb', line 38

def initialize(io: $stdout, level: ENV.fetch("RUBINO_LOG_LEVEL", "info"),
               format: ENV.fetch("RUBINO_LOG_FORMAT", "json"))
  @logger = ::Logger.new(io)
  @logger.level = LEVELS.fetch(level.to_sym, ::Logger::INFO)
  @format = format.to_sym
  @logger.formatter = formatter
end

Class Method Details

.redact(value) ⇒ Object

Recursively walk a value, masking entries whose key matches REDACT_KEYS. Hash and Array are descended; scalars pass through unchanged. Public because middleware and tests call it directly.

Parameters:

  • value (Object)

    any value (typically Hash, Array, or scalar)

Returns:

  • (Object)

    same shape as input with sensitive values replaced by REDACTED



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rubino/logger.rb', line 74

def self.redact(value)
  case value
  when Hash
    value.each_with_object({}) do |(k, v), out|
      out[k] = REDACT_KEYS.include?(k.to_s.downcase) ? REDACTED : redact(v)
    end
  when Array
    value.map { |v| redact(v) }
  else
    value
  end
end

Instance Method Details

#reopen(io) ⇒ Object

Rebinds the underlying sink to a new IO (or path) WITHOUT replacing the Logger object, so existing references (and the memoized Rubino.logger) keep working. Level and format are preserved.

The interactive CLI uses this to route structured JSON lines to a file instead of the terminal $stdout that the raw-mode TUI owns (#125): otherwise a warn/info event (e.g. a network blip during a background subagent) prints raw JSON into the rendered conversation and corrupts the bottom-composer frame. Returns the previous IO so the caller can restore it on exit.



56
57
58
59
60
# File 'lib/rubino/logger.rb', line 56

def reopen(io)
  previous = @logger.instance_variable_get(:@logdev)&.dev
  @logger.reopen(io)
  previous
end