Skip to content

Commit 081906a

Browse files
authored
Merge 8a93fec into 2a08014
2 parents 2a08014 + 8a93fec commit 081906a

File tree

7 files changed

+138
-40
lines changed

7 files changed

+138
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
- For `graphql-java` v22 and newer please use the `sentry-graphql-22` module
4646
- We now provide a `SentryInstrumenter` bean directly for Spring (Boot) if there is none yet instead of using `GraphQlSourceBuilderCustomizer` to add the instrumentation ([#3744](https://github.com/getsentry/sentry-java/pull/3744))
4747
- It is now also possible to provide a bean of type `SentryGraphqlInstrumentation.BeforeSpanCallback` which is then used by `SentryInstrumenter`
48+
- Lazy uuid generation for SentryId and SpanId ([#3770](https://github.com/getsentry/sentry-java/pull/3770))
4849

4950
### Fixes
5051

sentry/api/sentry.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6125,6 +6125,7 @@ public final class io/sentry/util/SpanUtils {
61256125
}
61266126

61276127
public final class io/sentry/util/StringUtils {
6128+
public static final field PROPER_NIL_UUID Ljava/lang/String;
61286129
public static fun byteCountToString (J)Ljava/lang/String;
61296130
public static fun calculateStringHash (Ljava/lang/String;Lio/sentry/ILogger;)Ljava/lang/String;
61306131
public static fun camelCase (Ljava/lang/String;)Ljava/lang/String;

sentry/src/main/java/io/sentry/SpanId.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,57 @@
11
package io.sentry;
22

3-
import io.sentry.util.Objects;
3+
import static io.sentry.util.StringUtils.PROPER_NIL_UUID;
4+
5+
import io.sentry.util.LazyEvaluator;
46
import io.sentry.util.StringUtils;
57
import java.io.IOException;
8+
import java.util.Objects;
69
import java.util.UUID;
710
import org.jetbrains.annotations.NotNull;
811

912
public final class SpanId implements JsonSerializable {
10-
public static final SpanId EMPTY_ID = new SpanId(new UUID(0, 0));
13+
public static final SpanId EMPTY_ID = new SpanId(PROPER_NIL_UUID);
1114

12-
private final @NotNull String value;
15+
private final @NotNull LazyEvaluator<String> lazyValue;
1316

1417
public SpanId(final @NotNull String value) {
15-
this.value = Objects.requireNonNull(value, "value is required");
18+
Objects.requireNonNull(value, "value is required");
19+
this.lazyValue = new LazyEvaluator<>(() -> value);
1620
}
1721

1822
public SpanId() {
19-
this(UUID.randomUUID());
20-
}
21-
22-
private SpanId(final @NotNull UUID uuid) {
23-
this(StringUtils.normalizeUUID(uuid.toString()).replace("-", "").substring(0, 16));
23+
this.lazyValue =
24+
new LazyEvaluator<>(
25+
() ->
26+
StringUtils.normalizeUUID(UUID.randomUUID().toString())
27+
.replace("-", "")
28+
.substring(0, 16));
2429
}
2530

2631
@Override
2732
public boolean equals(Object o) {
2833
if (this == o) return true;
2934
if (o == null || getClass() != o.getClass()) return false;
3035
SpanId spanId = (SpanId) o;
31-
return value.equals(spanId.value);
36+
return lazyValue.getValue().equals(spanId.lazyValue.getValue());
3237
}
3338

3439
@Override
3540
public int hashCode() {
36-
return value.hashCode();
41+
return lazyValue.getValue().hashCode();
3742
}
3843

3944
@Override
4045
public String toString() {
41-
return this.value;
46+
return lazyValue.getValue();
4247
}
4348

4449
// JsonElementSerializer
4550

4651
@Override
4752
public void serialize(final @NotNull ObjectWriter writer, final @NotNull ILogger logger)
4853
throws IOException {
49-
writer.value(value);
54+
writer.value(lazyValue.getValue());
5055
}
5156

5257
// JsonElementDeserializer

sentry/src/main/java/io/sentry/protocol/SentryId.java

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,69 +5,66 @@
55
import io.sentry.JsonSerializable;
66
import io.sentry.ObjectReader;
77
import io.sentry.ObjectWriter;
8+
import io.sentry.util.LazyEvaluator;
89
import io.sentry.util.StringUtils;
910
import java.io.IOException;
1011
import java.util.UUID;
1112
import org.jetbrains.annotations.NotNull;
1213
import org.jetbrains.annotations.Nullable;
1314

1415
public final class SentryId implements JsonSerializable {
15-
private final @NotNull UUID uuid;
1616

1717
public static final SentryId EMPTY_ID = new SentryId(new UUID(0, 0));
1818

19+
private final @NotNull LazyEvaluator<String> lazyStringValue;
20+
1921
public SentryId() {
2022
this((UUID) null);
2123
}
2224

2325
public SentryId(@Nullable UUID uuid) {
24-
if (uuid == null) {
25-
uuid = UUID.randomUUID();
26+
if (uuid != null) {
27+
this.lazyStringValue = new LazyEvaluator<>(() -> uuidToSentryIdString(uuid));
28+
} else {
29+
this.lazyStringValue = new LazyEvaluator<>(() -> uuidToSentryIdString(UUID.randomUUID()));
2630
}
27-
this.uuid = uuid;
2831
}
2932

3033
public SentryId(final @NotNull String sentryIdString) {
31-
this.uuid = fromStringSentryId(StringUtils.normalizeUUID(sentryIdString));
34+
String normalized = StringUtils.normalizeUUID(sentryIdString);
35+
if (normalized.length() != 32 && normalized.length() != 36) {
36+
throw new IllegalArgumentException(
37+
"String representation of SentryId has either 32 (UUID no dashes) "
38+
+ "or 36 characters long (completed UUID). Received: "
39+
+ sentryIdString);
40+
}
41+
this.lazyStringValue = new LazyEvaluator<>(() -> uuidStringToSentryIdString(normalized));
3242
}
3343

3444
@Override
3545
public String toString() {
36-
return StringUtils.normalizeUUID(uuid.toString()).replace("-", "");
46+
return lazyStringValue.getValue();
3747
}
3848

3949
@Override
4050
public boolean equals(final @Nullable Object o) {
4151
if (this == o) return true;
4252
if (o == null || getClass() != o.getClass()) return false;
4353
SentryId sentryId = (SentryId) o;
44-
return uuid.compareTo(sentryId.uuid) == 0;
54+
return lazyStringValue.getValue().compareTo(sentryId.lazyStringValue.getValue()) == 0;
4555
}
4656

4757
@Override
4858
public int hashCode() {
49-
return uuid.hashCode();
59+
return lazyStringValue.getValue().hashCode();
5060
}
5161

52-
private @NotNull UUID fromStringSentryId(@NotNull String sentryIdString) {
53-
if (sentryIdString.length() == 32) {
54-
// expected format, SentryId is a UUID without dashes
55-
sentryIdString =
56-
new StringBuilder(sentryIdString)
57-
.insert(8, "-")
58-
.insert(13, "-")
59-
.insert(18, "-")
60-
.insert(23, "-")
61-
.toString();
62-
}
63-
if (sentryIdString.length() != 36) {
64-
throw new IllegalArgumentException(
65-
"String representation of SentryId has either 32 (UUID no dashes) "
66-
+ "or 36 characters long (completed UUID). Received: "
67-
+ sentryIdString);
68-
}
62+
private @NotNull String uuidToSentryIdString(@NotNull UUID uuid) {
63+
return uuidStringToSentryIdString(uuid.toString());
64+
}
6965

70-
return UUID.fromString(sentryIdString);
66+
private @NotNull String uuidStringToSentryIdString(@NotNull String uuidString) {
67+
return StringUtils.normalizeUUID(uuidString).replace("-", "");
7168
}
7269

7370
// JsonSerializable

sentry/src/main/java/io/sentry/util/StringUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ public final class StringUtils {
2020

2121
private static final Charset UTF_8 = Charset.forName("UTF-8");
2222

23+
public static final String PROPER_NIL_UUID = "00000000-0000-0000-0000-000000000000";
2324
private static final String CORRUPTED_NIL_UUID = "0000-0000";
24-
private static final String PROPER_NIL_UUID = "00000000-0000-0000-0000-000000000000";
2525
private static final @NotNull Pattern PATTERN_WORD_SNAKE_CASE = Pattern.compile("[\\W_]+");
2626

2727
private StringUtils() {}

sentry/src/test/java/io/sentry/protocol/SentryIdTest.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package io.sentry.protocol
22

3+
import io.sentry.util.StringUtils
4+
import org.mockito.Mockito
5+
import org.mockito.kotlin.any
6+
import org.mockito.kotlin.never
7+
import org.mockito.kotlin.times
8+
import java.util.*
39
import kotlin.test.Test
410
import kotlin.test.assertEquals
511

@@ -10,4 +16,41 @@ class SentryIdTest {
1016
val id = SentryId("0000-0000")
1117
assertEquals("00000000000000000000000000000000", id.toString())
1218
}
19+
20+
@Test
21+
fun `UUID is not generated on initialization`() {
22+
val uuid = UUID.randomUUID()
23+
Mockito.mockStatic(UUID::class.java).use { utils ->
24+
utils.`when`<UUID> { UUID.randomUUID() }.thenReturn(uuid)
25+
val ignored = SentryId()
26+
utils.verify({ UUID.randomUUID() }, never())
27+
}
28+
}
29+
30+
@Test
31+
fun `UUID is generated only once`() {
32+
val uuid = UUID.randomUUID()
33+
Mockito.mockStatic(UUID::class.java).use { utils ->
34+
utils.`when`<UUID> { UUID.randomUUID() }.thenReturn(uuid)
35+
val sentryId = SentryId()
36+
val uuid1 = sentryId.toString()
37+
val uuid2 = sentryId.toString()
38+
39+
assertEquals(uuid1, uuid2)
40+
utils.verify({ UUID.randomUUID() }, times(1))
41+
}
42+
}
43+
44+
@Test
45+
fun `normalizeUUID is only called once`() {
46+
Mockito.mockStatic(StringUtils::class.java).use { utils ->
47+
utils.`when`<Any> { StringUtils.normalizeUUID(any()) }.thenReturn("00000000000000000000000000000000")
48+
val sentryId = SentryId()
49+
val uuid1 = sentryId.toString()
50+
val uuid2 = sentryId.toString()
51+
52+
assertEquals(uuid1, uuid2)
53+
utils.verify({ StringUtils.normalizeUUID(any()) }, times(1))
54+
}
55+
}
1356
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package io.sentry.protocol
2+
3+
import io.sentry.SpanId
4+
import io.sentry.util.StringUtils
5+
import org.mockito.Mockito
6+
import org.mockito.kotlin.any
7+
import org.mockito.kotlin.never
8+
import org.mockito.kotlin.times
9+
import java.util.*
10+
import kotlin.test.Test
11+
import kotlin.test.assertEquals
12+
13+
class SpanIdTest {
14+
15+
@Test
16+
fun `UUID is not generated on initialization`() {
17+
val uuid = UUID.randomUUID()
18+
Mockito.mockStatic(UUID::class.java).use { utils ->
19+
utils.`when`<UUID> { UUID.randomUUID() }.thenReturn(uuid)
20+
val ignored = SpanId()
21+
utils.verify({ UUID.randomUUID() }, never())
22+
}
23+
}
24+
25+
@Test
26+
fun `UUID is generated only once`() {
27+
val uuid = UUID.randomUUID()
28+
Mockito.mockStatic(java.util.UUID::class.java).use { utils ->
29+
utils.`when`<UUID> { UUID.randomUUID() }.thenReturn(uuid)
30+
val spanId = SpanId()
31+
val uuid1 = spanId.toString()
32+
val uuid2 = spanId.toString()
33+
34+
assertEquals(uuid1, uuid2)
35+
utils.verify({ UUID.randomUUID() }, times(1))
36+
}
37+
}
38+
39+
@Test
40+
fun `normalizeUUID is only called once`() {
41+
Mockito.mockStatic(StringUtils::class.java).use { utils ->
42+
utils.`when`<Any> { StringUtils.normalizeUUID(any()) }.thenReturn("00000000000000000000000000000000")
43+
val spanId = SpanId()
44+
val uuid1 = spanId.toString()
45+
val uuid2 = spanId.toString()
46+
47+
assertEquals(uuid1, uuid2)
48+
utils.verify({ StringUtils.normalizeUUID(any()) }, times(1))
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)