4444"""
4545_METRICS = dict ()
4646
47+ # Global container socket and runtime information for telemetry
48+ # This persists across context boundaries to ensure accurate telemetry reporting
49+ _CONTAINER_RUNTIME_INFO : Dict [str , Optional [str ]] = {"container_socket_path" : None , "admin_preference" : None }
50+
4751
4852@dataclass
4953class ProjectDetails :
@@ -241,9 +245,8 @@ def _send_command_run_metrics(ctx: Context, duration: int, exit_reason: str, exi
241245 # Add container engine information for all command runs
242246 metric_specific_attributes ["containerEngine" ] = metric ._get_container_host ()
243247
244- # Add admin container preference (None if not set)
245- admin_pref = getattr (ctx , "admin_container_preference" , None )
246- metric_specific_attributes ["adminContainerPreference" ] = admin_pref
248+ # Add admin container preference from global storage (set by container client factory)
249+ metric_specific_attributes ["adminContainerPreference" ] = metric ._get_container_admin_preference ()
247250
248251 metric .add_data ("metricSpecificAttributes" , metric_specific_attributes )
249252 # Metric about command's execution characteristics
@@ -404,6 +407,37 @@ def emit_all_metrics():
404407 emit_metric (key )
405408
406409
410+ def set_container_socket_host_telemetry (
411+ container_socket_path : Optional [str ] = None , admin_preference : Optional [str ] = None
412+ ):
413+ """
414+ Set container socket path and admin preference for telemetry collection.
415+
416+ This function stores container socket information globally to ensure it persists
417+ across context boundaries and is available during telemetry collection.
418+
419+ Args:
420+ container_socket_path: The socket path being used (e.g., "unix:///var/run/docker.sock", "unix://~/.finch/finch.sock")
421+ admin_preference: The administrator's container preference (e.g., "finch", "docker", None)
422+ """
423+ if container_socket_path is not None :
424+ _CONTAINER_RUNTIME_INFO ["container_socket_path" ] = container_socket_path
425+ LOG .debug (f"Set global container socket path: container_socket_path={ container_socket_path } " )
426+ if admin_preference is not None :
427+ _CONTAINER_RUNTIME_INFO ["admin_preference" ] = admin_preference
428+ LOG .debug (f"Set global container runtime info: admin_preference={ admin_preference } " )
429+
430+
431+ def get_container_runtime_telemetry_info () -> Dict [str , Optional [str ]]:
432+ """
433+ Get container socket path and runtime information for telemetry collection.
434+
435+ Returns:
436+ Dict containing container_socket_path and admin_preference
437+ """
438+ return _CONTAINER_RUNTIME_INFO
439+
440+
407441class Metric :
408442 """
409443 Metric class to store metric data and adding common attributes
@@ -487,35 +521,31 @@ def _get_execution_environment(self) -> str:
487521
488522 def _get_container_host (self ) -> str :
489523 """
490- Returns the container engine being used with context-first detection:
491- 1. If actual runtime type is stored in context, use that (for native SAM CLI clients like Finch )
524+ Returns the container engine being used with socket path detection:
525+ 1. Check global container socket path (set by container client factory )
492526 2. Otherwise, fall back to DOCKER_HOST environment variable detection (for custom configurations)
493527 """
494- try :
495- # First, try to get the actual runtime type from context (same pattern as admin_container_preference)
496- ctx = Context .get_current_context ()
497- if ctx :
498- actual_runtime = getattr (ctx , "actual_container_runtime" , None )
499- if actual_runtime :
500- return str (actual_runtime )
501- except (RuntimeError , ImportError ):
502- # No Click context available
503- pass
528+ # First, check global container socket path (set by container client factory)
529+ global_runtime_info = get_container_runtime_telemetry_info ()
530+ if global_runtime_info ["container_socket_path" ]:
531+ LOG .debug (f"Using global container socket path: { global_runtime_info ['container_socket_path' ]} " )
532+ return self ._get_container_engine_from_socket_path (global_runtime_info ["container_socket_path" ])
504533
505534 # Fall back to environment variable detection for custom DOCKER_HOST configurations
506- LOG .debug ("No container runtime in context, falling back to DOCKER_HOST detection" )
507- return self ._get_container_host_from_env ()
535+ docker_host = os .environ .get ("DOCKER_HOST" , "" )
536+ LOG .debug (f"No container socket path in global storage, falling back to DOCKER_HOST detection: { docker_host } " )
537+ return self ._get_container_engine_from_socket_path (docker_host )
508538
509- def _get_container_host_from_env (self ) -> str :
539+ def _get_container_engine_from_socket_path (self , socket_path : str ) -> str :
510540 """
511- Detect container engine from environment variables for custom DOCKER_HOST configurations .
512- This handles cases where customers manually set DOCKER_HOST to non-native runtimes .
541+ Detect container engine from socket path .
542+ This handles detection for any socket path, whether from stored telemetry or environment .
513543
514544 Translation:
515545 Value Final Value
516546
517- # Case 1: Not set
518- - (unset) -> docker-default
547+ # Case 1: Empty/None
548+ - "" -> docker-default
519549
520550 # Case 2: Check Path-specific
521551 - unix://~/.colima/default/docker.sock -> colima
@@ -536,38 +566,45 @@ def _get_container_host_from_env(self) -> str:
536566 # Case 5: Other
537567 - other value -> unknown
538568 """
539- # Read current DOCKER_HOST directly from environment instead of cached value
540- docker_host = os .environ .get ("DOCKER_HOST" , "" )
541-
542- if not docker_host :
569+ if not socket_path :
543570 return "docker-default"
544571
545- docker_host = docker_host .lower ()
572+ socket_path_lower = socket_path .lower ()
546573
547574 # Path-specific mappings (checked first for specificity)
548575 path_map = {".colima/" : "colima" , ".lima/" : "lima" , ".orbstack/" : "orbstack" , ".rd/" : "rancher-desktop" }
549576
550577 # Check path-specific patterns first
551578 for path , engine in path_map .items ():
552- if path in docker_host and docker_host .endswith ("docker.sock" ):
579+ if path in socket_path_lower and socket_path_lower .endswith ("docker.sock" ):
553580 return engine
554581
555582 # Socket mappings
556583 socket_map = {"docker.sock" : "docker" , "finch.sock" : "finch" , "podman.sock" : "podman" }
557584
558585 # Check socket patterns
559586 for sock , engine in socket_map .items ():
560- if docker_host .endswith (sock ):
587+ if socket_path_lower .endswith (sock ):
561588 return engine
562589
563590 # TCP patterns
564- if docker_host .startswith ("tcp://" ):
591+ if socket_path_lower .startswith ("tcp://" ):
565592 local_hosts = ["localhost:" , "127.0.0.1:" ]
566- is_local = any (host in docker_host for host in local_hosts )
593+ is_local = any (host in socket_path_lower for host in local_hosts )
567594 return "tcp-local" if is_local else "tcp-remote"
568595
569596 return "unknown"
570597
598+ def _get_container_admin_preference (self ) -> Optional [str ]:
599+ """
600+ Get the administrator's container preference from global storage.
601+
602+ Returns:
603+ Optional[str]: Admin preference value ("finch", "docker", "other", or None)
604+ """
605+ global_runtime_info = get_container_runtime_telemetry_info ()
606+ return global_runtime_info ["admin_preference" ]
607+
571608
572609class MetricDataNotList (Exception ):
573610 pass
0 commit comments