Class: Smplkit::Logging::LoggingClient

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

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 CRUD 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, service: nil, base_url: nil, profile: nil, base_domain: nil, scheme: nil, debug: nil, extra_headers: nil, streaming: true, parent: nil, transport: nil, metrics: nil) ⇒ LoggingClient

Returns a new instance of LoggingClient.

Parameters:

  • api_key (String, nil) (defaults to: nil)

    API key. When omitted, resolved from SMPLKIT_API_KEY or ~/.smplkit.

  • environment (String, nil) (defaults to: nil)

    Deployment environment used to resolve logger levels and to scope discovery declarations. When omitted, resolved from SMPLKIT_ENVIRONMENT or ~/.smplkit.

  • service (String, nil) (defaults to: nil)

    Service name attached to discovery declarations. When omitted, resolved from SMPLKIT_SERVICE or ~/.smplkit. Optional.

  • base_url (String, nil) (defaults to: nil)

    Full logging-service base URL. Usually resolved from +base_domain+/+scheme+; supplied directly by the top-level clients which have already computed it.

  • profile (String, nil) (defaults to: nil)

    Named ~/.smplkit profile section.

  • base_domain (String, nil) (defaults to: nil)

    Base domain for API requests (default "smplkit.com").

  • scheme (String, nil) (defaults to: nil)

    URL scheme (default "https").

  • debug (Boolean, nil) (defaults to: nil)

    Enable SDK debug logging.

  • extra_headers (Hash{String => String}, nil) (defaults to: nil)

    Extra headers attached to every request.

  • streaming (Boolean) (defaults to: true)

    Live updates over WebSocket (default true): install opens a shared socket and server-side level changes stream in. Set false for the stateless apply-once surface: install still loads adapters, flushes discovery, and applies the server's levels — all blocking — but NO socket or background thread is ever created; refresh re-fetches and re-applies on demand. The right shape for serverless and edge runtimes; note that live level changes then arrive only via refresh.

  • parent (Smplkit::Client, nil) (defaults to: nil)

    Internal — the owning client. Not for direct use.

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

    Internal — a pre-built logging transport supplied by a top-level client so the logging surface shares one connection pool. Not for direct use.

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

    Internal — the parent's metrics reporter.



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/smplkit/logging/client.rb', line 474

def initialize(api_key = nil, environment: nil, service: nil, base_url: nil, profile: nil,
               base_domain: nil, scheme: nil, debug: nil, extra_headers: nil,
               streaming: true, parent: nil, transport: nil, metrics: nil)
  @parent = parent
  @metrics = metrics
  @streaming = streaming ? true : false
  @standalone_api_key = nil
  if transport.nil?
    # Standalone: resolve like Smplkit::Client — defaults → ~/.smplkit →
    # SMPLKIT_* env vars → constructor args — environment and service
    # included.
    @logging_http, _app_http, @app_base_url, @standalone_api_key, resolved_env, resolved_service =
      Logging.logging_transport(
        api_key: api_key, base_url: base_url, profile: profile,
        base_domain: base_domain, scheme: scheme, environment: environment,
        service: service, debug: debug, extra_headers: extra_headers
      )
    @environment = parent.nil? ? resolved_env : parent._environment
    @service = parent.nil? ? resolved_service : parent._service
  else
    @logging_http = transport
    @app_base_url = nil
    # Wired: the parent has already resolved environment/service once —
    # its values win over both the raw kwargs and re-resolution.
    @environment = parent.nil? ? environment : parent._environment
    @service = parent.nil? ? service : parent._service
  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, streaming: @streaming)
  @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.



440
441
442
# File 'lib/smplkit/logging/client.rb', line 440

def log_groups
  @log_groups
end

#loggersObject (readonly)

Returns the value of attribute loggers.



440
441
442
# File 'lib/smplkit/logging/client.rb', line 440

def loggers
  @loggers
end

Class Method Details

.open(**kwargs) {|client| ... } ⇒ Object

Construct a LoggingClient, yield it to the block, and close it on exit.

Mirrors Ruby's File.open block form: the client is closed automatically when the block returns or raises, so a standalone client's owned transports and WebSocket are always torn down.

Smplkit::LoggingClient.open(environment: "production") do |logging|
logging.loggers.new("sqlalchemy.engine").save
logging.install
end

Parameters:

  • kwargs (Hash)

    keyword arguments forwarded to new.

Yield Parameters:

Returns:

  • (Object)

    the block's return value.



687
688
689
690
691
692
693
694
# File 'lib/smplkit/logging/client.rb', line 687

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

Instance Method Details

#adaptersArray<Adapters::Base>

Registered logging adapters.

Returns:

  • (Array<Adapters::Base>)

    a copy of the adapters this client uses to discover loggers and apply levels.



544
545
546
# File 'lib/smplkit/logging/client.rb', line 544

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.



649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
# File 'lib/smplkit/logging/client.rb', line 649

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

#deleteBoolean

Parameters:

  • (String)

Returns:

  • (Boolean)


54
# File 'sig/smplkit/logging.rbs', line 54

def delete: (String) -> bool

#getSmplLogger

Parameters:

  • (String)

Returns:



52
# File 'sig/smplkit/logging.rbs', line 52

def get: (String) -> SmplLogger

#installLoggingClient

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.

Returns:



557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/smplkit/logging/client.rb', line 557

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.
  # In stateless mode (+streaming: false+) no socket is ever created —
  # level changes then arrive only via +refresh+.
  if @streaming
    @ws_manager = ensure_ws
    ws_handlers.each { |event, handler| @ws_manager.on(event, &handler) }
  end

  @connected = true
  self
end

#listArray[SmplLogger]

Returns:



53
# File 'sig/smplkit/logging.rbs', line 53

def list: () -> Array[SmplLogger]

#on_change(name = nil) {|arg0| ... } ⇒ Proc

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.

Parameters:

  • (String, nil)

Yields:

Yield Parameters:

  • arg0 (Object)

Yield Returns:

  • (void)

Returns:

  • (Proc)

Raises:

  • (ArgumentError)


623
624
625
626
627
628
629
630
631
632
633
# File 'lib/smplkit/logging/client.rb', line 623

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.



638
639
640
641
642
# File 'lib/smplkit/logging/client.rb', line 638

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) ⇒ LoggingClient

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.

Parameters:

Returns:



529
530
531
532
533
534
535
536
537
538
# File 'lib/smplkit/logging/client.rb', line 529

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