Logging and Secret Redaction¶
stonepy is deliberately quiet. It does not log your HTTP requests, responses, headers, or payloads. It writes warnings for failed proactive session refresh. Its own object representations mask values keyed as app key, password, session, authorization, or proxy. Usernames are not masked, and logging from the external HTTP stack is not sanitized by stonepy.
This guide covers two distinct mechanisms:
- The stdlib
loggingusage (warnings only). - Secret redaction at the
repr()level, which protectsClientConfigand the internalRequestobject regardless of how you log them.
What stonepy logs¶
stonepy uses Python's standard logging module.
stonepy.pipeline emits one warning when a proactive session refresh raises a stonepy
error:
This refresh behavior is fail-soft. The warning contains no credentials or token, and the
request proceeds with the existing session so reactive 401 recovery remains available.
Note
There is no request/response logging built into stonepy. The client does not log URLs, auth headers, order payloads, or rate-limit activity. If you need wire-level tracing you must enable it on the underlying HTTP stack (see the caution at the end of this guide).
Enabling logging¶
Because stonepy logs through the stdlib, you control it with the normal logging
configuration. The pipeline logger lives under the stonepy hierarchy, so configuring
stonepy captures its warnings:
import logging
# Simplest: configure the root logger and let propagation carry stonepy records.
logging.basicConfig(level=logging.WARNING)
# Or target the stonepy hierarchy specifically.
stonepy_logger = logging.getLogger("stonepy")
stonepy_logger.setLevel(logging.WARNING)
A failed proactive refresh then produces output similar to:
Tip
Set the level on "stonepy" (the parent) rather than "stonepy.pipeline" if
you want a single switch that will also pick up any future stonepy loggers.
How secrets are redacted¶
Redaction in stonepy happens when an object is converted to its string
representation, not inside a logging handler. Values under the recognized keys
are therefore hidden in a deliberate log.info(config), an f-string, a print(),
or an exception that captures the object. Other values, including usernames, are
not masked. The helpers live in stonepy._core.logging.
The core primitive replaces any non-empty value with a fixed mask (it does not truncate or partially reveal the value):
The default secret key set¶
Mapping, header, URL-query, and generated-model redaction share SECRET_KEYS, defined in
stonepy/_core/logging.py:
SECRET_KEYS = frozenset(
{
"api-key",
"app-key",
"app_key",
"appkey",
"authorization",
"cookie",
"newpassword",
"password",
"proxy",
"proxy-authorization",
"session",
"set-cookie",
"token",
"twofatoken",
"x-api-key",
}
)
Matching is case-insensitive: every candidate key is lowercased before it is
compared against this set, so Password, AppKey, Session, and Authorization
all match. Any matching value is rendered as '***'.
The secret query-string key set¶
URL query parameters use the same set in stonepy/_core/transport.py:
When the internal Request object is repr'd, _redact_url_query() parses the
URL's query string and masks any parameter whose (lowercased) name is in that
set, leaving the rest intact.
ClientConfig repr redaction¶
ClientConfig uses the standard dataclass repr. Its app_key, password, and proxy
fields have repr=False, so their names and values are omitted. safe_repr redacts mapping
keys and falls back to ordinary repr for other objects, including dataclasses. Given:
from stonepy import ClientConfig
config = ClientConfig(
base_url="https://ciapi.cityindex.com/TradingAPI",
app_key="SECRET-KEY",
username="alice",
password="hunter2",
proxy="http://corp-proxy:9999",
)
print(repr(config))
you get (truncated for readability) - note app_key, password, and proxy
are omitted while non-secret fields such as username and base_url are shown
verbatim:
ClientConfig(base_url='https://ciapi.cityindex.com/TradingAPI', username='alice', ..., user_agent='stonepy/<version>', ...)
Note
username is not in the secret set, so it is printed in full. Only
app_key, password, and proxy among the credential-bearing
ClientConfig fields are omitted.
Request repr redaction¶
The internal Request dataclass (used to build every HTTP call) defines a
custom __repr__ that redacts on three fronts: the URL query string, the
headers mapping, and the params mapping. The body content is never shown - only
its length:
def __repr__(self) -> str:
content = "None" if self.content is None else f"<redacted {len(self.content)} bytes>"
return (
f"{type(self).__qualname__}(method={self.method!r}, "
f"url={_redact_url_query(self.url)!r}, "
f"headers={safe_repr(self.headers)}, params={safe_repr(self.params)}, "
f"content={content})"
)
For mappings, safe_repr returns the repr of a dict whose secret-keyed values are replaced
with '***'. A representative repr looks like:
Request(method='GET', url='https://ciapi.cityindex.com/TradingAPI/order/123?Session=***&UserName=alice&Password=***', headers={'Authorization': '***', 'Session': '***', 'UserName': 'alice'}, params={'Session': '***', 'MarketId': '99'}, content=<redacted 7 bytes>)
Here Session, Password, and Authorization are masked in both the URL and
the headers/params, while UserName and MarketId pass through, and the request
body is reduced to a byte count.
Custom secret keys¶
safe_repr accepts an optional secret_keys argument that is merged (after
lowercasing) into the defaults:
So calling safe_repr(some_mapping, secret_keys={"custom_secret"}) masks custom_secret in
addition to the 15 default keys.
Warning
This extra-keys capability is an internal helper in stonepy._core.logging;
it is not exported from the public stonepy API. Request.__repr__ calls
safe_repr(...) on headers and params without custom keys. ClientConfig relies on
dataclass field omission. There is no ClientConfig setting to register additional
secret key names.
Caution: the HTTP layer is not redacted¶
stonepy's redaction only applies to its own ClientConfig and Request
reprs. The underlying transport is httpx, which has its own loggers
(for example httpx and httpcore). httpx logs request lines, including URLs, at INFO;
DEBUG logging also exposes connection details. stonepy does not redact these logs,
and those URLs can contain Session,
AppKey, and similar query parameters in clear text.