Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/0008-debug-observability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"pan-scm-cli": minor
---

Added a global `--debug` flag that enables DEBUG logging (including SDK OAuth/auth traffic, previously impossible to surface) and full tracebacks on unexpected errors. Logging is now configured once at CLI startup with a quiet WARNING default (precedence: `--debug` > `SCM_LOG_LEVEL` > context/settings `log_level` > WARNING); invalid log levels warn instead of crashing. Identity profile upserts no longer swallow update failures — real API errors now surface instead of a misleading create-path error.
32 changes: 29 additions & 3 deletions docs-site/docs/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ The `settings.yaml` file contains default application settings:
```yaml
---
default:
log_level: "INFO"
log_level: "WARNING"

production:
log_level: "WARNING"
Expand All @@ -331,7 +331,7 @@ The following legacy configuration methods are no longer supported as of version

| Setting | Default | Environment Variable | Description |
| --- | --- | --- | --- |
| `log_level` | `"INFO"` | `SCM_LOG_LEVEL` | Controls SDK client logging verbosity |
| `log_level` | `"WARNING"` | `SCM_LOG_LEVEL` | Controls logging verbosity (CRITICAL, ERROR, WARNING, INFO, DEBUG) |
| `client_id` | None | `SCM_CLIENT_ID` | SCM API authentication |
| `client_secret` | None | `SCM_CLIENT_SECRET` | SCM API authentication |
| `tsg_id` | None | `SCM_TSG_ID` | SCM API tenant identification |
Expand Down Expand Up @@ -362,7 +362,33 @@ scm context test --mock
scm context test staging --mock
```

## Debugging Configuration
## Debugging

### The --debug Flag

Pass the global `--debug` flag to any command for full diagnostics:

```bash
scm --debug show object address --folder Texas
```

`--debug` enables DEBUG-level logging (including the SDK's OAuth/auth loggers,
which are silenced at every other level) and prints full tracebacks on
unexpected errors instead of one-line messages.

### Log Levels

Logging is configured once at CLI startup with this precedence:

1. `--debug` flag (forces DEBUG)
2. `SCM_LOG_LEVEL` environment variable
3. `log_level` in the active context or `settings.yaml`
4. Default: `WARNING`

Valid levels: `CRITICAL`, `ERROR`, `WARNING`, `INFO`, `DEBUG`. An invalid value
produces a warning and falls back to `WARNING`.

### Debugging Configuration

To see which configuration values are being loaded:

Expand Down
2 changes: 1 addition & 1 deletion settings.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
default:
# Logging configuration
log_level: "INFO"
log_level: "WARNING"

