mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
* feat(otel): attribute Prisma database spans to PostgreSQL instead of localhost Prisma reaches PostgreSQL through a query engine on loopback, so transport instrumentation attributes database waits to localhost and operators cannot tell the work is PostgreSQL or correlate it with the database's own metrics. Datastore service spans now carry db.system.name, db.system, db.operation.name and, for PostgreSQL, server.address, server.port and db.namespace derived from DATABASE_URL, and are emitted as CLIENT spans. Only host, port, database and schema are read, so no credential reaches an exporter. Endpoint attributes are omitted when a read replica is configured, because routing is decided per Prisma call underneath the span. * fix(otel): reject a mis-split DSN authority and name socket-only databases An unencoded '/' in the password truncates the URL authority, so urlparse reports the username as the host and the password tail as the database, which put credential material in db.namespace. Postgres drivers reject that DSN outright, so the only safe reading is no endpoint at all. A hostless 'postgresql:///litellm' is a valid local-socket DSN that Prisma accepts, and it now yields db.namespace with no server address rather than nothing. The default schema is matched case-insensitively, since an unquoted PostgreSQL identifier folds and one deployment must yield one namespace. * fix(otel): keep a non-default schema in db.namespace Prisma quotes the schema name, so a DSN with ?schema=PUBLIC provisions a second schema alongside public rather than reusing it. Observed on a live proxy: the PUBLIC schema came up with its own 70 tables next to public's 78, and a key created under one was not visible under the other. Case-folding the two into a single namespace therefore reported two different schemas as the same database, which is the misattribution this feature exists to remove. Match the default literally. * fix(otel): reject any DSN whose userinfo fell outside the authority An unencoded '#' or '?' in the password sends the tail to the fragment or query, leaving the path empty, so the marker check on the database segment never fired and urlparse's hostname (the database username) was exported as server.address. The stranded userinfo '@' is the general tell for every mis-split, so guard on that instead of enumerating the characters that cause it. * fix(otel): allow an at-sign inside a well-formed DSN query The previous guard rejected any DSN whose userinfo at-sign fell outside the netloc, which also caught libpq parameters that legitimately carry one, so ?application_name=svc@prod and ?user=admin@company.com lost their endpoint attributes. Discriminate instead: a PostgreSQL DSN never has a fragment, its database name cannot hold an unencoded at-sign or slash, and an at-sign in the query is only suspicious when the query did not parse as parameters. * fix(otel): resolve the database endpoint per span instead of once per process The endpoint was cached for the process lifetime on the premise that DATABASE_URL is deployment-static. It is not. The RDS IAM refresh rebuilds the URL from DATABASE_HOST/PORT/NAME/SCHEMA on every rotation, the reconnect path re-reads DATABASE_URL, and the DB-backed environment_variables config overlay sets arbitrary keys post-startup with no blocklist covering DATABASE_*. A process that had genuinely failed over kept exporting the old server. Read the environment per span, which is also what Prisma connects with, so the span can no longer name a different server than the one serving the query; get_secret_str consulted a secret manager first and could diverge from it. Only the parse is memoized, keyed on the URL. * fix(otel): reject a question-mark mis-split whose tail parses as parameters A '?' in a password strands the rest of the authority in the query, and that tail can still parse as key=value, so testing only for an unparseable query let the login through as server.address. One spelling hijacked the host= parameter and put the password suffix there directly. A legitimate at-sign in a query always follows a database path, and a '?'-mis-split never leaves one, so require both. * refactor(otel): drop the DSN parse cache that retained rotated credentials The cache was keyed on the full DATABASE_URL, so up to eight complete DSNs, each carrying a password or a retired IAM token, stayed referenced for the process lifetime and outlived the rotation that replaced them. Nothing reached a span, but a heap dump or crash report would have surfaced them. Parsing costs about four microseconds against a span emission that costs orders of magnitude more, so the cache bought nothing worth that. * fix(otel): avoid a set construction the tightened LIT002 budget rejects * fix(otel): refuse an ambiguous DSN authority instead of guessing at it A password holding both an unencoded slash and a query-like tail defeated all three shape checks: the slash left a clean path carrying the password remainder, the query still parsed as parameters, and no fragment survived. The login went out as server.address, the password's leading digits as server.port and the rest as db.namespace. A DSN whose at-sign sits in a query parameter is indistinguishable from that mis-split by any property of the parse; both leave no userinfo, a host, a port and a path. Guessing wrong publishes a credential fragment, so the ambiguity now resolves to refusing the endpoint. Such a DSN loses server.address and db.namespace and keeps the rest of the span; percent-encoding the at-sign restores them. Also honour port= alongside host=, which libpq pairs and this read ignored. * docs(otel): fix a spliced sentence and a stale cache claim in db_endpoint Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|---|---|---|
| .. | ||
| test_db_endpoint.py | ||
| test_otel_v2_baggage.py | ||
| test_otel_v2_components.py | ||
| test_otel_v2_config_baggage_parenting_guardrails.py | ||
| test_otel_v2_dynamic.py | ||
| test_otel_v2_emitter.py | ||
| test_otel_v2_logger.py | ||
| test_otel_v2_metrics.py | ||
| test_otel_v2_mount.py | ||
| test_otel_v2_multibackend.py | ||
| test_otel_v2_presets.py | ||
| test_otel_v2_sources_of_truth.py | ||
| test_otel_v2_vendor_mappers.py | ||
| test_runtime.py | ||