Naming rules and limits
Names are validated identically on the client and the server, so a name the SDK accepts is one ingestion accepts. Validation is blacklist-based — Metrana rejects only what’s genuinely unsafe and allows everything else, including arbitrary Unicode (Chinese, Japanese, Cyrillic, emoji, …) and most ASCII punctuation.
There are three tiers, each building on the one before it.
Tier 1 — names (metric names, scales, attribute paths, label keys)
Section titled “Tier 1 — names (metric names, scales, attribute paths, label keys)”A plain name must:
- be non-empty;
- not start with
:(e.g.:metric✗, butns:metric✓ anda:b:c✓); - contain no whitespace or control characters (no spaces, tabs, newlines, NUL, …);
- contain none of the query-language reserved characters:
( ) [ ] , = < > ! ~ " |
Everything else is allowed — including / (used for hierarchy), ., -, _, : (not leading), and
punctuation like # $ + * ; { } @ ? &, plus any Unicode.
metrana.log("train/loss", v) # ✓ slash for hierarchymetrana.log("model.layers.0", v) # ✓metrana.log("my-metric_v2", v) # ✓metrana.log("ns:metric", v) # ✓ colon allowed (just not leading)metrana.log("loss_损失", v) # ✓ unicodemetrana.log("a=b", v) # ✗ '=' is reservedmetrana.log("a b", v) # ✗ whitespaceUse /-delimited prefixes to group related series (train/loss, eval/loss are distinct series);
labels and the evaluation shorthand are the
other way to split a name into distinct series.
Tier 2 — URL-segment names (run, project, environment ids)
Section titled “Tier 2 — URL-segment names (run, project, environment ids)”Run names, project names, and environment ids are rendered into portal URL paths and used as by-name lookup keys, so they add two restrictions on top of Tier 1:
- no URL-structural characters:
/ \ ? # %(these break or get rewritten in a URL path segment — e.g. the browser rewrites\→/, and%starts an escape); - must not be a reserved portal route segment (case-insensitive) — names like
login,dashboard,settings,runs,searchwould collide with a portal route and are rejected.
metrana.log_rl_environment_step("reward", r, rl_step=s, env_id="worker-3", episode=e) # ✓metrana.log_rl_environment_step("reward", r, rl_step=s, env_id="env/0", episode=e) # ✗ '/' not allowed in an env idSo an environment id like env0 or cartpole-7 is fine; env/0, a?b, or dashboard are not. (Note /
is fine in a metric name, which is not a URL segment — only the URL-segment names forbid it.)
Tier 3 — labels
Section titled “Tier 3 — labels”Label keys and values are stored in a k=v&k=v
encoding, so they reserve a couple more characters:
- Label keys follow the Tier-1 name rules plus reserve
&and=. (Non-empty, no leading:, no whitespace/control, none of()[],=<>!~"|&.) - Label values are more permissive: only control characters,
=, and&are rejected. Spaces, parens, colons, and Unicode are all fine, and an empty value is allowed.
metrana.log("r", v, labels={"policy": "greedy"}) # ✓metrana.log("r", v, labels={"host": "node-0:gpu1"}) # ✓ colon fine in a valuemetrana.log("r", v, labels={"a=b": "x"}) # ✗ '=' in a keymetrana.log("r", v, labels={"split": "a&b"}) # ✗ '&' in a valueAttribute paths
Section titled “Attribute paths”Attribute paths are /-delimited Tier-1 names. Nested
dicts/lists passed to log_config / config= / log_attributes flatten into these paths automatically (e.g.
{"optimizer": {"lr": 3e-4}} → config/optimizer/lr).
Two reservations apply by attribute kind:
- Run attributes: the
sys/sys/*namespace is reserved for system-managed fields — writing to it is rejected (useset_description/set_tagsfor the supported ones). - Environment attributes: there is no
sys/*reservation, but the three RL-environment query-language special fields —environment_id,latest_episode,latest_rl_step(and their camelCase aliases) — are reserved so a config attribute can’t shadow them in a filter / order-by.
Size and count limits
Section titled “Size and count limits”Like the name rules, every limit is enforced identically on the client and the server, and all of them sit far above realistic usage — they exist so no input, however malformed, can destabilize the ingestion backend. Lengths are in bytes (UTF-8), not characters.
Identifier lengths
Section titled “Identifier lengths”| Identifier | Maximum |
|---|---|
| Metric name | 256 bytes |
| Scale | 64 bytes |
| Environment id | 128 bytes |
| Label key / label value | 64 bytes each |
| Attribute path (run and environment) | 512 bytes |
| Run name, project name | 512 bytes |
| Orchestration id | 256 bytes (opaque token — skips the name rules, only control characters are rejected; see Distributed logging) |
Values and counts
Section titled “Values and counts”| Input | Maximum |
|---|---|
| Labels per series | 8 |
| String attribute value | 16 KiB |
| String-set attribute (e.g. tags), total across values | 32 KiB per update — a delta update is capped on its own added + removed contribution |
| Histogram bins per point | 512 (see Histograms) |
| Scatter points per step | 32,768 coordinate pairs (see Scatter plots) |
| Steps and episodes | i64::MAX (9_223_372_036_854_775_807) |
Points per call — no limit (the SDK chunks)
Section titled “Points per call — no limit (the SDK chunks)”There is no cap on how many points one log* call may carry. The wire format bounds a single batch at
10,000 points, a single histogram update at 128 points, and a single scatter update at 128 point
sets (32,768 coordinate pairs total), but the SDK splits an oversized call into
capped batches transparently — steps, ordering, and delivery semantics are identical to logging the same data
in smaller calls. Large environment-attribute batches are likewise split automatically. Only clients speaking
the REST/gRPC API directly need to respect the wire caps themselves.
Anything over a limit is rejected synchronously with a MetranaValidationError from the log* call, same as
an invalid name (see Errors).