A slow request can come from a cache miss, a waiting refresh, or a failing cache backend. Facade telemetry distinguishes operation latency, lookup outcomes, and error classes without recording entry names or values. The cache publishes no events; use returned errors for application decisions and telemetry for observation.
Connecting Telemetry
With an existing cache driver and a configured *telemetry.Telemetry named
signals, select the handle when constructing the facade. Import cache from
arkoren.dev/core/service/cache; telemetry handles come from
arkoren.dev/core/service/telemetry:
store := cache.New(driver, func(store *cache.Builder) {
store.Telemetry(signals)
store.Name("catalog")
})
Use a short fixed name such as catalog, not a tenant ID, token, or per-request
value. Name controls the arkoren.cache.store label only: it does not prefix
keys or isolate storage. An empty name reports default.
Without an explicit handle, each operation resolves the
process default. It is a
no-op until configured; installing the default after cache construction still
affects later operations. Telemetry(nil) also follows that default. Provider
connections and lifecycle remain in Cache Drivers.
Reading Signals
Each facade operation records a client span named cache {operation}, one
arkoren.cache.operations counter increment, and one
arkoren.cache.operation.duration histogram observation in seconds. Operation
and store labels identify the call, for example get on catalog.
The span’s arkoren.cache.hit attribute describes a known lookup outcome:
| Operation | Hit Meaning |
|---|---|
get, pull, ttl |
Whether an unexpired entry was found. A subsequent decoding error is still a hit. |
has |
The returned existence result. |
remember |
Whether the result came from cache rather than running the loader, including a hit after waiting for another caller. |
For these lookups, true means found and false means missing or recomputed.
A read that fails before determining lookup outcome has no span hit attribute.
Writes and lock acquisitions have no lookup hit state either. The operations
counter uses string labels "true", "false", or "" for unknown/inapplicable
hit state; the duration histogram has no hit label.
Compute a hit ratio for one operation and store as its "true" count divided by
the sum of its "true" and "false" counts. Exclude unknown states and writes;
combining all operations in the denominator understates the lookup hit ratio.
A Remember hit means reuse, not necessarily a request that never waited.
Errors wrapping cache.ErrNotFound are misses, not telemetry failures: they do
not set error status or error.type. Other failures set the span error status
and error.type using an error class, never the error text. For example, invalid
TTL declarations report cache.ErrInvalidTTL. A rejected guarded write is
false, nil, not a read miss or an error; unsuccessful Add and contended
TryLock likewise are ordinary non-error results.
For the complete attribute and metric catalog, see instrumented cache components.
Operation Boundaries
Remember produces one span around the entire operation, not child facade spans
for each internal read, acquisition, or write. Its duration can include waiting,
loading, storage, and the separate cleanup budget described in
Remembering Values.
Lock and TryLock each emit one acquisition span (cache lock and
cache try_lock). Lock includes time spent polling for ownership. Lease.Unlock
and Lease.Extend emit no facade operation spans; report cleanup or renewal
failures explicitly if your application needs that visibility. Losing ownership
returns cache.ErrLockLost, not a published event.
Driver and DriverAs are untraced accessors. Calls through a raw driver or
native Redis client do not emit facade telemetry. Native instrumentation, if
configured independently, has its own measurement and privacy policy.
One operation span is not a backend command count or proof of exactly-once effects. A facade operation can make several driver calls, and native clients can retry transport failures. A lost response can leave a mutation’s outcome unknown even though the facade span reports an error.
Privacy Boundaries
Facade spans and metrics never record cache keys, lock names, values, or error messages. Error classes are used because backend error text can contain private entry names. Store names remain caller-supplied telemetry labels, so keep them fixed and non-sensitive.
This protection does not sanitize your application logs or raw/native client
instrumentation. Owned keys and namespaces redact formatting, but explicit
Name() results, raw arguments, and Match or Entry descriptors expose their
contents. Follow identity inspection rules,
and do not attach sensitive names to your own spans to compensate for their
absence from facade telemetry.
Testing Signals
To test instrumentation without exporting it, use the fake from
arkoren.dev/core/service/telemetry/telemetrytest. Inside an existing test with
t *testing.T, configure an isolated handle. Import memorycache from
arkoren.dev/core/service/cache/memorycache:
fake := telemetrytest.New()
store := cache.New(memorycache.New(), func(store *cache.Builder) {
store.Telemetry(fake.Telemetry(t))
})
With standard-library errors, confirm absence as part of the test before
asserting its signal:
_, err := store.Get(t.Context(), cache.NewKey[string]("catalog:missing"))
if !errors.Is(err, cache.ErrNotFound) {
t.Fatalf("Get error = %v, want ErrNotFound", err)
}
The fresh memory driver guarantees this read misses. Assert one facade span
with a false hit attribute, using telemetry from
arkoren.dev/core/service/telemetry:
fake.AssertSpanCount(t, 1)
fake.AssertSpan(t, "cache get", telemetry.Bool("arkoren.cache.hit", false))
fake.AssertCounter(t, "arkoren.cache.operations", 1,
telemetry.String("arkoren.cache.operation", "get"),
telemetry.String("arkoren.cache.hit", "false"))
These assertions verify the facade signal, not Redis connectivity, persistence, or exactly-once execution. The fake also supports histogram and snapshot inspection; see telemetry testing.
For every instrumentation setting, see the cache API reference.