Skip to content

Commit 8baac33

Browse files
authored
Merge 6d6f5eb into 7735a81
2 parents 7735a81 + 6d6f5eb commit 8baac33

File tree

7 files changed

+598
-0
lines changed

7 files changed

+598
-0
lines changed

sentry-spring-7/api/sentry-spring-7.api

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,27 @@ public final class io/sentry/spring7/SpringSecuritySentryUserProvider : io/sentr
104104
public fun provideUser ()Lio/sentry/protocol/User;
105105
}
106106

107+
public final class io/sentry/spring7/cache/SentryCacheManagerWrapper : org/springframework/cache/CacheManager {
108+
public fun <init> (Lorg/springframework/cache/CacheManager;Lio/sentry/IScopes;)V
109+
public fun getCache (Ljava/lang/String;)Lorg/springframework/cache/Cache;
110+
public fun getCacheNames ()Ljava/util/Collection;
111+
}
112+
113+
public final class io/sentry/spring7/cache/SentryCacheWrapper : org/springframework/cache/Cache {
114+
public fun <init> (Lorg/springframework/cache/Cache;Lio/sentry/IScopes;)V
115+
public fun clear ()V
116+
public fun evict (Ljava/lang/Object;)V
117+
public fun evictIfPresent (Ljava/lang/Object;)Z
118+
public fun get (Ljava/lang/Object;)Lorg/springframework/cache/Cache$ValueWrapper;
119+
public fun get (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object;
120+
public fun get (Ljava/lang/Object;Ljava/util/concurrent/Callable;)Ljava/lang/Object;
121+
public fun getName ()Ljava/lang/String;
122+
public fun getNativeCache ()Ljava/lang/Object;
123+
public fun invalidate ()Z
124+
public fun put (Ljava/lang/Object;Ljava/lang/Object;)V
125+
public fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Lorg/springframework/cache/Cache$ValueWrapper;
126+
}
127+
107128
public abstract interface annotation class io/sentry/spring7/checkin/SentryCheckIn : java/lang/annotation/Annotation {
108129
public abstract fun heartbeat ()Z
109130
public abstract fun monitorSlug ()Ljava/lang/String;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.sentry.spring7.cache;
2+
3+
import io.sentry.IScopes;
4+
import java.util.Collection;
5+
import org.jetbrains.annotations.ApiStatus;
6+
import org.jetbrains.annotations.NotNull;
7+
import org.jetbrains.annotations.Nullable;
8+
import org.springframework.cache.Cache;
9+
import org.springframework.cache.CacheManager;
10+
11+
/** Wraps a Spring {@link CacheManager} to return Sentry-instrumented caches. */
12+
@ApiStatus.Internal
13+
public final class SentryCacheManagerWrapper implements CacheManager {
14+
15+
private final @NotNull CacheManager delegate;
16+
private final @NotNull IScopes scopes;
17+
18+
public SentryCacheManagerWrapper(
19+
final @NotNull CacheManager delegate, final @NotNull IScopes scopes) {
20+
this.delegate = delegate;
21+
this.scopes = scopes;
22+
}
23+
24+
@Override
25+
public @Nullable Cache getCache(final @NotNull String name) {
26+
final Cache cache = delegate.getCache(name);
27+
if (cache == null) {
28+
return null;
29+
}
30+
return new SentryCacheWrapper(cache, scopes);
31+
}
32+
33+
@Override
34+
public @NotNull Collection<String> getCacheNames() {
35+
return delegate.getCacheNames();
36+
}
37+
}
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
package io.sentry.spring7.cache;
2+
3+
import io.sentry.IScopes;
4+
import io.sentry.ISpan;
5+
import io.sentry.SpanDataConvention;
6+
import io.sentry.SpanOptions;
7+
import io.sentry.SpanStatus;
8+
import java.util.Arrays;
9+
import java.util.concurrent.Callable;
10+
import org.jetbrains.annotations.ApiStatus;
11+
import org.jetbrains.annotations.NotNull;
12+
import org.jetbrains.annotations.Nullable;
13+
import org.springframework.cache.Cache;
14+
15+
/** Wraps a Spring {@link Cache} to create Sentry spans for cache operations. */
16+
@ApiStatus.Internal
17+
public final class SentryCacheWrapper implements Cache {
18+
19+
private static final String TRACE_ORIGIN = "auto.cache.spring";
20+
21+
private final @NotNull Cache delegate;
22+
private final @NotNull IScopes scopes;
23+
24+
public SentryCacheWrapper(final @NotNull Cache delegate, final @NotNull IScopes scopes) {
25+
this.delegate = delegate;
26+
this.scopes = scopes;
27+
}
28+
29+
@Override
30+
public @NotNull String getName() {
31+
return delegate.getName();
32+
}
33+
34+
@Override
35+
public @NotNull Object getNativeCache() {
36+
return delegate.getNativeCache();
37+
}
38+
39+
@Override
40+
public @Nullable ValueWrapper get(final @NotNull Object key) {
41+
final ISpan span = startSpan("cache.get", key);
42+
if (span == null) {
43+
return delegate.get(key);
44+
}
45+
try {
46+
final ValueWrapper result = delegate.get(key);
47+
span.setData(SpanDataConvention.CACHE_HIT_KEY, result != null);
48+
span.setStatus(SpanStatus.OK);
49+
return result;
50+
} catch (Throwable e) {
51+
span.setStatus(SpanStatus.INTERNAL_ERROR);
52+
span.setThrowable(e);
53+
throw e;
54+
} finally {
55+
span.finish();
56+
}
57+
}
58+
59+
@Override
60+
public @Nullable <T> T get(final @NotNull Object key, final @Nullable Class<T> type) {
61+
final ISpan span = startSpan("cache.get", key);
62+
if (span == null) {
63+
return delegate.get(key, type);
64+
}
65+
try {
66+
final T result = delegate.get(key, type);
67+
span.setData(SpanDataConvention.CACHE_HIT_KEY, result != null);
68+
span.setStatus(SpanStatus.OK);
69+
return result;
70+
} catch (Throwable e) {
71+
span.setStatus(SpanStatus.INTERNAL_ERROR);
72+
span.setThrowable(e);
73+
throw e;
74+
} finally {
75+
span.finish();
76+
}
77+
}
78+
79+
@Override
80+
public @Nullable <T> T get(final @NotNull Object key, final @NotNull Callable<T> valueLoader) {
81+
final ISpan span = startSpan("cache.get", key);
82+
if (span == null) {
83+
return delegate.get(key, valueLoader);
84+
}
85+
try {
86+
final T result = delegate.get(key, valueLoader);
87+
// valueLoader is called on miss, so the method always returns a value
88+
span.setData(SpanDataConvention.CACHE_HIT_KEY, true);
89+
span.setStatus(SpanStatus.OK);
90+
return result;
91+
} catch (Throwable e) {
92+
span.setStatus(SpanStatus.INTERNAL_ERROR);
93+
span.setThrowable(e);
94+
throw e;
95+
} finally {
96+
span.finish();
97+
}
98+
}
99+
100+
@Override
101+
public void put(final @NotNull Object key, final @Nullable Object value) {
102+
final ISpan span = startSpan("cache.put", key);
103+
if (span == null) {
104+
delegate.put(key, value);
105+
return;
106+
}
107+
try {
108+
delegate.put(key, value);
109+
span.setStatus(SpanStatus.OK);
110+
} catch (Throwable e) {
111+
span.setStatus(SpanStatus.INTERNAL_ERROR);
112+
span.setThrowable(e);
113+
throw e;
114+
} finally {
115+
span.finish();
116+
}
117+
}
118+
119+
@Override
120+
public @Nullable ValueWrapper putIfAbsent(
121+
final @NotNull Object key, final @Nullable Object value) {
122+
final ISpan span = startSpan("cache.put", key);
123+
if (span == null) {
124+
return delegate.putIfAbsent(key, value);
125+
}
126+
try {
127+
final ValueWrapper result = delegate.putIfAbsent(key, value);
128+
span.setStatus(SpanStatus.OK);
129+
return result;
130+
} catch (Throwable e) {
131+
span.setStatus(SpanStatus.INTERNAL_ERROR);
132+
span.setThrowable(e);
133+
throw e;
134+
} finally {
135+
span.finish();
136+
}
137+
}
138+
139+
@Override
140+
public void evict(final @NotNull Object key) {
141+
final ISpan span = startSpan("cache.remove", key);
142+
if (span == null) {
143+
delegate.evict(key);
144+
return;
145+
}
146+
try {
147+
delegate.evict(key);
148+
span.setStatus(SpanStatus.OK);
149+
} catch (Throwable e) {
150+
span.setStatus(SpanStatus.INTERNAL_ERROR);
151+
span.setThrowable(e);
152+
throw e;
153+
} finally {
154+
span.finish();
155+
}
156+
}
157+
158+
@Override
159+
public boolean evictIfPresent(final @NotNull Object key) {
160+
final ISpan span = startSpan("cache.remove", key);
161+
if (span == null) {
162+
return delegate.evictIfPresent(key);
163+
}
164+
try {
165+
final boolean result = delegate.evictIfPresent(key);
166+
span.setStatus(SpanStatus.OK);
167+
return result;
168+
} catch (Throwable e) {
169+
span.setStatus(SpanStatus.INTERNAL_ERROR);
170+
span.setThrowable(e);
171+
throw e;
172+
} finally {
173+
span.finish();
174+
}
175+
}
176+
177+
@Override
178+
public void clear() {
179+
final ISpan span = startSpan("cache.flush", null);
180+
if (span == null) {
181+
delegate.clear();
182+
return;
183+
}
184+
try {
185+
delegate.clear();
186+
span.setStatus(SpanStatus.OK);
187+
} catch (Throwable e) {
188+
span.setStatus(SpanStatus.INTERNAL_ERROR);
189+
span.setThrowable(e);
190+
throw e;
191+
} finally {
192+
span.finish();
193+
}
194+
}
195+
196+
@Override
197+
public boolean invalidate() {
198+
final ISpan span = startSpan("cache.flush", null);
199+
if (span == null) {
200+
return delegate.invalidate();
201+
}
202+
try {
203+
final boolean result = delegate.invalidate();
204+
span.setStatus(SpanStatus.OK);
205+
return result;
206+
} catch (Throwable e) {
207+
span.setStatus(SpanStatus.INTERNAL_ERROR);
208+
span.setThrowable(e);
209+
throw e;
210+
} finally {
211+
span.finish();
212+
}
213+
}
214+
215+
private @Nullable ISpan startSpan(final @NotNull String operation, final @Nullable Object key) {
216+
final ISpan activeSpan = scopes.getSpan();
217+
if (activeSpan == null || activeSpan.isNoOp()) {
218+
return null;
219+
}
220+
221+
final SpanOptions spanOptions = new SpanOptions();
222+
spanOptions.setOrigin(TRACE_ORIGIN);
223+
final ISpan span = activeSpan.startChild(operation, getName(), spanOptions);
224+
if (key != null) {
225+
span.setData(SpanDataConvention.CACHE_KEY_KEY, Arrays.asList(String.valueOf(key)));
226+
}
227+
return span;
228+
}
229+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.sentry.spring7.cache
2+
3+
import io.sentry.IScopes
4+
import kotlin.test.Test
5+
import kotlin.test.assertEquals
6+
import kotlin.test.assertNull
7+
import kotlin.test.assertTrue
8+
import org.mockito.kotlin.mock
9+
import org.mockito.kotlin.whenever
10+
import org.springframework.cache.Cache
11+
import org.springframework.cache.CacheManager
12+
13+
class SentryCacheManagerWrapperTest {
14+
15+
private val scopes: IScopes = mock()
16+
private val delegate: CacheManager = mock()
17+
18+
@Test
19+
fun `getCache wraps returned cache in SentryCacheWrapper`() {
20+
val cache = mock<Cache>()
21+
whenever(delegate.getCache("test")).thenReturn(cache)
22+
23+
val wrapper = SentryCacheManagerWrapper(delegate, scopes)
24+
val result = wrapper.getCache("test")
25+
26+
assertTrue(result is SentryCacheWrapper)
27+
}
28+
29+
@Test
30+
fun `getCache returns null when delegate returns null`() {
31+
whenever(delegate.getCache("missing")).thenReturn(null)
32+
33+
val wrapper = SentryCacheManagerWrapper(delegate, scopes)
34+
val result = wrapper.getCache("missing")
35+
36+
assertNull(result)
37+
}
38+
39+
@Test
40+
fun `getCacheNames delegates to underlying cache manager`() {
41+
whenever(delegate.cacheNames).thenReturn(listOf("cache1", "cache2"))
42+
43+
val wrapper = SentryCacheManagerWrapper(delegate, scopes)
44+
val result = wrapper.cacheNames
45+
46+
assertEquals(listOf("cache1", "cache2"), result)
47+
}
48+
}

0 commit comments

Comments
 (0)