From 6b00eab67057405dc5495523a0c20f8f6eb02239 Mon Sep 17 00:00:00 2001 From: Scott Florentino Date: Thu, 26 Mar 2026 11:23:11 -0700 Subject: [PATCH 1/2] feat: add UIPATH_LOG_TO_FILE env var for file logging without job context Adds a `log_to_file` flag that enables file-based logging (`__uipath/execution.log`) without requiring `UIPATH_JOB_KEY` to be set. This allows developers to reproduce and debug file-logging issues locally without triggering job-related side effects (licensing headers, output.json writes, state file preservation). Set `UIPATH_LOG_TO_FILE=true` to enable. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/uipath/runtime/context.py | 5 +++++ src/uipath/runtime/logging/_interceptor.py | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/uipath/runtime/context.py b/src/uipath/runtime/context.py index c77ceb0..10b3426 100644 --- a/src/uipath/runtime/context.py +++ b/src/uipath/runtime/context.py @@ -32,6 +32,7 @@ class UiPathRuntimeContext(BaseModel): resume: bool = False command: str | None = None job_id: str | None = None + log_to_file: bool = False conversation_id: str | None = Field( None, description="Conversation identifier for CAS" ) @@ -188,6 +189,7 @@ def __enter__(self): dir=self.runtime_dir, file=self.logs_file, job_id=self.job_id, + log_to_file=self.log_to_file, ) self.logs_interceptor.setup() @@ -327,6 +329,9 @@ def with_defaults( # Apply defaults from env base.job_id = os.environ.get("UIPATH_JOB_KEY") + base.log_to_file = ( + os.environ.get("UIPATH_LOG_TO_FILE", "").lower() == "true" + ) base.logs_min_level = os.environ.get("LOG_LEVEL", "INFO") base.org_id = os.environ.get("UIPATH_ORGANIZATION_ID") base.tenant_id = os.environ.get("UIPATH_TENANT_ID") diff --git a/src/uipath/runtime/logging/_interceptor.py b/src/uipath/runtime/logging/_interceptor.py index 85d4a1e..7c1de0b 100644 --- a/src/uipath/runtime/logging/_interceptor.py +++ b/src/uipath/runtime/logging/_interceptor.py @@ -28,6 +28,7 @@ def __init__( job_id: str | None = None, execution_id: str | None = None, log_handler: logging.Handler | None = None, + log_to_file: bool = False, ): """Initialize the log interceptor. @@ -38,6 +39,7 @@ def __init__( job_id (str, optional): If provided, logs go to file; otherwise, to stdout. execution_id (str, optional): Unique identifier for this execution context. log_handler (logging.Handler, optional): Custom log handler to use for this execution context. + log_to_file (bool): If True, force file logging even without a job_id. """ min_level = min_level or "INFO" self.job_id = job_id @@ -67,8 +69,8 @@ def __init__( if log_handler: self.log_handler = log_handler else: - # Create either file handler (runtime) or stdout handler (debug) - if not job_id: + # Create either file handler (runtime/log_to_file) or stdout handler (debug) + if not job_id and not log_to_file: # Only wrap if stdout is using a problematic encoding (like cp1252 on Windows) if ( hasattr(sys.stdout, "encoding") From 6d61d6cb0be06291d6c843e49e0a3983ce334ad1 Mon Sep 17 00:00:00 2001 From: Scott Florentino Date: Thu, 26 Mar 2026 22:44:02 -0700 Subject: [PATCH 2/2] chore: run formatting --- src/uipath/runtime/context.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/uipath/runtime/context.py b/src/uipath/runtime/context.py index 10b3426..cd8dfaf 100644 --- a/src/uipath/runtime/context.py +++ b/src/uipath/runtime/context.py @@ -329,9 +329,7 @@ def with_defaults( # Apply defaults from env base.job_id = os.environ.get("UIPATH_JOB_KEY") - base.log_to_file = ( - os.environ.get("UIPATH_LOG_TO_FILE", "").lower() == "true" - ) + base.log_to_file = os.environ.get("UIPATH_LOG_TO_FILE", "").lower() == "true" base.logs_min_level = os.environ.get("LOG_LEVEL", "INFO") base.org_id = os.environ.get("UIPATH_ORGANIZATION_ID") base.tenant_id = os.environ.get("UIPATH_TENANT_ID")