production:
# Production-specific settings
Expand Down
6 changes: 3 additions & 3 deletions src/scm_cli/commands/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def show_command(
else:
console.print(" Client Secret: [red]Not set[/red]")

console.print(f" Log Level: {config.get('log_level', 'INFO')}")
console.print(f" Log Level: {config.get('log_level', 'WARNING')}")
console.print(f" API Base URL: {config.get('api_base_url') or '[dim]SDK default[/dim]'}")
console.print(f" Token URL: {config.get('token_url') or '[dim]SDK default[/dim]'}")

Expand Down Expand Up @@ -142,7 +142,7 @@ def create_command(
help="Bearer access token for direct authentication (alternative to OAuth2)",
),
log_level: str = typer.Option(
"INFO",
"WARNING",
"--log-level",
"-l",
help="Logging level",
Expand Down Expand Up @@ -382,7 +382,7 @@ def test_command(
"client_id": config.get("client_id"),
"client_secret": config.get("client_secret"),
"tsg_id": config.get("tsg_id"),
"log_level": config.get("log_level", "INFO"),
"log_level": config.get("log_level", "WARNING"),
}
# Endpoint overrides — omit when unset so SDK defaults apply
if config.get("api_base_url"):
Expand Down
28 changes: 0 additions & 28 deletions src/scm_cli/commands/insights.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@

import typer

from ..utils.config import settings
from ..utils.context import get_current_context
from ..utils.decorators import handle_command_errors
from ..utils.output import OutputFormat, emit, info, success
from ..utils.sdk_client import scm_client
Expand All @@ -24,20 +22,6 @@
# =============================================================================================================================================================================================


def show_context_info() -> None:
"""Display current context information if log level is INFO."""
log_level = settings.get("log_level", "INFO").upper()
if log_level == "INFO":
current_context = get_current_context()
if current_context:
typer.echo(f"[INFO] Using authentication context: {current_context}", err=True)
else:
typer.echo(
"[INFO] No context set, using environment variables or default settings",
err=True,
)


def export_data(data: list[dict[str, Any]], export_format: str, output_file: str) -> None:
"""Export data to the specified format.

Expand Down Expand Up @@ -155,8 +139,6 @@ def show_alerts(
scm insights alerts --real-time

"""
show_context_info()

# Note: scm_client automatically uses mock mode when no credentials are available

if alert_id:
Expand Down Expand Up @@ -257,8 +239,6 @@ def show_mobile_users(
scm insights mobile-users --list --export json --output users.json

"""
show_context_info()

# Note: scm_client automatically uses mock mode when no credentials are available

if user_id:
Expand Down Expand Up @@ -337,8 +317,6 @@ def show_locations(
scm insights locations --list --export csv --output locations.csv

"""
show_context_info()

# Note: scm_client automatically uses mock mode when no credentials are available

if location_id:
Expand Down Expand Up @@ -420,8 +398,6 @@ def show_remote_networks(
scm insights remote-networks --list --export json --output networks.json

"""
show_context_info()

# Note: scm_client automatically uses mock mode when no credentials are available

if network_id:
Expand Down Expand Up @@ -501,8 +477,6 @@ def show_service_connections(
scm insights service-connections --list --metrics --export csv --output connections.csv

"""
show_context_info()

# Note: scm_client automatically uses mock mode when no credentials are available

if connection_id:
Expand Down Expand Up @@ -599,8 +573,6 @@ def show_tunnels(
scm insights tunnels --list --stats --export json --output tunnels.json

"""
show_context_info()

# Note: scm_client automatically uses mock mode when no credentials are available

if tunnel_id:
Expand Down
39 changes: 1 addition & 38 deletions src/scm_cli/commands/mobile_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
from pydantic import ValidationError

from ..utils import validate_location_params
from ..utils.config import load_from_yaml, settings
from ..utils.context import get_current_context
from ..utils.config import load_from_yaml
from ..utils.decorators import handle_command_errors
from ..utils.output import OUTPUT_OPTION, OutputFormat, emit, error, info, success
from ..utils.sdk_client import scm_client
Expand All @@ -35,20 +34,6 @@
# =============================================================================================================================================================================================


def show_context_info() -> None:
"""Display current context information if log level is INFO."""
log_level = settings.get("log_level", "INFO").upper()
if log_level == "INFO":
current_context = get_current_context()
if current_context:
typer.echo(f"[INFO] Using authentication context: {current_context}", err=True)
else:
typer.echo(
"[INFO] No context set, using environment variables or default settings",
err=True,
)


def get_default_backup_filename(object_type: str, location_type: str, location_value: str) -> str:
"""Generate default backup filename based on object type and location."""
safe_location = location_value.lower().replace("/", "-").replace(" ", "-")
Expand Down Expand Up @@ -177,8 +162,6 @@ def show_agent_version(
scm show mobile-agent agent-version --folder "Mobile Users" --name "5.2.0"

"""
show_context_info()

if name:
# Get a specific agent version by name
version = scm_client.get_agent_version(folder=folder, name=name)
Expand Down Expand Up @@ -440,8 +423,6 @@ def show_auth_setting(
scm show mobile-agent auth-setting --folder "Mobile Users" --name "saml-auth"

"""
show_context_info()

if name:
# Get a specific auth setting by name
setting = scm_client.get_auth_setting(folder=folder, name=name)
Expand Down Expand Up @@ -710,8 +691,6 @@ def show_forwarding_profile(
scm show mobile-agent forwarding-profile --id "123e4567-e89b-12d3-a456-426655440000"

"""
show_context_info()

if profile_id or name:
profile = scm_client.get_forwarding_profile(folder=folder, name=name, profile_id=profile_id)
emit(profile, output, title=f"Forwarding Profile: {profile.get('name', 'N/A')}")
Expand Down Expand Up @@ -949,8 +928,6 @@ def show_forwarding_profile_destination(
scm show mobile-agent forwarding-profile-destination --id "123e4567-e89b-12d3-a456-426655440000"

"""
show_context_info()

if destination_id or name:
destination = scm_client.get_forwarding_profile_destination(folder=folder, name=name, destination_id=destination_id)
emit(destination, output, title=f"Forwarding Profile Destination: {destination.get('name', 'N/A')}")
Expand Down Expand Up @@ -1228,8 +1205,6 @@ def show_agent_profile(
scm show mobile-agent agent-profile --folder "Mobile Users" --name "corp-app-settings"

"""
show_context_info()

if name:
profile = scm_client.get_agent_profile(folder=folder, name=name)
emit(profile, output, title=f"Agent Profile: {profile.get('name', 'N/A')}")
Expand Down Expand Up @@ -1476,8 +1451,6 @@ def show_tunnel_profile(
scm show mobile-agent tunnel-profile --folder "Mobile Users" --name "corp-tunnel"

"""
show_context_info()

if name:
profile = scm_client.get_tunnel_profile(folder=folder, name=name)
emit(profile, output, title=f"Tunnel Profile: {profile.get('name', 'N/A')}")
Expand Down Expand Up @@ -1592,8 +1565,6 @@ def show_infrastructure_setting(
scm show mobile-agent infrastructure-setting --name "gp-infra"

"""
show_context_info()

setting = scm_client.get_infrastructure_setting(folder=folder, name=name)
emit(setting, output, title=f"Infrastructure Setting: {setting.get('name', 'N/A')}")
return setting
Expand Down Expand Up @@ -1747,8 +1718,6 @@ def show_global_setting(
scm show mobile-agent global-setting

"""
show_context_info()

setting = scm_client.get_global_settings()

if not setting:
Expand Down Expand Up @@ -2022,8 +1991,6 @@ def show_forwarding_profile_source_application(
scm show mobile-agent forwarding-profile-source-application --folder "Mobile Users" --name "office-apps"

"""
show_context_info()

if name:
# Get a specific source application by name
app = scm_client.get_forwarding_profile_source_application(folder=folder, name=name)
Expand Down Expand Up @@ -2299,8 +2266,6 @@ def show_forwarding_profile_user_location(
scm show mobile-agent forwarding-profile-user-location --folder "Mobile Users" --name "branch-network"

"""
show_context_info()

if name:
# Get a specific user location by name
location = scm_client.get_forwarding_profile_user_location(folder=folder, name=name)
Expand Down Expand Up @@ -2623,8 +2588,6 @@ def show_forwarding_profile_regional_and_custom_proxy(
scm show mobile-agent forwarding-profile-regional-and-custom-proxy --folder "Mobile Users" --name "emea-proxy"

"""
show_context_info()

if name:
# Get a specific regional and custom proxy by name
proxy = scm_client.get_forwarding_profile_regional_and_custom_proxy(folder=folder, name=name)
Expand Down
24 changes: 0 additions & 24 deletions src/scm_cli/commands/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@

# Removed unused import: from the `..utils.config` import load_from_yaml
from ..utils import parse_comma_separated_list, validate_location_params
from ..utils.config import settings
from ..utils.context import get_current_context
from ..utils.decorators import handle_command_errors
from ..utils.output import OUTPUT_OPTION, OutputFormat, emit, error, info, redact, success, warning
from ..utils.sdk_client import scm_client
Expand Down Expand Up @@ -44,20 +42,6 @@
# =============================================================================================================================================================================================


def show_context_info() -> None:
"""Display current context information if log level is INFO."""
log_level = settings.get("log_level", "INFO").upper()
if log_level == "INFO":
current_context = get_current_context()
if current_context:
typer.echo(f"[INFO] Using authentication context: {current_context}", err=True)
else:
typer.echo(
"[INFO] No context set, using environment variables or default settings",
err=True,
)


# =============================================================================================================================================================================================
# TYPER APP CONFIGURATION
# =============================================================================================================================================================================================
Expand Down Expand Up @@ -1021,8 +1005,6 @@ def show_address(

"""
# Show context info if log level is INFO
show_context_info()

if name:
# Get a specific address by name
address = scm_client.get_address(folder=folder, name=name)
Expand Down Expand Up @@ -4037,8 +4019,6 @@ def delete_quarantined_device(
scm delete object quarantined-device 01abcdef-2345-6789-abcd-ef0123456789

"""
show_context_info()

if not force:
confirm = typer.confirm(f"Delete quarantined device '{host_id}'?")
if not confirm:
Expand Down Expand Up @@ -4214,8 +4194,6 @@ def set_quarantined_device(
serial_number: str = typer.Option(None, "--serial-number", help="Serial number of the device"),
) -> None:
"""Create a quarantined device entry."""
show_context_info()

# Build device data
device_data: dict[str, Any] = {
"host_id": host_id,
Expand Down Expand Up @@ -4254,8 +4232,6 @@ def show_quarantined_device(
scm show object quarantined-device --host-id abc123

"""
show_context_info()

devices = scm_client.list_quarantined_devices(
host_id=host_id,
serial_number=serial_number,
Expand Down
Loading
Loading