feat(spring): Cache Tracing (#5165)
* feat(spring): [Cache Tracing 1] Add SentryCacheWrapper and SentryCacheManagerWrapper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * collection: Cache Tracing * fix(cache): Fix span description, putIfAbsent, and Callable hit detection - Use cache key as span description instead of cache name, matching the spec and other SDKs (Python, JavaScript) - Skip instrumentation for putIfAbsent since we cannot know if a write actually occurred; override to bypass default get()+put() delegation - Wrap valueLoader Callable in get(key, Callable) to detect cache hit/miss instead of always reporting hit=true - Update tests to match new behavior Co-Authored-By: Claude <noreply@anthropic.com> * feat(core): [Cache Tracing 2] Add enableCacheTracing option Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(spring): [Cache Tracing 3] Add BeanPostProcessor and auto-configuration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * changelog * fix: Update changelog PR references Co-Authored-By: Claude <noreply@anthropic.com> * feat(samples): [Cache Tracing 4] Add cache tracing e2e sample Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ref(samples): Replace ConcurrentMapCacheManager with Caffeine Use Caffeine as the cache provider instead of a plain ConcurrentMapCacheManager. Spring Boot auto-configures CaffeineCacheManager when Caffeine is on the classpath, so the explicit CacheManager bean is no longer needed. Co-Authored-By: Claude <noreply@anthropic.com> * fix dependencies; move to toml * feat(jcache): Add SentryJCacheWrapper for JCache (JSR-107) cache tracing * changelog * fix(jcache): Make replace and getAndReplace passthrough (no span) Like putIfAbsent, these are conditional writes that may be no-ops. Emitting a cache.put span for them would be misleading. * fix(jcache): Check for NoOp span after startChild startChild can return a NoOp span (e.g. when span limit is reached). Skip instrumentation in that case to avoid unnecessary work. * fix(jcache): Use cache.flush for removeAll() without keys removeAll() with no args removes all entries, which is semantically equivalent to clear(). Use cache.flush instead of cache.remove. The keyed removeAll(Set) remains cache.remove. * feat(samples): Add JCache cache tracing demo to console sample Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * changelog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * changelog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(core): Use correct cache span op terminology in Javadoc Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * changelog * fix(spring7): Avoid double-wrapping caches in SentryCacheManagerWrapper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(samples): Add cache tracing to all Spring Boot 4 samples Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(test): Add SENTRY_ENABLE_CACHE_TRACING env var to system test runner Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(jcache): Add SentryJCacheWrapper ctor that uses ScopesAdapter Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(spring7): Add retrieve() overrides to SentryCacheWrapper Adds support for Spring 6.1+ async cache operations (CompletableFuture and Mono/Flux). Without these overrides, @Cacheable on reactive return types crashes with UnsupportedOperationException. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(spring-jakarta): Add cache tracing for Spring Boot 3 / Spring 6 Port cache tracing classes from sentry-spring-7 to sentry-spring-jakarta, covering Spring Boot 3 (Spring Framework 6.x) users. Includes SentryCacheWrapper, SentryCacheManagerWrapper, SentryCacheBeanPostProcessor, and auto-configuration in sentry-spring-boot-jakarta. The retrieve() overrides for CompletableFuture/reactive cache operations are included and safe on Spring 6.0 (where retrieve() doesn't exist on the Cache interface) — they're simply dead code, never called by the framework until Spring 6.1+. Co-Authored-By: Claude <noreply@anthropic.com> * feat(samples): Add cache tracing to all Spring Boot 3 Jakarta samples Add CacheController, TodoService with @Cacheable/@CachePut/@CacheEvict, Caffeine cache config, and CacheSystemTest e2e tests to all four Jakarta sample modules: - sentry-samples-spring-boot-jakarta - sentry-samples-spring-boot-jakarta-opentelemetry - sentry-samples-spring-boot-jakarta-opentelemetry-noagent - sentry-samples-spring-boot-webflux-jakarta Co-Authored-By: Claude <noreply@anthropic.com> * feat(spring): Add cache tracing for Spring Boot 2 / Spring 5 Port cache tracing instrumentation from sentry-spring-jakarta to sentry-spring for Spring Boot 2 users. Adds SentryCacheWrapper, SentryCacheManagerWrapper, and SentryCacheBeanPostProcessor in the io.sentry.spring.cache package. Wires auto-configuration in sentry-spring-boot via sentry.enable-cache-tracing=true property. The retrieve() methods are omitted since Spring 5 does not have them (they were added in Spring 6.1). Co-Authored-By: Claude <noreply@anthropic.com> * feat(samples): Add cache tracing to Spring Boot 2 sample Add CacheController, TodoService with @Cacheable/@CachePut/@CacheEvict annotations, and CacheSystemTest e2e tests to the sentry-samples-spring-boot sample. Enables cache tracing with Caffeine as the cache provider. Co-Authored-By: Claude <noreply@anthropic.com> * changelog * fix(spring): Skip cache span data when child span is NoOp Add span.isNoOp() check after startChild() in all three Spring SentryCacheWrapper variants, matching the existing pattern in SentryJCacheWrapper. This avoids setting span data on noop spans when sampling drops the span. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat(spring): Add db.operation.name attribute to cache spans Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(spring): Instrument putIfAbsent, replace, and getAndReplace cache operations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(spring): Use ValueWrapper to determine cache hit in typed get The get(key, type) method incorrectly used result != null to detect cache hits, failing to distinguish a miss from a cached null value. Now uses delegate.get(key) to check the ValueWrapper first. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(jcache): Fix docs link in README Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ref(spring): Use method-specific span operations for cache spans Instead of the 4 generic categories (cache.get, cache.put, cache.remove, cache.flush), use the actual method name as the span operation (e.g. cache.evict, cache.putIfAbsent, cache.retrieve). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ref(spring): Derive span operation from operationName in startSpan Remove redundant first parameter since it was always "cache." + operationName. The prefix is now applied inside the helper method. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ref(jcache): Merge startSpanForKeys into startSpan overload Replace the separate startSpanForKeys helper with a startSpan(Set, String) overload, unifying the two span creation methods under the same name. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Format code * ref(cache): Move operation attribute to SpanDataConvention as CACHE_OPERATION_KEY Replace local OPERATION_ATTRIBUTE constants in all cache wrappers with a shared CACHE_OPERATION_KEY constant in SpanDataConvention. Also changes the attribute key from "db.operation.name" to "cache.operation". * feat(cache): Add cache.write boolean span attribute Set cache.write on spans across all four cache wrapper implementations to indicate whether an operation actually modified the cache. This complements the existing cache.hit attribute for read operations. Co-Authored-By: Claude <noreply@anthropic.com> * fix(jcache): Use comma-joined keys as span description for bulk operations Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * ref(cache): Remove _KEY suffix from cache SpanDataConvention constants Rename CACHE_HIT_KEY, CACHE_KEY_KEY, and CACHE_OPERATION_KEY to CACHE_HIT, CACHE_KEY, and CACHE_OPERATION to match the newer naming convention used by CACHE_WRITE, THREAD_ID, FRAMES_TOTAL, etc. Co-Authored-By: Claude <noreply@anthropic.com> * fix(spring): Fix get(key, type) double-call in SentryCacheWrapper Use a single delegate.get(key, type) call instead of calling delegate.get(key) for hit detection and delegate.get(key, type) for the actual value. This eliminates doubled cache round trips (e.g. Redis network calls) and a TOCTOU race where the entry could expire between the two calls. The trade-off is that cached null values are now indistinguishable from cache misses, which is acceptable for observability purposes. * fix(samples): Fix cache evict system test to match actual span op * assert multiple keys in single assertions * Format code * update PR links in changelog * fix(spring): [Cache Tracing 24] Track invalidate cache.write accurately Set cache.write based on delegate.invalidate() result instead of always true. This keeps span data aligned with Spring's invalidate semantics when no entries were present. Add tests in spring, spring-jakarta, and spring-7 wrappers to cover the false return path and assert cache.write is false. Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
A
Alexander Dinauer committed
028aa67fabce3d2f9caa097bd4e2d13be6ba7b85
Parent: 23b2680
Committed by GitHub <noreply@github.com>
on 3/25/2026, 10:13:59 AM