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_dirvalues - 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_diris one of the inputs to the filename, changinglog_dirfor the sameaccountidandbase_urlproduces 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.
- 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. - A substring match against a shorter set of stems:
password,passwd,secret,token,apikey,credential,privatekey,passphrase. A compound name such asproxypassword,bindPassword, orsshPassphraseis 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
usecredentialgets 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:
| Path | Permissions |
|---|---|
log_dir (only when the SDK creates it) | 0700, owner read/write/execute only |
Each .jsonl file, including rotated backups | 0600, 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:
| Setting | Behavior |
|---|---|
| Rotation frequency | Weekly |
| Time reference | UTC |
| Retained backups | Last 8 weeks |
| Format | JSONL |
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
| Field | Description |
|---|---|
| timestamp | Request execution time |
| method | RPC method name invoked |
| url | Target API endpoint |
| payload | Full RPC request payload sent (secrets redacted) |
| status | HTTP response status code |
| response | API response body |
| success | Whether request succeeded |
| attempt | Retry attempt number |
| retry_after | Seconds parsed from Retry-After, if present, only on retryable-status failures (429/500/502/503/504) |
| time_ms | Request execution duration |
| accountid | Account context used |
REST Requests
| Field | Description |
|---|---|
| timestamp | Request execution time |
| method | HTTP method used (GET, POST, etc.) |
| url | Target API endpoint |
| params | Query parameters sent |
| json_body | Request body sent (secrets redacted) |
| status | HTTP response status code |
| response | API response body |
| success | Whether request succeeded |
| attempt | Retry attempt number |
| retry_after | Seconds parsed from Retry-After, if present, only on retryable-status failures (429/500/502/503/504) |
| time_ms | Request execution duration |
| accountid | Account context used |
Note:
status,time_ms, andretry_afterare 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_afterto 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.
Updated 5 days ago
