Class: Smplkit::Logging::LoggingClient

Inherits:
Object
  • Object
show all
Defined in:
lib/smplkit/logging/client.rb

Overview

The Smpl Logging client (sync).

One client exposes the full surface, reachable as client.logging (Smplkit::Client) or constructed directly:

logging = Smplkit::LoggingClient.new(environment: "production", service: "my-svc")
logging.loggers.new("sqlalchemy.engine").save
logging.install

The management surface (loggers / log_groups sub-clients) works immediately. register_adapter is a pre-install configuration call. The live surface (install / on_change / refresh) requires install first; calling on_change / refresh earlier raises NotInstalledError.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key = nil, environment: nil, base_url: nil, profile: nil, base_domain: nil, scheme: nil, debug: nil, extra_headers: nil, parent: nil, transport: nil, metrics: nil) ⇒ LoggingClient

Returns a new instance of LoggingClient.



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/smplkit/logging/client.rb', line 339

def initialize(api_key = nil, environment: nil, base_url: nil, profile: nil,
               base_domain: nil, scheme: nil, debug: nil, extra_headers: nil,
               parent: nil, transport: nil, metrics: nil)
  @parent = parent
  @metrics = metrics
  @environment = parent.nil? ? environment : parent._environment
  @service = parent&._service
  @standalone_api_key = nil
  if transport.nil?
    @logging_http, _app_http, @app_base_url, @standalone_api_key = Logging.logging_transport(
      api_key: api_key, base_url: base_url, profile: profile,
      base_domain: base_domain, scheme: scheme, debug: debug, extra_headers: extra_headers
    )
  else
    @logging_http = transport
    @app_base_url = nil
  end

  # Discovery buffer is owned by this client; the loggers sub-client shares
  # it so discovery and explicit registration drain together.
  @buffer = LoggerRegistrationBuffer.new
  @loggers = LoggersClient.new(@logging_http, buffer: @buffer)
  @log_groups = LogGroupsClient.new(@logging_http)

  # Live-surface state.
  @connected = false
  @name_map = {}        # original_name → normalized_id
  @loggers_cache = {}   # id → logger data
  @groups_cache = {}    # id → group data
  @global_listeners = []
  @key_listeners = Hash.new { |h, k| h[k] = [] }
  @adapters = []
  @explicit_adapters = false
  @ws_manager = nil
  @owns_ws = false
  @lock = Mutex.new
end

Instance Attribute Details

#log_groupsObject (readonly)

Returns the value of attribute log_groups.



337
338
339
# File 'lib/smplkit/logging/client.rb', line 337

def log_groups
  @log_groups
end

#loggersObject (readonly)

Returns the value of attribute loggers.



337
338
339
# File 'lib/smplkit/logging/client.rb', line 337

def loggers
  @loggers
end

Class Method Details

.open(**kwargs) ⇒ Object

Construct, yield to the block, and close on exit.



516
517
518
519
520
521
522
523
# File 'lib/smplkit/logging/client.rb', line 516

def self.open(**kwargs)
  client = new(**kwargs)
  begin
    yield client
  ensure
    client.close
  end
end

Instance Method Details

#adaptersObject



395
396
397
# File 'lib/smplkit/logging/client.rb', line 395

def adapters
  @adapters.dup
end

#closeObject Also known as: _close

Release resources — only those this client owns.

Uninstalls the adapter hooks, unsubscribes from the WebSocket, and tears down the owned WebSocket (standalone install). A wired client borrows the parent’s transport and WebSocket and closes neither.



496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
# File 'lib/smplkit/logging/client.rb', line 496

def close
  Smplkit.debug("lifecycle", "LoggingClient.close() called")
  @adapters.each do |adapter|
    adapter.uninstall_hook
  rescue StandardError => e
    Smplkit.debug("logging", "adapter #{adapter.name} uninstall_hook failed: #{e.class}: #{e.message}")
  end
  if @ws_manager
    ws_handlers.each { |event, handler| @ws_manager.off(event, handler) }
    if @owns_ws
      @ws_manager.stop
      @owns_ws = false
    end
    @ws_manager = nil
  end
  @connected = false
