Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
# 3.1.2

* feat: Manually capturing User Feedback
* Enhancement: Set environment to "production" by default.

# 3.1.1
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,8 @@
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import io.sentry.Sentry;
import io.sentry.UserFeedback;
import io.sentry.protocol.SentryId;
import io.sentry.protocol.User;
import io.sentry.sample.databinding.ActivityMainBinding;
import java.util.Collections;
Expand All@@ -22,6 +24,16 @@ protected void onCreate(Bundle savedInstanceState) {

binding.sendMessage.setOnClickListener(view -> Sentry.captureMessage("Some message."));

binding.sendUserFeedback.setOnClickListener(view -> {
SentryId sentryId = Sentry.captureException(new Exception("I have feedback"));

UserFeedback userFeedback = new UserFeedback(sentryId);
userFeedback.setComments("It broke on Android. I don't know why, but this happens.");
userFeedback.setEmail("john@me.com");
userFeedback.setName("John Me");
Sentry.captureUserFeedback(userFeedback);
});

binding.captureException.setOnClickListener(
view ->
Sentry.captureException(
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,7 +16,13 @@
android:id="@+id/send_message"
android:text="@string/send_message"/>

<Button android:layout_width="wrap_content"
<Button
android:id="@+id/send_user_feedback"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/send_user_feedback" />

<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/capture_exception"
android:text="@string/capture_exception"/>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@
<string name="app_name">Sentry sample</string>
<string name="crash_from_java">Crash from Java (UncaughtException)</string>
<string name="send_message">Send Message</string>
<string name="send_user_feedback">Send User Feedback</string>
<string name="capture_exception">Capture Exception</string>
<string name="breadcrumb">Breadcrumb</string>
<string name="set_user">Set user</string>
Expand Down
29 changes: 29 additions & 0 deletions sentry/src/main/java/io/sentry/GsonSerializer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -106,6 +106,19 @@ Device.DeviceOrientation.class, new OrientationDeserializerAdapter(logger))
return gson.fromJson(reader, SentryEvent.class);
}

/**
* Deserialize UserFeedback from a stream Reader (JSON)
*
* @param reader the Reader
* @return the UserFeedback class or null
*/
@Override
public UserFeedback deserializeUserFeedback(Reader reader) {
Objects.requireNonNull(reader, "The Reader object is required.");

return gson.fromJson(reader, UserFeedback.class);
}

/**
* Deserialize a Session from a stream Reader (JSON)
*
Expand DownExpand Up@@ -170,6 +183,22 @@ public void serialize(final @NotNull Session session, final @NotNull Writer writ
writer.flush();
}

/**
* Serialize UserFeedback to a stream Writer (JSON)
*
* @param userFeedback the Session
* @param writer the Writer
* @throws IOException an IOException
*/
@Override
public void serialize(UserFeedback userFeedback, Writer writer) throws IOException {
Objects.requireNonNull(userFeedback, "The UserFeedback object is required.");
Objects.requireNonNull(writer, "The Writer object is required.");

gson.toJson(userFeedback, UserFeedback.class, writer);
writer.flush();
}

/**
* Serialize a SentryEnvelope to a stream Writer (JSON)
*
Expand Down
22 changes: 22 additions & 0 deletions sentry/src/main/java/io/sentry/Hub.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -188,6 +188,28 @@ public SentryId captureEnvelope(
return sentryId;
}

@Override
public void captureUserFeedback(UserFeedback userFeedback) {
if (!isEnabled()) {
options
.getLogger()
.log(
SentryLevel.WARNING, "Instance is disabled and this 'captureUserFeedback' call is a no-op.");
} else {
try {
final StackItem item = stack.peek();
if (item != null) {
item.client.captureUserFeedback(userFeedback);
} else {
options.getLogger().log(SentryLevel.FATAL, "Stack peek was null when captureUserFeedback");
}
} catch (Exception e) {
options.getLogger().log(SentryLevel.ERROR,
"Error while capturing captureUserFeedback: " + userFeedback.toString(), e);
}
}
}

@Override
public void startSession() {
if (!isEnabled()) {
Expand Down
5 changes: 5 additions & 0 deletions sentry/src/main/java/io/sentry/HubAdapter.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,11 @@ public SentryId captureException(Throwable throwable, @Nullable Object hint) {
return Sentry.captureException(throwable, hint);
}

@Override
public void captureUserFeedback(UserFeedback userFeedback) {
Sentry.captureUserFeedback(userFeedback);
}

@Override
public void startSession() {
Sentry.startSession();
Expand Down
7 changes: 7 additions & 0 deletions sentry/src/main/java/io/sentry/IHub.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,6 +92,13 @@ default SentryId captureException(Throwable throwable) {
return captureException(throwable, null);
}

/**
* Captures a manually created user feedback and sends it to Sentry.
*
* @param userFeedback The user feedback to send to Sentry.
*/
void captureUserFeedback(UserFeedback userFeedback);

/** Starts a new session. If there's a running session, it ends it before starting the new one. */
void startSession();

Expand Down
7 changes: 7 additions & 0 deletions sentry/src/main/java/io/sentry/ISentryClient.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -141,6 +141,13 @@ default SentryId captureException(Throwable throwable, @Nullable Scope scope) {
return captureException(throwable, scope, null);
}

/**
* Captures a manually created user feedback and sends it to Sentry.
*
* @param userFeedback The user feedback to send to Sentry.
*/
void captureUserFeedback(UserFeedback userFeedback);

/**
* Captures a session. This method transform a session to an envelope and forwards to
* captureEnvelope
Expand Down
4 changes: 4 additions & 0 deletions sentry/src/main/java/io/sentry/ISerializer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,6 +9,8 @@
public interface ISerializer {
SentryEvent deserializeEvent(Reader reader);

UserFeedback deserializeUserFeedback(Reader reader);

Session deserializeSession(Reader reader);

SentryEnvelope deserializeEnvelope(InputStream inputStream);
Expand All@@ -17,6 +19,8 @@ public interface ISerializer {

void serialize(Session session, Writer writer) throws IOException;

void serialize(UserFeedback userFeedback, Writer writer) throws IOException;

void serialize(SentryEnvelope envelope, Writer writer) throws Exception;

String serialize(Map<String, Object> data) throws Exception;
Expand Down
3 changes: 3 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpHub.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -40,6 +40,9 @@ public SentryId captureException(Throwable throwable, @Nullable Object hint) {
return SentryId.EMPTY_ID;
}

@Override
public void captureUserFeedback(UserFeedback userFeedback) { }

@Override
public void startSession() {}

Expand Down
3 changes: 3 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpSentryClient.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,9 @@ public void close() {}
@Override
public void flush(long timeoutMillis) {}

@Override
public void captureUserFeedback(UserFeedback userFeedback) { }

@Override
public void captureSession(Session session, @Nullable Object hint) {}

Expand Down
8 changes: 8 additions & 0 deletions sentry/src/main/java/io/sentry/NoOpSerializer.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,11 @@ public SentryEvent deserializeEvent(Reader reader) {
return null;
}

@Override
public UserFeedback deserializeUserFeedback(Reader reader) {
return null;
}

@Override
public Session deserializeSession(Reader reader) {
return null;
Expand All@@ -38,6 +43,9 @@ public void serialize(SentryEvent event, Writer writer) {}
@Override
public void serialize(Session session, Writer writer) throws IOException {}

@Override
public void serialize(UserFeedback userFeedback, Writer writer) throws IOException { }

@Override
public void serialize(SentryEnvelope envelope, Writer outputStream) throws Exception {}

Expand Down
9 changes: 9 additions & 0 deletions sentry/src/main/java/io/sentry/Sentry.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -293,6 +293,15 @@ public static synchronized void close() {
return getCurrentHub().captureException(throwable, hint);
}

/**
* Captures a manually created user feedback and sends it to Sentry.
*
* @param userFeedback The user feedback to send to Sentry.
*/
public static void captureUserFeedback(UserFeedback userFeedback) {
getCurrentHub().captureUserFeedback(userFeedback);
}

/**
* Adds a breadcrumb to the current Scope
*
Expand Down
26 changes: 26 additions & 0 deletions sentry/src/main/java/io/sentry/SentryClient.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -183,6 +183,32 @@ private SentryEvent processEvent(
return event;
}

@Override
public void captureUserFeedback(UserFeedback userFeedback) {
Objects.requireNonNull(userFeedback, "SentryEvent is required.");

options.getLogger().log(SentryLevel.DEBUG, "Capturing userFeedback: %s", userFeedback.getEventId());

try {
final SentryEnvelope envelope = buildEnvelope(userFeedback);
connection.send(envelope);
} catch (IOException e) {
options.getLogger().log(SentryLevel.WARNING, e, "Capturing user feedback %s failed.", userFeedback.getEventId());
}
}

private SentryEnvelope buildEnvelope(@NotNull UserFeedback userFeedback) {
final List<SentryEnvelopeItem> envelopeItems = new ArrayList<>();

final SentryEnvelopeItem userFeedbackItem = SentryEnvelopeItem.fromUserFeedback(options.getSerializer(), userFeedback);
envelopeItems.add(userFeedbackItem);

final SentryEnvelopeHeader envelopeHeader =
new SentryEnvelopeHeader(userFeedback.getEventId(), options.getSdkVersion());

return new SentryEnvelope(envelopeHeader, envelopeItems);
}

/**
* Updates the session data based on the event, hint and scope data
*
Expand Down
22 changes: 22 additions & 0 deletions sentry/src/main/java/io/sentry/SentryEnvelopeItem.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -108,6 +108,28 @@ public final class SentryEnvelopeItem {
return new SentryEnvelopeItem(itemHeader, () -> cachedItem.getBytes());
}

public static SentryEnvelopeItem fromUserFeedback(
final @NotNull ISerializer serializer, final @NotNull UserFeedback userFeedback) {
Objects.requireNonNull(serializer, "ISerializer is required.");
Objects.requireNonNull(userFeedback, "UserFeedback is required.");

final CachedItem cachedItem =
new CachedItem(
() -> {
try (final ByteArrayOutputStream stream = new ByteArrayOutputStream();
final Writer writer = new BufferedWriter(new OutputStreamWriter(stream, UTF_8))) {
serializer.serialize(userFeedback, writer);
return stream.toByteArray();
}
});

SentryEnvelopeItemHeader itemHeader =
new SentryEnvelopeItemHeader(
SentryItemType.UserFeedback, () -> cachedItem.getBytes().length, "application/json", null);

return new SentryEnvelopeItem(itemHeader, cachedItem::getBytes);
}

private static class CachedItem {
private @Nullable byte[] bytes;
private final @Nullable Callable<byte[]> dataFactory;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,7 +31,7 @@ public void write(JsonWriter writer, SentryEnvelopeItemHeader value) throws IOEx

if (!SentryItemType.Unknown.equals(value.getType())) {
writer.name("type");
writer.value(value.getType().name().toLowerCase(Locale.ROOT));
writer.value(value.getType().getItemType().toLowerCase(Locale.ROOT));
}

writer.name("length");
Expand Down
1 change: 1 addition & 0 deletions sentry/src/main/java/io/sentry/SentryItemType.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
public enum SentryItemType {
Session("session"),
Event("event"), // DataCategory.Error
UserFeedback("user_report"), // Sentry backend still uses user_report
Attachment("attachment"),
Transaction("transaction"),
Unknown("__unknown__"); // DataCategory.Unknown
Expand Down
Loading