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 the event stream (default true): install opens a shared stream 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.



473
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
# File 'lib/smplkit/logging/client.rb', line 473

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
  @event_stream = nil
  @owns_stream = false
  @lock = Mutex.new
end

Instance Attribute Details

#log_groupsObject (readonly)

Returns the value of attribute log_groups.



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

def log_groups
  @log_groups
end

#loggersObject (readonly)

Returns the value of attribute loggers.



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

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 event stream 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.



690
691
692
693
694
695
696
697
# File 'lib/smplkit/logging/client.rb', line 690

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.



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

def adapters
  @adapters.dup
end

#closeObject Also known as: _close

Release resources — only those this client owns.

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



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

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 @event_stream
    stream_handlers.each { |event, handler| @event_stream.off(event, handler) }
    @event_stream.off_reconnect(refetch_callback)
    if @owns_stream
      @event_stream.stop
      @owns_stream = false
    end
    @event_stream = 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 event stream handlers for live updates. This IS the explicit consent gate — on_change / refresh require it first.

Idempotent — safe to call multiple times.

Returns:



556
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
614
615
# File 'lib/smplkit/logging/client.rb', line 556

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 event stream handlers for real-time level updates, plus
  # the bulk-refresh path as the stream's reconnect refetch so a stream
  # outage ends with a full re-sync. In stateless mode
  # (+streaming: false+) no stream is ever created — level changes then
  # arrive only via +refresh+.
  if @streaming
    @event_stream = ensure_event_stream
    stream_handlers.each { |event, handler| @event_stream.on(event, &handler) }
    @event_stream.on_reconnect(refetch_callback)
  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)


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

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.



640
641
642
643
644
# File 'lib/smplkit/logging/client.rb', line 640

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:



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

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