io.sentry.appengine.connection.AppEngineAsyncConnection Maven / Gradle / Ivy
Show all versions of sentry-appengine Show documentation
package io.sentry.appengine.connection;
import io.sentry.connection.EventSendCallback;
import com.google.appengine.api.taskqueue.DeferredTask;
import com.google.appengine.api.taskqueue.Queue;
import com.google.appengine.api.taskqueue.QueueFactory;
import io.sentry.connection.Connection;
import io.sentry.environment.SentryEnvironment;
import io.sentry.event.Event;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static com.google.appengine.api.taskqueue.DeferredTaskContext.setDoNotRetry;
import static com.google.appengine.api.taskqueue.TaskOptions.Builder.withPayload;
/**
* Asynchronous connections that can be used within Google App Engine.
*
* Instead of synchronously sending each event, use a the App Engine queue system to establish the connection
* and send the event.
*
* Google App Engine serialises the tasks before queuing them, to keep a link between the task and the
* {@link AppEngineAsyncConnection} associated, a register of the instances of {@code AppEngineAsyncConnection} is
* kept in {@link #APP_ENGINE_ASYNC_CONNECTIONS}.
* This register is populated when a new instance of {@code AppEngineAsyncConnection} is created and the connection
* is removed from the register when it has been closed with {@link #close()}.
* The register works based on identifier defined by the user. There is no ID conflict handling, the user is expected
* to manage the uniqueness of those ID.
*/
public class AppEngineAsyncConnection implements Connection {
private static final Logger logger = LoggerFactory.getLogger(AppEngineAsyncConnection.class);
// visible for testing
static final Map APP_ENGINE_ASYNC_CONNECTIONS = new HashMap<>();
/**
* Identifier of the async connection.
*/
private final String id;
/**
* Connection used to actually send the events.
*/
private final Connection actualConnection;
/**
* Queue used to send deferred tasks.
*/
private Queue queue = QueueFactory.getDefaultQueue();
/**
* Boolean used to check whether the connection is still open or not.
*/
private boolean closed;
/**
* Creates a connection which will rely on an executor to send events.
*
* Will propagate the {@link #close()} operation.
*
* @param id Identifier of the connection shared across all the instances of the application.
* @param actualConnection Connection used to send the events.
*/
public AppEngineAsyncConnection(String id, Connection actualConnection) {
this.actualConnection = actualConnection;
this.id = id;
APP_ENGINE_ASYNC_CONNECTIONS.put(id, this);
}
/**
* {@inheritDoc}
*
* The event will be added to a queue and will be handled by a separate {@code Thread} later on.
*/
@Override
public void send(Event event) {
if (!closed) {
queue.add(withPayload(new EventSubmitter(id, event)));
}
}
@Override
public void addEventSendCallback(EventSendCallback eventSendCallback) {
actualConnection.addEventSendCallback(eventSendCallback);
}
/**
* {@inheritDoc}.
*
* Closing the {@link AppEngineAsyncConnection} will gracefully remove every task created earlier from the queue.
*/
@Override
public void close() throws IOException {
logger.debug("Gracefully stopping Sentry tasks.");
closed = true;
actualConnection.close();
APP_ENGINE_ASYNC_CONNECTIONS.remove(id);
}
/**
* Set the queue used to send EventSubmitter tasks.
*
* @param queueName name of the queue to use.
*/
public void setQueue(String queueName) {
this.queue = QueueFactory.getQueue(queueName);
}
/**
* Gets the queue that the events are sent to.
* @return the App Engine Queue used by this connection
*/
public String getQueue() {
return queue == null ? null : queue.getQueueName();
}
/**
* Simple DeferredTask using the {@link #send(Event)} method of the {@link #actualConnection}.
*/
// visible for testing
static final class EventSubmitter implements DeferredTask {
private final String connectionId;
private final Event event;
private EventSubmitter(String connectionId, Event event) {
this.connectionId = connectionId;
this.event = event;
}
// visible for testing
String getConnectionId() {
return connectionId;
}
// visible for testing
Event getEvent() {
return event;
}
@Override
public void run() {
setDoNotRetry(true);
SentryEnvironment.startManagingThread();
try {
// The current thread is managed by sentry
AppEngineAsyncConnection connection = APP_ENGINE_ASYNC_CONNECTIONS.get(connectionId);
if (connection == null) {
logger.warn("Couldn't find the AppEngineAsyncConnection identified by '{}'. "
+ "The connection has probably been closed.", connectionId);
return;
}
connection.actualConnection.send(event);
} catch (RuntimeException e) {
logger.error("An exception occurred while sending the event to Sentry.", e);
} finally {
SentryEnvironment.stopManagingThread();
}
}
}
}