Logging

The Saner Python SDK includes built-in structured logging designed for production automation, troubleshooting, and observability. When enabled, the SDK records request execution details in a machine-readable format to help developers debug integrations and monitor API activity reliably.

Logging is disabled by default and can be enabled during client initialization.


Enabling Logging

To enable SDK logging:

from saner import SanerClient

client = SanerClient(
    api_key="YOUR_API_KEY",
    accountid="YOUR_ACCOUNT_ID",
    enable_logging=True
)

Once enabled, logs are automatically written for every API request executed through the SDK.


Where Logs Are Stored

By default, logs are stored inside a directory named:

logs/

Each client instance generates a dedicated log file based on:

  • account ID
  • API endpoint (base_url)
  • log directory (log_dir)

Example filename:

logs/secpod-saner-sdk__<Account_ID>__a1b2c3d4.jsonl

This naming strategy ensures:

  • separation between environments
  • separation between accounts
  • separation between log directories, if you point different clients at different log_dir values
  • safe execution of multiple client instances within the same script
  • predictable log traceability

You can customize the storage directory using:

client = SanerClient(
    api_key="YOUR_API_KEY",
    accountid="Default",
    enable_logging=True,
    log_dir="saner_logs"
)

Note: Because log_dir is one of the inputs to the filename, changing log_dir for the same accountid and base_url produces a different log filename, not the same file in a new location.


Log File Format

Logs are stored in JSON Lines format (.jsonl), where each line represents one API request execution record.

Example (RPC Successful) :

{
  "timestamp": "2026-04-01 15:09:01",
  "method": "getOrganization",
  "url": "https://eu.saner.secpod.com/AncorWebService/perform?accountid=Default",
  "payload": {...},
  "status": 200,
  "response": {...},
  "success": true,
  "attempt": 1,
  "time_ms": 1563.0,
  "accountid": "Default"
}

Example (REST Successful) :

{
    "timestamp": "2026-04-03 14:28:14",
    "method": "POST",
    "params": null,
    "json_body": {...},
    "url": "https://eu.saner.secpod.com/CHScanner/getCHScoreSummaryForGroup",
    "status": 200,
    "response": {...},
    "success": true,
    "attempt": 1,
    "time_ms": 837.37,
    "accountid": "Default"
}

Example (RPC Failure, retryable HTTP status) :

{
    "timestamp": "2026-04-03 14:27:03",
    "method": "getDeviceVulnerabilities",
    "url": "https://eu.saner.secpod.com/AncorWebService/perform?accountid=Default",
    "status": 503,
    "error": "Retryable HTTP error",
    "success": false,
    "attempt": 3,
    "response": "...",
    "retry_after": null,
    "time_ms": 105.63,
    "accountid": "Default"
}

Example (REST Failure, connection-level) :

{
    "timestamp": "2026-04-03 14:38:12",
    "method": "POST",
    "params": null,
    "json_body": {...},
    "url": "https://eu.saner.secpod.com/CHScanner/getCHScoreSummaryForGroup",
    "error": "Timeout",
    "success": false,
    "attempt": 1,
    "details": "...",
    "accountid": "Default"
}

This structured format allows easy parsing by:

  • debugging tools
  • log aggregators
  • SIEM platforms
  • automation scripts

Sensitive Data Handling

Before any record is written to disk, the SDK automatically redacts fields that look like credentials and truncates values that are unreasonably large. This applies to every log entry: RPC and REST, success and failure.

What gets redacted: matching is two-tier, so a new secret parameter doesn't need to be named exactly to be caught.

  1. An exact match (case-insensitively, ignoring separators) against a known list: password, newpassword, oldpassword, secret, client_id / clientid, client_secret, token, access_token, refresh_token, api_key, authorization, credential(s), private_key, and a few related names.
  2. A substring match against a shorter set of stems: password, passwd, secret, token, apikey, credential, privatekey, passphrase. A compound name such as proxypassword, bindPassword, or sshPassphrase is redacted this way even though it isn't in the exact list above.

The RPC parameter shape ({"key": "password", "value": "..."}) is recognized as well, so the sibling value is redacted even though the secret isn't a plain dict key.

Note: The stem match is intentionally a little aggressive: a flag like usecredential gets redacted too, even though it only ever holds "true"/"false". Over-redacting a non-secret is the accepted tradeoff for guaranteeing a real secret can't slip through under a compound name.

Before and after:

// what your code sent
{"key": "password", "value": "Sup3rSecret!"}

// what actually reaches the log file
{"key": "password", "value": "***REDACTED***"}

What gets truncated: any string value longer than 2048 characters (for example, a base64-encoded file upload) is cut short and annotated:

"<original 2048 characters>...<truncated 8213 chars>"

This keeps log files safe to share for debugging and keeps them from growing unbounded when large payloads are involved.

Note: Redaction only applies to parameters you sent: password fields, tokens, and similar. Response bodies are logged as returned by the server and may still contain other sensitive business data (asset inventories, user details, etc.). Treat the logs/ directory with the same access controls you'd apply to any file containing API response data.