end

#installObject

Hook smplkit into the application’s logging machinery.

Loads adapters, scans existing loggers, applies levels from the smplkit server, and wires WebSocket handlers for live updates. This IS the explicit consent gate — on_change / refresh require it first.

Idempotent — safe to call multiple times.



408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/smplkit/logging/client.rb', line 408

def install
  Smplkit.debug("lifecycle", "LoggingClient.install() called")
  @parent&._ensure_started
  return self if @connected

  # 0. Load adapters
  @adapters = Logging.auto_load_adapters if @adapters.empty?

  # 1. Discover existing loggers from all adapters (keep discovery and hook
  # installation as two passes — discover every adapter before any hook is
  # live, mirroring the Python SDK).
  # rubocop:disable Style/CombinableLoops
  @adapters.each do |adapter|
    existing = adapter.discover
    existing.each do |name, explicit_level, effective_level|
      @name_map[name] = Normalize.normalize_logger_name(name)
      @loggers.register(loggersource_for(name, explicit_level, effective_level))
    end
  rescue StandardError => e
    Smplkit.debug("logging", "adapter #{adapter.name} discover failed: #{e.class}: #{e.message}")
  end

  # 2. Install continuous discovery hooks
  @adapters.each do |adapter|
    adapter.install_hook { |name, explicit, effective| on_new_logger(name, explicit, effective) }
  rescue StandardError => e
    Smplkit.debug("logging", "adapter #{adapter.name} install_hook failed: #{e.class}: #{e.message}")
  end
  # rubocop:enable Style/CombinableLoops

  # 3. Flush initial batch
  begin
    @loggers.flush
  rescue StandardError => e
    Smplkit.debug("registration", "bulk logger registration failed: #{e.class}: #{e.message}")
  end

  # 4-6. Fetch, resolve, apply
  begin
    fetch_and_apply(trigger: "install()")
  rescue StandardError => e
    Smplkit.debug("resolution",
                  "failed to fetch/apply logging levels during connect " \
                  "(logging: #{@logging_http&.config&.host}): #{e.class}: #{e.message}")
  end

  # 7. Register WebSocket event handlers for real-time level updates
  @ws_manager = ensure_ws
  ws_handlers.each { |event, handler| @ws_manager.on(event, &handler) }

  @connected = true
  self
end

#on_change(name = nil, &block) ⇒ Object

Register a change listener.

client.logging.on_change { |event| ... }                 # global
client.logging.on_change("sqlalchemy.engine") { |e| ... } # key-scoped

Requires install first; raises NotInstalledError otherwise.

Raises:

  • (ArgumentError)


470
471
472
473
474
475
476
477
478
479
480
# File 'lib/smplkit/logging/client.rb', line 470

def on_change(name = nil, &block)
  require_installed
  raise ArgumentError, "on_change requires a block" unless block

  if name.nil?
    @global_listeners << block
  else
    @key_listeners[name] << block
  end
  block
end

#refreshObject

Re-fetch all loggers and groups and fire listener events for any deltas.

Requires install first; raises NotInstalledError otherwise.



485
486
487
488
489
# File 'lib/smplkit/logging/client.rb', line 485

def refresh
  require_installed
  Smplkit.debug("resolution", "refresh() called, triggering full resolution pass")
  fetch_and_apply_deltas(trigger: "refresh()", source: "manual")
end

#register_adapter(adapter) ⇒ Object

Register a logging adapter. Must be called before install().

If called at least once, auto-loading is disabled — only explicitly registered adapters are used. This is a pre-install configuration call: it is intentionally NOT gated by install.



384
385
386
387
388
389
390
391
392
393
# File 'lib/smplkit/logging/client.rb', line 384

def register_adapter(adapter)
  raise "Cannot register adapters after install()" if @connected
  unless adapter.is_a?(Adapters::Base)
    raise ArgumentError, "adapter must implement Smplkit::Logging::Adapters::Base"
  end

  @explicit_adapters = true
  @adapters << adapter
  self
end