Class: ConvertSdk::LogManager

Inherits:
Object
  • Object
show all
Defined in:
lib/convert_sdk/log_manager.rb

Overview

Multi-sink, level-gated logger with secret redaction wired in by construction.

LogManager is consumed by every manager from the HTTP client (Story 1.5) onward. It fans messages out to any number of stdlib-+Logger+-compatible sinks and guarantees, structurally, that no message reaches a sink without first passing through the Redactor: every public level method funnels through the single private #emit path, and that path applies the loggable conversion boundary and redaction before touching a sink. There is no public method that bypasses #emit.

Levels

Verbosity is gated by the JS-parity LogLevel values (TRACE=0 … SILENT=5). A call at level L emits only when L >= configured_level; SILENT suppresses everything. The stdlib Logger has no trace, so both #trace and #debug dispatch to the sink's #debug — the numeric level value (0 vs 1), not the sink method, decides whether they emit.

Level conventions (callers choose the level by intent):

  • trace / debug — decisioning internals (bucketing, rule evaluation).
  • info — lifecycle events (SDK ready, config refreshed).
  • warn — recoverable conditions (stale config, retry).
  • error — internal failures (parse error, exhausted retries).

Message format

Callers pass messages already formatted as {ClassName}#{method}: {message}. LogManager does not prepend the class name itself — the format is a usage convention, documented here and enforced at call sites.

Thread safety

The sink list is guarded by @sinks_mutex. Compound operations on the list happen inside the lock; the (potentially slow, potentially raising) sink I/O happens outside the lock by iterating a dup snapshot. A sink that raises is contained (rescue StandardError) so a broken sink never crashes the host or starves the other sinks.

Constant Summary collapse

REQUIRED_SINK_METHODS =

The methods every valid sink must respond to (stdlib Logger contract).

%i[debug info warn error].freeze

Instance Method Summary collapse

Constructor Details

#initialize(level: LogLevel::ERROR, sink: nil, secrets: []) ⇒ LogManager

Returns a new instance of LogManager.

Parameters:

  • level (Integer) (defaults to: LogLevel::ERROR)

    a ConvertSdk::LogLevel threshold; messages below it are suppressed. Defaults to ERROR (quiet by default).

  • sink (Object, nil) (defaults to: nil)

    an optional initial sink (anything responding to debug/info/warn/error). Invalid sinks are rejected, not raised.

  • secrets (Array<String>) (defaults to: [])

    secret values to redact from every message. More can be added later via #register_secret.



50
51
52
53
54
55
56
57
# File 'lib/convert_sdk/log_manager.rb', line 50

def initialize(level: LogLevel::ERROR, sink: nil, secrets: [])
  @level = level
  @redactor = Redactor.new(secrets)
  @sinks = []
  # Thread safety: guarded by @sinks_mutex.
  @sinks_mutex = Thread::Mutex.new
  add_sink(sink) unless sink.nil?
end

Instance Method Details

#add_sink(sink) ⇒ self

Register a sink. Accepted iff it duck-types to the stdlib Logger contract (responds to debug/info/warn/error). An invalid sink is rejected with a logged error rather than raising — registration must never crash the host.

Parameters:

  • sink (Object)

    the candidate sink.

Returns:

  • (self)

    for chaining. A rejected sink is logged, not registered.



69
70
71
72
73
74
75
76
77
# File 'lib/convert_sdk/log_manager.rb', line 69

def add_sink(sink)
  if REQUIRED_SINK_METHODS.all? { |m| sink.respond_to?(m) }
    @sinks_mutex.synchronize { @sinks << sink }
  else
    emit(LogLevel::ERROR, "LogManager#add_sink: rejected sink #{sink.class} " \
                          "(must respond to #{REQUIRED_SINK_METHODS.join("/")})")
  end
  self
end

#debug(message) ⇒ Object

Log at DEBUG — decisioning internals. Dispatches to sink #debug.



# File 'lib/convert_sdk/log_manager.rb', line 88

#error(message) ⇒ Object

Log at ERROR — internal failures.



# File 'lib/convert_sdk/log_manager.rb', line 88

#info(message) ⇒ Object

Log at INFO — lifecycle events.



# File 'lib/convert_sdk/log_manager.rb', line 88

#register_secret(secret) ⇒ void

This method returns an undefined value.

Register an additional secret to redact (e.g. once the SDK key is known at ConvertSdk.create time). nil/blank is a no-op.

Parameters:

  • secret (String, nil)


84
85
86
# File 'lib/convert_sdk/log_manager.rb', line 84

def register_secret(secret)
  @redactor.register_secret(secret)
end

#trace(message) ⇒ void

This method returns an undefined value.

Returns log at TRACE (finest-grained); dispatches to the sink's #debug.

Parameters:

  • message (String)

    the already-formatted message.



# File 'lib/convert_sdk/log_manager.rb', line 88

#warn(message) ⇒ Object

Log at WARN — recoverable conditions.



# File 'lib/convert_sdk/log_manager.rb', line 88