Log File Permissions

Because these logs can contain full request/response bodies, including asset inventories and CVE exposure data in a CVEM product, the SDK locks the log directory and files down to the owner wherever the operating system supports POSIX file permissions:

PathPermissions
log_dir (only when the SDK creates it)0700, owner read/write/execute only
Each .jsonl file, including rotated backups0600, owner read/write only

If log_dir already exists when the client starts, its existing permissions are left as-is. The SDK only sets the mode on a directory it creates itself. On a filesystem without POSIX permissions (some network shares, some Windows configurations), the permission change is skipped silently rather than blocking logging.


Log Rotation Behavior

The SDK automatically rotates logs weekly and retains previous log history.

Rotation settings:

SettingBehavior
Rotation frequencyWeekly
Time referenceUTC
Retained backupsLast 8 weeks
FormatJSONL

This prevents uncontrolled log growth in long-running automation environments.


What Gets Logged

Each API request generates a structured execution record. The exact fields vary depending on whether the request is executed through the RPC layer or the REST layer, and whether the failure is connection-level or a retryable HTTP status.

RPC Requests

FieldDescription
timestampRequest execution time
methodRPC method name invoked
urlTarget API endpoint
payloadFull RPC request payload sent (secrets redacted)
statusHTTP response status code
responseAPI response body
successWhether request succeeded
attemptRetry attempt number
retry_afterSeconds parsed from Retry-After, if present, only on retryable-status failures (429/500/502/503/504)
time_msRequest execution duration
accountidAccount context used

REST Requests

FieldDescription
timestampRequest execution time
methodHTTP method used (GET, POST, etc.)
urlTarget API endpoint
paramsQuery parameters sent
json_bodyRequest body sent (secrets redacted)
statusHTTP response status code
responseAPI response body
successWhether request succeeded
attemptRetry attempt number
retry_afterSeconds parsed from Retry-After, if present, only on retryable-status failures (429/500/502/503/504)
time_msRequest execution duration
accountidAccount context used

Note: status, time_ms, and retry_after are only present when a response was received from the server.
Network-level failures such as timeouts and connection errors will not include these fields.


Debugging Benefits

Structured logging helps developers quickly identify integration issues without modifying application code.

Logging is especially useful for:

Troubleshooting API Failures

Inspect request payloads and responses directly:

payload  → what was sent
response → what was returned
status   → whether request succeeded

Monitoring Retry Behavior

Track retry attempts automatically:

attempt: 1
attempt: 2
attempt: 3

Useful when diagnosing:

  • network instability
  • rate limiting: check retry_after to see what the server requested
  • intermittent platform errors

Performance Analysis

Measure request execution time:

time_ms: 1563.0

Helps detect:

  • slow endpoints
  • timeout tuning needs
  • infrastructure latency

Multi-Account Automation Visibility

Because logs include accountid + base_url + log_dir, they help distinguish activity when running scripts across multiple tenants or environments.

Example:

secpod-saner-sdk__ACCOUNT_1__1p2r69: ACCOUNT_1 + https://eu.saner.secpod.com 
secpod-saner-sdk__ACCOUNT_1__45fgh5: ACCOUNT_1 + https://in.saner.secpod.com 
secpod-saner-sdk__ACCOUNT_2__7g896d: ACCOUNT_2 + https://uk.saner.secpod.com 

Log Files When Mixing Client Types

Each client instance, regardless of whether it's a SanerClient, PlatformClient, or CvemClient, writes to its own log file, since the filename is derived from that instance's accountid, base_url, and log_dir. If a script uses one PlatformClient and one CvemClient against the same account to cover both products, enable_logging=True on both produces two separate .jsonl files rather than one combined log, even though they share the same accountid:

logs/secpod-saner-sdk__ACCOUNT_1__a1b2c3d4.jsonl   # from the PlatformClient instance
logs/secpod-saner-sdk__ACCOUNT_1__a1b2c3d4.jsonl   # from the CvemClient instance: same name, same file

Because the filename hash only depends on accountid/base_url/log_dir (not on which client class wrote the entry), two clients with identical values actually share one log file (interleaved by request, distinguishable by the method field), while a single SanerClient covering both namespaces also writes everything to that same one file. See the "Client Types" guide for when it's worth isolating platform and cvem traffic into separate log_dir values instead.


Recommended Usage in Production

Enable logging when:

  • developing new integrations
  • debugging automation workflows
  • running scheduled jobs
  • monitoring large batch executions
  • operating across multiple environments

Disable logging when:

  • running lightweight scripts
  • minimizing disk usage
  • executing high-frequency short-lived calls

Example: Logging Enabled Configuration

from saner import SanerClient

client = SanerClient(
    api_key="YOUR_API_KEY",
    accountid="Default",
    enable_logging=True,
    log_dir="logs"
)

Once enabled, every SDK request is automatically recorded for traceability and diagnostics, with credentials redacted and oversized values truncated before anything touches disk.


Did this page help you?