diff --git a/sentry-core/src/main/java/io/sentry/core/SentryEnvelope.java b/sentry-core/src/main/java/io/sentry/core/SentryEnvelope.java index 006c83a56..54ceb3a3b 100644 --- a/sentry-core/src/main/java/io/sentry/core/SentryEnvelope.java +++ b/sentry-core/src/main/java/io/sentry/core/SentryEnvelope.java @@ -64,4 +64,16 @@ public SentryEnvelope( return new SentryEnvelope( null, sdkVersion, SentryEnvelopeItem.fromSession(serializer, session)); } + + public static @NotNull SentryEnvelope fromEvent( + final @NotNull ISerializer serializer, + final @NotNull SentryEvent event, + final @Nullable SdkVersion sdkVersion) + throws IOException { + Objects.requireNonNull(serializer, "Serializer is required."); + Objects.requireNonNull(event, "Event is required."); + + return new SentryEnvelope( + event.getEventId(), sdkVersion, SentryEnvelopeItem.fromEvent(serializer, event)); + } } diff --git a/sentry-core/src/main/java/io/sentry/core/transport/AsyncConnection.java b/sentry-core/src/main/java/io/sentry/core/transport/AsyncConnection.java index 678b3ddd5..a351597d8 100644 --- a/sentry-core/src/main/java/io/sentry/core/transport/AsyncConnection.java +++ b/sentry-core/src/main/java/io/sentry/core/transport/AsyncConnection.java @@ -244,7 +244,7 @@ private final class EventSender implements Runnable { private final SentryEvent event; private final Object hint; private final IEventCache eventCache; - private final TransportResult failedResult = TransportResult.error(-1); + private final TransportResult failedResult = TransportResult.error(); EventSender( final @NotNull SentryEvent event, @@ -288,7 +288,10 @@ public void run() { if (transportGate.isConnected()) { try { - result = transport.send(event); + result = + transport.send( + SentryEnvelope.fromEvent( + options.getSerializer(), event, options.getSdkVersion())); if (result.isSuccess()) { eventCache.discard(event); } else { @@ -325,7 +328,7 @@ private final class SessionSender implements Runnable { private final @NotNull SentryEnvelope envelope; private final @Nullable Object hint; private final @NotNull IEnvelopeCache sessionCache; - private final TransportResult failedResult = TransportResult.error(-1); + private final TransportResult failedResult = TransportResult.error(); SessionSender( final @NotNull SentryEnvelope envelope, diff --git a/sentry-core/src/main/java/io/sentry/core/transport/HttpTransport.java b/sentry-core/src/main/java/io/sentry/core/transport/HttpTransport.java index bf5f835db..bc0c257f8 100644 --- a/sentry-core/src/main/java/io/sentry/core/transport/HttpTransport.java +++ b/sentry-core/src/main/java/io/sentry/core/transport/HttpTransport.java @@ -9,7 +9,6 @@ import io.sentry.core.ILogger; import io.sentry.core.ISerializer; import io.sentry.core.SentryEnvelope; -import io.sentry.core.SentryEvent; import io.sentry.core.SentryOptions; import io.sentry.core.util.Objects; import io.sentry.core.util.StringUtils; @@ -76,7 +75,6 @@ public String getCategory() { private final int connectionTimeout; private final int readTimeout; private final boolean bypassSecurity; - private final @NotNull URL storeUrl; private final @NotNull URL envelopeUrl; private final @NotNull SentryOptions options; @@ -139,42 +137,15 @@ public HttpTransport( try { final URI uri = sentryUrl.toURI(); - storeUrl = uri.resolve(uri.getPath() + "/store/").toURL(); envelopeUrl = uri.resolve(uri.getPath() + "/envelope/").toURL(); } catch (URISyntaxException | MalformedURLException e) { throw new IllegalArgumentException("Failed to compose the Sentry's server URL.", e); } } - // giving up on testing this method is probably the simplest way of having the rest of the class - // testable... - protected @NotNull HttpURLConnection open(final @Nullable Proxy proxy) throws IOException { - return open(storeUrl, proxy); - } - - protected @NotNull HttpURLConnection open(final @NotNull URL url, final @Nullable Proxy proxy) - throws IOException { - return (HttpURLConnection) (proxy == null ? url.openConnection() : url.openConnection(proxy)); - } - - @Override - public @NotNull TransportResult send(final @NotNull SentryEvent event) throws IOException { - final HttpURLConnection connection = createConnection(false); - TransportResult result; - - try (final OutputStream outputStream = connection.getOutputStream(); - final GZIPOutputStream gzip = new GZIPOutputStream(outputStream); - final Writer writer = new BufferedWriter(new OutputStreamWriter(gzip, UTF_8))) { - - serializer.serialize(event, writer); - } catch (IOException e) { - logger.log( - ERROR, e, "An exception occurred while submitting the event to the Sentry server."); - } finally { - result = - readAndLog(connection, String.format("Event sent %s successfully.", event.getEventId())); - } - return result; + protected @NotNull HttpURLConnection open() throws IOException { + return (HttpURLConnection) + (proxy == null ? envelopeUrl.openConnection() : envelopeUrl.openConnection(proxy)); } /** @@ -235,19 +206,11 @@ public boolean isRetryAfter(final @NotNull String itemType) { /** * Create a HttpURLConnection connection Sets specific content-type if its an envelope or not * - * @param asEnvelope if its an envelope or not * @return the HttpURLConnection * @throws IOException if connection has a problem */ - private @NotNull HttpURLConnection createConnection(boolean asEnvelope) throws IOException { - String contentType = "application/json"; - HttpURLConnection connection; - if (asEnvelope) { - connection = open(envelopeUrl, proxy); - contentType = "application/x-sentry-envelope"; - } else { - connection = open(proxy); - } + private @NotNull HttpURLConnection createConnection() throws IOException { + HttpURLConnection connection = open(); connectionConfigurator.configure(connection); connection.setRequestMethod("POST"); @@ -255,7 +218,7 @@ public boolean isRetryAfter(final @NotNull String itemType) { connection.setChunkedStreamingMode(0); connection.setRequestProperty("Content-Encoding", "gzip"); - connection.setRequestProperty("Content-Type", contentType); + connection.setRequestProperty("Content-Type", "application/x-sentry-envelope"); connection.setRequestProperty("Accept", "application/json"); // https://stackoverflow.com/questions/52726909/java-io-ioexception-unexpected-end-of-stream-on-connection/53089882 @@ -274,7 +237,7 @@ public boolean isRetryAfter(final @NotNull String itemType) { @Override public @NotNull TransportResult send(final @NotNull SentryEnvelope envelope) throws IOException { - final HttpURLConnection connection = createConnection(true); + final HttpURLConnection connection = createConnection(); TransportResult result; try (final OutputStream outputStream = connection.getOutputStream(); @@ -286,7 +249,7 @@ public boolean isRetryAfter(final @NotNull String itemType) { logger.log( ERROR, e, "An exception occurred while submitting the envelope to the Sentry server."); } finally { - result = readAndLog(connection, "Envelope sent successfully."); + result = readAndLog(connection); } return result; } @@ -295,11 +258,9 @@ public boolean isRetryAfter(final @NotNull String itemType) { * Read responde code, retry after header and its error stream if there are errors and log it * * @param connection the HttpURLConnection - * @param message the message, if custom message if its an event or envelope * @return TransportResult.success if responseCode is 200 or TransportResult.error otherwise */ - private @NotNull TransportResult readAndLog( - final @NotNull HttpURLConnection connection, final @NotNull String message) { + private @NotNull TransportResult readAndLog(final @NotNull HttpURLConnection connection) { try { final int responseCode = connection.getResponseCode(); @@ -316,7 +277,7 @@ public boolean isRetryAfter(final @NotNull String itemType) { return TransportResult.error(responseCode); } - logger.log(DEBUG, message); + logger.log(DEBUG, "Envelope sent successfully."); return TransportResult.success(); } catch (IOException e) { diff --git a/sentry-core/src/main/java/io/sentry/core/transport/ITransport.java b/sentry-core/src/main/java/io/sentry/core/transport/ITransport.java index a2c424521..c0b165285 100644 --- a/sentry-core/src/main/java/io/sentry/core/transport/ITransport.java +++ b/sentry-core/src/main/java/io/sentry/core/transport/ITransport.java @@ -1,14 +1,11 @@ package io.sentry.core.transport; import io.sentry.core.SentryEnvelope; -import io.sentry.core.SentryEvent; import java.io.Closeable; import java.io.IOException; /** A transport is in charge of sending the event to the Sentry server. */ public interface ITransport extends Closeable { - TransportResult send(SentryEvent event) throws IOException; - boolean isRetryAfter(String type); TransportResult send(SentryEnvelope envelope) throws IOException; diff --git a/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransport.java b/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransport.java index c0de83a03..f3e856c6c 100644 --- a/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransport.java +++ b/sentry-core/src/main/java/io/sentry/core/transport/NoOpTransport.java @@ -1,7 +1,6 @@ package io.sentry.core.transport; import io.sentry.core.SentryEnvelope; -import io.sentry.core.SentryEvent; import java.io.IOException; import org.jetbrains.annotations.ApiStatus; @@ -16,11 +15,6 @@ public static NoOpTransport getInstance() { private NoOpTransport() {} - @Override - public TransportResult send(SentryEvent event) throws IOException { - return TransportResult.success(); - } - @Override public boolean isRetryAfter(String type) { return false; diff --git a/sentry-core/src/main/java/io/sentry/core/transport/StdoutTransport.java b/sentry-core/src/main/java/io/sentry/core/transport/StdoutTransport.java index f82f4e079..f5b9c1929 100644 --- a/sentry-core/src/main/java/io/sentry/core/transport/StdoutTransport.java +++ b/sentry-core/src/main/java/io/sentry/core/transport/StdoutTransport.java @@ -2,7 +2,6 @@ import io.sentry.core.ISerializer; import io.sentry.core.SentryEnvelope; -import io.sentry.core.SentryEvent; import io.sentry.core.util.Objects; import java.io.BufferedWriter; import java.io.IOException; @@ -22,17 +21,6 @@ public StdoutTransport(final @NotNull ISerializer serializer) { this.serializer = Objects.requireNonNull(serializer, "Serializer is required"); } - @Override - public TransportResult send(final @NotNull SentryEvent event) throws IOException { - Objects.requireNonNull(event, "SentryEvent is required"); - - try (final Writer writer = new BufferedWriter(new OutputStreamWriter(System.out, UTF_8)); - final Writer printWriter = new PrintWriter(writer)) { - serializer.serialize(event, printWriter); - return TransportResult.success(); - } - } - @Override public boolean isRetryAfter(String type) { return false; @@ -47,7 +35,7 @@ public TransportResult send(final @NotNull SentryEnvelope envelope) throws IOExc serializer.serialize(envelope, printWriter); return TransportResult.success(); } catch (Exception e) { - return TransportResult.error(-1); + return TransportResult.error(); } } diff --git a/sentry-core/src/main/java/io/sentry/core/transport/TransportResult.java b/sentry-core/src/main/java/io/sentry/core/transport/TransportResult.java index 3ed1cf48e..845eb38b4 100644 --- a/sentry-core/src/main/java/io/sentry/core/transport/TransportResult.java +++ b/sentry-core/src/main/java/io/sentry/core/transport/TransportResult.java @@ -1,12 +1,11 @@ package io.sentry.core.transport; -import io.sentry.core.SentryEvent; import org.jetbrains.annotations.NotNull; /** - * A result of {@link ITransport#send(SentryEvent)}. Note that this class is intentionally not - * subclassable and has only two factory methods to capture the 2 possible states - success or - * error. + * A result of {@link ITransport#send(io.sentry.core.SentryEnvelope)}. Note that this class is + * intentionally not subclassable and has only two factory methods to capture the 2 possible states + * - success or error. */ public abstract class TransportResult { diff --git a/sentry-core/src/test/java/io/sentry/core/transport/AsyncConnectionTest.kt b/sentry-core/src/test/java/io/sentry/core/transport/AsyncConnectionTest.kt index ae96c3cec..2b7b8dc05 100644 --- a/sentry-core/src/test/java/io/sentry/core/transport/AsyncConnectionTest.kt +++ b/sentry-core/src/test/java/io/sentry/core/transport/AsyncConnectionTest.kt @@ -57,7 +57,7 @@ class AsyncConnectionTest { // given val ev = mock() whenever(fixture.transportGate.isConnected).thenReturn(true) - whenever(fixture.transport.send(any())).thenReturn(TransportResult.success()) + whenever(fixture.transport.send(any())).thenReturn(TransportResult.success()) // when fixture.getSUT().send(ev) @@ -68,7 +68,9 @@ class AsyncConnectionTest { // because storeBeforeSend is enabled by default order.verify(fixture.eventCache).store(eq(ev)) - order.verify(fixture.transport).send(eq(ev)) + order.verify(fixture.transport).send(check { + assertEquals(ev.eventId, it.header.eventId) + }) order.verify(fixture.eventCache).discard(eq(ev)) } @@ -125,7 +127,7 @@ class AsyncConnectionTest { // given val ev = mock() whenever(fixture.transportGate.isConnected).thenReturn(true) - whenever(fixture.transport.send(any())).thenReturn(TransportResult.error(500)) + whenever(fixture.transport.send(any())).thenReturn(TransportResult.error(500)) // when try { @@ -140,7 +142,9 @@ class AsyncConnectionTest { // because storeBeforeSend is enabled by default order.verify(fixture.eventCache).store(eq(ev)) - order.verify(fixture.transport).send(eq(ev)) + order.verify(fixture.transport).send(check { + assertEquals(ev.eventId, it.header.eventId) + }) verify(fixture.eventCache, never()).discard(any()) } @@ -173,7 +177,7 @@ class AsyncConnectionTest { // given val ev = mock() whenever(fixture.transportGate.isConnected).thenReturn(true) - whenever(fixture.transport.send(any())).thenThrow(IOException()) + whenever(fixture.transport.send(any())).thenThrow(IOException()) // when try { @@ -184,7 +188,9 @@ class AsyncConnectionTest { // then val order = inOrder(fixture.transport, fixture.eventCache) - order.verify(fixture.transport).send(eq(ev)) + order.verify(fixture.transport).send(check { + assertEquals(ev.eventId, it.header.eventId) + }) verify(fixture.eventCache, never()).discard(any()) } diff --git a/sentry-core/src/test/java/io/sentry/core/transport/HttpTransportTest.kt b/sentry-core/src/test/java/io/sentry/core/transport/HttpTransportTest.kt index 50f678753..2235747db 100644 --- a/sentry-core/src/test/java/io/sentry/core/transport/HttpTransportTest.kt +++ b/sentry-core/src/test/java/io/sentry/core/transport/HttpTransportTest.kt @@ -45,11 +45,7 @@ class HttpTransportTest { options.proxy = proxy return object : HttpTransport(options, requestUpdater, connectionTimeout, readTimeout, bypassSecurity, dsn, currentDateProvider) { - override fun open(proxy: Proxy?): HttpURLConnection { - return connection - } - - override fun open(url: URL, proxy: Proxy?): HttpURLConnection { + override fun open(): HttpURLConnection { return connection } } @@ -58,19 +54,6 @@ class HttpTransportTest { private val fixture = Fixture() - @Test - fun `test serializes event`() { - val transport = fixture.getSUT() - whenever(fixture.connection.responseCode).thenReturn(200) - - val event = SentryEvent() - - val result = transport.send(event) - - verify(fixture.serializer).serialize(eq(event), any()) - assertTrue(result.isSuccess) - } - @Test fun `test serializes envelope`() { val transport = fixture.getSUT() @@ -84,24 +67,6 @@ class HttpTransportTest { assertTrue(result.isSuccess) } - @Test - fun `uses Retry-After header if X-Sentry-Rate-Limit is not set when sending an event`() { - val transport = fixture.getSUT() - - throwOnEventSerialize() - whenever(fixture.connection.getHeaderField(eq("Retry-After"))).thenReturn("30") - whenever(fixture.connection.responseCode).thenReturn(429) - whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0) - - val event = SentryEvent() - - val result = transport.send(event) - - verify(fixture.serializer).serialize(eq(event), any()) - assertFalse(result.isSuccess) - assertTrue(transport.isRetryAfter("event")) - } - @Test fun `uses Retry-After header if X-Sentry-Rate-Limit is not set when sending an envelope`() { val transport = fixture.getSUT() @@ -120,22 +85,6 @@ class HttpTransportTest { assertTrue(transport.isRetryAfter("session")) } - @Test - fun `passes on the response code on error when sending an event`() { - val transport = fixture.getSUT() - - throwOnEventSerialize() - whenever(fixture.connection.responseCode).thenReturn(1234) - - val event = SentryEvent() - - val result = transport.send(event) - - verify(fixture.serializer).serialize(eq(event), any()) - assertFalse(result.isSuccess) - assertEquals(1234, result.responseCode) - } - @Test fun `passes on the response code on error when sending an envelope`() { val transport = fixture.getSUT() @@ -152,23 +101,6 @@ class HttpTransportTest { assertEquals(1234, result.responseCode) } - @Test - fun `uses the default retry interval if there is no Retry-After header when sending an event`() { - val transport = fixture.getSUT() - - throwOnEventSerialize() - whenever(fixture.connection.responseCode).thenReturn(429) - whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0) - - val event = SentryEvent() - - val result = transport.send(event) - - verify(fixture.serializer).serialize(eq(event), any()) - assertFalse(result.isSuccess) - assertTrue(transport.isRetryAfter("event")) - } - @Test fun `uses the default retry interval if there is no Retry-After header when sending an envelope`() { val transport = fixture.getSUT() @@ -186,22 +118,6 @@ class HttpTransportTest { assertTrue(transport.isRetryAfter("session")) } - @Test - fun `failure to get response code doesn't break sending an event`() { - val transport = fixture.getSUT() - - throwOnEventSerialize() - whenever(fixture.connection.responseCode).thenThrow(IOException()) - - val event = SentryEvent() - - val result = transport.send(event) - - verify(fixture.serializer).serialize(eq(event), any()) - assertFalse(result.isSuccess) - assertEquals(-1, result.responseCode) - } - @Test fun `failure to get response code doesn't break sending an envelope`() { val transport = fixture.getSUT() @@ -228,11 +144,10 @@ class HttpTransportTest { .thenReturn("50:transaction:key, 2700:default;error;security:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0) - val event = SentryEvent() - - val result = transport.send(event) + val envelope = createEnvelope() + val result = transport.send(envelope) - verify(fixture.serializer).serialize(eq(event), any()) + verify(fixture.serializer).serialize(eq(envelope), any()) assertFalse(result.isSuccess) assertTrue(transport.isRetryAfter("event")) } @@ -246,11 +161,10 @@ class HttpTransportTest { .thenReturn("50:transaction:key, 1:default;error;security:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001) - val event = SentryEvent() - - val result = transport.send(event) + val envelope = createEnvelope() + val result = transport.send(envelope) - verify(fixture.serializer).serialize(eq(event), any()) + verify(fixture.serializer).serialize(eq(envelope), any()) assertFalse(result.isSuccess) assertFalse(transport.isRetryAfter("event")) } @@ -264,9 +178,7 @@ class HttpTransportTest { .thenReturn("50:transaction:key, 2700:default;error;security:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0) - val event = SentryEvent() - - transport.send(event) + transport.send(createEnvelope()) assertTrue(transport.isRetryAfter("transaction")) assertTrue(transport.isRetryAfter("event")) @@ -281,9 +193,8 @@ class HttpTransportTest { .thenReturn("1:transaction:key, 1:default;error;security:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001) - val event = SentryEvent() + transport.send(createEnvelope()) - transport.send(event) assertFalse(transport.isRetryAfter("transaction")) assertFalse(transport.isRetryAfter("event")) } @@ -297,9 +208,7 @@ class HttpTransportTest { .thenReturn("50::key") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0) - val event = SentryEvent() - - transport.send(event) + transport.send(createEnvelope()) assertTrue(transport.isRetryAfter("event")) } @@ -313,9 +222,7 @@ class HttpTransportTest { .thenReturn("60:default;foobar;error;security:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0) - val event = SentryEvent() - - transport.send(event) + transport.send(createEnvelope()) assertFalse(transport.isRetryAfter("foobar")) } @@ -328,9 +235,7 @@ class HttpTransportTest { .thenReturn("1::key, 60:default;error;security:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001) - val event = SentryEvent() - - transport.send(event) + transport.send(createEnvelope()) assertTrue(transport.isRetryAfter("event")) } @@ -343,9 +248,7 @@ class HttpTransportTest { .thenReturn("60:error:key, 1:error:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001) - val event = SentryEvent() - - transport.send(event) + transport.send(createEnvelope()) assertTrue(transport.isRetryAfter("event")) } @@ -358,9 +261,7 @@ class HttpTransportTest { .thenReturn("1:error:key, 5:error:organization") whenever(fixture.currentDateProvider.currentTimeMillis).thenReturn(0, 0, 1001) - val event = SentryEvent() - - transport.send(event) + transport.send(createEnvelope()) assertTrue(transport.isRetryAfter("event")) } @@ -376,4 +277,8 @@ class HttpTransportTest { private fun throwOnEnvelopeSerialize() { whenever(fixture.serializer.serialize(any(), any())).thenThrow(IOException()) } + + private fun createEnvelope(event: SentryEvent = SentryEvent()): SentryEnvelope { + return SentryEnvelope.fromEvent(fixture.serializer, event, null) + } } diff --git a/sentry-core/src/test/java/io/sentry/core/transport/StdoutTransportTest.kt b/sentry-core/src/test/java/io/sentry/core/transport/StdoutTransportTest.kt index fc2fe1f1d..5b742a14b 100644 --- a/sentry-core/src/test/java/io/sentry/core/transport/StdoutTransportTest.kt +++ b/sentry-core/src/test/java/io/sentry/core/transport/StdoutTransportTest.kt @@ -5,8 +5,8 @@ import com.nhaarman.mockitokotlin2.eq import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import io.sentry.core.ISerializer +import io.sentry.core.SentryEnvelope import io.sentry.core.SentryEvent -import io.sentry.core.SentryOptions import kotlin.test.Test import kotlin.test.assertTrue @@ -15,9 +15,6 @@ class StdoutTransportTest { val serializer = mock() fun getSUT(): ITransport { - val options = SentryOptions() - options.setSerializer(serializer) - return StdoutTransport(serializer) } } @@ -25,13 +22,14 @@ class StdoutTransportTest { private val fixture = Fixture() @Test - fun `test serializes event`() { + fun `test serializes envelope`() { val transport = fixture.getSUT() val event = SentryEvent() + val envelope = SentryEnvelope.fromEvent(fixture.serializer, event, null) - val result = transport.send(event) + val result = transport.send(envelope) - verify(fixture.serializer).serialize(eq(event), any()) + verify(fixture.serializer).serialize(eq(envelope), any()) assertTrue(result.isSuccess) } }