// 
// Decompiled by Procyon v0.6.0
// 

package io.sentry;

import io.sentry.util.Random;
import io.sentry.util.SentryRandom;
import io.sentry.transport.RateLimiter;
import java.io.Closeable;
import java.util.Comparator;
import java.util.Collection;
import io.sentry.protocol.Contexts;
import java.util.Map;
import java.util.HashMap;
import io.sentry.protocol.FeatureFlags;
import io.sentry.util.JsonSerializationUtils;
import io.sentry.protocol.Feedback;
import io.sentry.util.CheckInUtils;
import java.util.Collections;
import io.sentry.protocol.DebugMeta;
import io.sentry.util.TracingUtils;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.TestOnly;
import io.sentry.hints.AbnormalExit;
import io.sentry.protocol.SentryTransaction;
import java.util.Iterator;
import java.util.ArrayList;
import io.sentry.hints.DiskFlushNotification;
import io.sentry.hints.TransactionEnd;
import java.util.List;
import io.sentry.exception.SentryEnvelopeException;
import java.io.IOException;
import io.sentry.hints.ApplyScopeData;
import io.sentry.hints.Cached;
import io.sentry.hints.Backfillable;
import io.sentry.util.EventSizeLimitingUtils;
import io.sentry.util.ErrorUtils;
import io.sentry.clientreport.DiscardReason;
import io.sentry.util.ExceptionUtils;
import io.sentry.protocol.SentryId;
import org.jetbrains.annotations.Nullable;
import io.sentry.util.HintUtils;
import io.sentry.logger.NoOpLoggerBatchProcessor;
import io.sentry.util.Objects;
import io.sentry.logger.ILoggerBatchProcessor;
import io.sentry.transport.ITransport;
import org.jetbrains.annotations.NotNull;

public final class SentryClient implements ISentryClient
{
    static final String SENTRY_PROTOCOL_VERSION = "7";
    private boolean enabled;
    @NotNull
    private final SentryOptions options;
    @NotNull
    private final ITransport transport;
    @NotNull
    private final SortBreadcrumbsByDate sortBreadcrumbsByDate;
    @NotNull
    private final ILoggerBatchProcessor loggerBatchProcessor;
    
    @Override
    public boolean isEnabled() {
        return this.enabled;
    }
    
    public SentryClient(@NotNull final SentryOptions options) {
        this.sortBreadcrumbsByDate = new SortBreadcrumbsByDate();
        this.options = Objects.requireNonNull(options, "SentryOptions is required.");
        this.enabled = true;
        ITransportFactory transportFactory = options.getTransportFactory();
        if (transportFactory instanceof NoOpTransportFactory) {
            transportFactory = new AsyncHttpTransportFactory();
            options.setTransportFactory(transportFactory);
        }
        final RequestDetailsResolver requestDetailsResolver = new RequestDetailsResolver(options);
        this.transport = transportFactory.create(options, requestDetailsResolver.resolve());
        if (options.getLogs().isEnabled()) {
            this.loggerBatchProcessor = options.getLogs().getLoggerBatchProcessorFactory().create(options, this);
        }
        else {
            this.loggerBatchProcessor = NoOpLoggerBatchProcessor.getInstance();
        }
    }
    
    private boolean shouldApplyScopeData(@NotNull final SentryBaseEvent event, @NotNull final Hint hint) {
        if (HintUtils.shouldApplyScopeData(hint)) {
            return true;
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Event was cached so not applying scope: %s", event.getEventId());
        return false;
    }
    
    private boolean shouldApplyScopeData(@NotNull final CheckIn event, @NotNull final Hint hint) {
        if (HintUtils.shouldApplyScopeData(hint)) {
            return true;
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Check-in was cached so not applying scope: %s", event.getCheckInId());
        return false;
    }
    
    @NotNull
    @Override
    public SentryId captureEvent(@NotNull SentryEvent event, @Nullable final IScope scope, @Nullable Hint hint) {
        Objects.requireNonNull(event, "SentryEvent is required.");
        if (hint == null) {
            hint = new Hint();
        }
        if (this.shouldApplyScopeData(event, hint)) {
            this.addScopeAttachmentsToHint(scope, hint);
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Capturing event: %s", event.getEventId());
        if (event != null) {
            final Throwable eventThrowable = event.getThrowable();
            if (eventThrowable != null && ExceptionUtils.isIgnored(this.options.getIgnoredExceptionsForType(), eventThrowable)) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event was dropped as the exception %s is ignored", eventThrowable.getClass());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error);
                return SentryId.EMPTY_ID;
            }
            if (ErrorUtils.isIgnored(this.options.getIgnoredErrors(), event)) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event was dropped as it matched a string/pattern in ignoredErrors", event.getMessage());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error);
                return SentryId.EMPTY_ID;
            }
        }
        if (this.shouldApplyScopeData(event, hint)) {
            event = this.applyScope(event, scope, hint);
            if (event == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by applyScope", new Object[0]);
                return SentryId.EMPTY_ID;
            }
        }
        event = this.processEvent(event, hint, this.options.getEventProcessors());
        if (event != null) {
            event = this.executeBeforeSend(event, hint);
            if (event == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by beforeSend", new Object[0]);
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Error);
            }
        }
        if (event != null) {
            event = EventSizeLimitingUtils.limitEventSize(event, hint, this.options);
        }
        if (event == null) {
            return SentryId.EMPTY_ID;
        }
        final Session sessionBeforeUpdate = (scope != null) ? scope.withSession(session -> {}) : null;
        Session session = null;
        if (event != null) {
            if (sessionBeforeUpdate == null || !sessionBeforeUpdate.isTerminated()) {
                session = this.updateSessionData(event, hint, scope);
            }
            if (!this.sample()) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event %s was dropped due to sampling decision.", event.getEventId());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.SAMPLE_RATE, DataCategory.Error);
                event = null;
            }
        }
        final boolean shouldSendSessionUpdate = this.shouldSendSessionUpdateForDroppedEvent(sessionBeforeUpdate, session);
        if (event == null && !shouldSendSessionUpdate) {
            this.options.getLogger().log(SentryLevel.DEBUG, "Not sending session update for dropped event as it did not cause the session health to change.", new Object[0]);
            return SentryId.EMPTY_ID;
        }
        SentryId sentryId = SentryId.EMPTY_ID;
        if (event != null && event.getEventId() != null) {
            sentryId = event.getEventId();
        }
        final boolean isBackfillable = HintUtils.hasType(hint, Backfillable.class);
        final boolean isCached = HintUtils.hasType(hint, Cached.class) && !HintUtils.hasType(hint, ApplyScopeData.class);
        if (event != null && !isBackfillable && !isCached && (event.isErrored() || event.isCrashed())) {
            this.options.getReplayController().captureReplay(event.isCrashed());
        }
        try {
            final TraceContext traceContext = this.getTraceContext(scope, hint, event);
            final boolean shouldSendAttachments = event != null;
            final List<Attachment> attachments = shouldSendAttachments ? this.getAttachments(hint) : null;
            final SentryEnvelope envelope = this.buildEnvelope(event, attachments, session, traceContext, null);
            hint.clear();
            if (envelope != null) {
                sentryId = this.sendEnvelope(envelope, hint);
            }
        }
        catch (final IOException | SentryEnvelopeException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing event %s failed.", sentryId);
            sentryId = SentryId.EMPTY_ID;
        }
        if (scope != null) {
            this.finalizeTransaction(scope, hint);
        }
        return sentryId;
    }
    
    private void finalizeTransaction(@NotNull final IScope scope, @NotNull final Hint hint) {
        final ITransaction transaction = scope.getTransaction();
        if (transaction != null && HintUtils.hasType(hint, TransactionEnd.class)) {
            final Object sentrySdkHint = HintUtils.getSentrySdkHint(hint);
            if (sentrySdkHint instanceof DiskFlushNotification) {
                ((DiskFlushNotification)sentrySdkHint).setFlushable(transaction.getEventId());
                transaction.forceFinish(SpanStatus.ABORTED, false, hint);
            }
            else {
                transaction.forceFinish(SpanStatus.ABORTED, false, null);
            }
        }
    }
    
    @NotNull
    @Override
    public SentryId captureReplayEvent(@NotNull SentryReplayEvent event, @Nullable final IScope scope, @Nullable Hint hint) {
        Objects.requireNonNull(event, "SessionReplay is required.");
        if (hint == null) {
            hint = new Hint();
        }
        if (this.shouldApplyScopeData(event, hint)) {
            this.applyScope(event, scope);
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Capturing session replay: %s", event.getEventId());
        SentryId sentryId = SentryId.EMPTY_ID;
        if (event.getEventId() != null) {
            sentryId = event.getEventId();
        }
        event = this.processReplayEvent(event, hint, this.options.getEventProcessors());
        if (event != null) {
            event = this.executeBeforeSendReplay(event, hint);
            if (event == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by beforeSendReplay", new Object[0]);
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Replay);
            }
        }
        if (event == null) {
            return SentryId.EMPTY_ID;
        }
        try {
            final TraceContext traceContext = this.getTraceContext(scope, hint, event, null);
            final boolean cleanupReplayFolder = HintUtils.hasType(hint, Backfillable.class);
            final SentryEnvelope envelope = this.buildEnvelope(event, hint.getReplayRecording(), traceContext, cleanupReplayFolder);
            hint.clear();
            this.transport.send(envelope, hint);
        }
        catch (final IOException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing event %s failed.", sentryId);
            sentryId = SentryId.EMPTY_ID;
        }
        return sentryId;
    }
    
    private void addScopeAttachmentsToHint(@Nullable final IScope scope, @NotNull final Hint hint) {
        if (scope != null) {
            hint.addAttachments(scope.getAttachments());
        }
    }
    
    private boolean shouldSendSessionUpdateForDroppedEvent(@Nullable final Session sessionBeforeUpdate, @Nullable final Session sessionAfterUpdate) {
        if (sessionAfterUpdate == null) {
            return false;
        }
        if (sessionBeforeUpdate == null) {
            return true;
        }
        final boolean didSessionMoveToCrashedState = sessionAfterUpdate.getStatus() == Session.State.Crashed && sessionBeforeUpdate.getStatus() != Session.State.Crashed;
        if (didSessionMoveToCrashedState) {
            return true;
        }
        final boolean didSessionMoveToErroredState = sessionAfterUpdate.errorCount() > 0 && sessionBeforeUpdate.errorCount() <= 0;
        return didSessionMoveToErroredState;
    }
    
    @Nullable
    private List<Attachment> getAttachments(@NotNull final Hint hint) {
        final List<Attachment> attachments = hint.getAttachments();
        final Attachment screenshot = hint.getScreenshot();
        if (screenshot != null) {
            attachments.add(screenshot);
        }
        final Attachment viewHierarchy = hint.getViewHierarchy();
        if (viewHierarchy != null) {
            attachments.add(viewHierarchy);
        }
        final Attachment threadDump = hint.getThreadDump();
        if (threadDump != null) {
            attachments.add(threadDump);
        }
        return attachments;
    }
    
    @Nullable
    private SentryEnvelope buildEnvelope(@Nullable final SentryBaseEvent event, @Nullable final List<Attachment> attachments, @Nullable final Session session, @Nullable final TraceContext traceContext, @Nullable final ProfilingTraceData profilingTraceData) throws IOException, SentryEnvelopeException {
        SentryId sentryId = null;
        final List<SentryEnvelopeItem> envelopeItems = new ArrayList<SentryEnvelopeItem>();
        if (event != null) {
            final SentryEnvelopeItem eventItem = SentryEnvelopeItem.fromEvent(this.options.getSerializer(), event);
            envelopeItems.add(eventItem);
            sentryId = event.getEventId();
        }
        if (session != null) {
            final SentryEnvelopeItem sessionItem = SentryEnvelopeItem.fromSession(this.options.getSerializer(), session);
            envelopeItems.add(sessionItem);
        }
        if (profilingTraceData != null) {
            final SentryEnvelopeItem profilingTraceItem = SentryEnvelopeItem.fromProfilingTrace(profilingTraceData, this.options.getMaxTraceFileSize(), this.options.getSerializer());
            envelopeItems.add(profilingTraceItem);
            if (sentryId == null) {
                sentryId = new SentryId(profilingTraceData.getProfileId());
            }
        }
        if (attachments != null) {
            for (final Attachment attachment : attachments) {
                final SentryEnvelopeItem attachmentItem = SentryEnvelopeItem.fromAttachment(this.options.getSerializer(), this.options.getLogger(), attachment, this.options.getMaxAttachmentSize());
                envelopeItems.add(attachmentItem);
            }
        }
        if (!envelopeItems.isEmpty()) {
            final SentryEnvelopeHeader envelopeHeader = new SentryEnvelopeHeader(sentryId, this.options.getSdkVersion(), traceContext);
            return new SentryEnvelope(envelopeHeader, envelopeItems);
        }
        return null;
    }
    
    @Nullable
    private SentryEvent processEvent(@NotNull SentryEvent event, @NotNull final Hint hint, @NotNull final List<EventProcessor> eventProcessors) {
        for (final EventProcessor processor : eventProcessors) {
            try {
                final boolean isBackfillingProcessor = processor instanceof BackfillingEventProcessor;
                final boolean isBackfillable = HintUtils.hasType(hint, Backfillable.class);
                if (isBackfillable && isBackfillingProcessor) {
                    event = processor.process(event, hint);
                }
                else if (!isBackfillable && !isBackfillingProcessor) {
                    event = processor.process(event, hint);
                }
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, e, "An exception occurred while processing event by processor: %s", processor.getClass().getName());
            }
            if (event == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by a processor: %s", processor.getClass().getName());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Error);
                break;
            }
        }
        return event;
    }
    
    @Nullable
    private SentryLogEvent processLogEvent(@NotNull SentryLogEvent event, @NotNull final List<EventProcessor> eventProcessors) {
        for (final EventProcessor processor : eventProcessors) {
            try {
                event = processor.process(event);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, e, "An exception occurred while processing log event by processor: %s", processor.getClass().getName());
            }
            if (event == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Log event was dropped by a processor: %s", processor.getClass().getName());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.LogItem);
                break;
            }
        }
        return event;
    }
    
    @Nullable
    private SentryTransaction processTransaction(@NotNull SentryTransaction transaction, @NotNull final Hint hint, @NotNull final List<EventProcessor> eventProcessors) {
        for (final EventProcessor processor : eventProcessors) {
            final int spanCountBeforeProcessor = transaction.getSpans().size();
            try {
                transaction = processor.process(transaction, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, e, "An exception occurred while processing transaction by processor: %s", processor.getClass().getName());
            }
            final int spanCountAfterProcessor = (transaction == null) ? 0 : transaction.getSpans().size();
            if (transaction == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Transaction was dropped by a processor: %s", processor.getClass().getName());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Transaction);
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Span, spanCountBeforeProcessor + 1);
                break;
            }
            if (spanCountAfterProcessor >= spanCountBeforeProcessor) {
                continue;
            }
            final int droppedSpanCount = spanCountBeforeProcessor - spanCountAfterProcessor;
            this.options.getLogger().log(SentryLevel.DEBUG, "%d spans were dropped by a processor: %s", droppedSpanCount, processor.getClass().getName());
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Span, droppedSpanCount);
        }
        return transaction;
    }
    
    @Nullable
    private SentryReplayEvent processReplayEvent(@NotNull SentryReplayEvent replayEvent, @NotNull final Hint hint, @NotNull final List<EventProcessor> eventProcessors) {
        for (final EventProcessor processor : eventProcessors) {
            try {
                replayEvent = processor.process(replayEvent, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, e, "An exception occurred while processing replay event by processor: %s", processor.getClass().getName());
            }
            if (replayEvent == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Replay event was dropped by a processor: %s", processor.getClass().getName());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Replay);
                break;
            }
        }
        return replayEvent;
    }
    
    @Nullable
    private SentryEvent processFeedbackEvent(@NotNull SentryEvent feedbackEvent, @NotNull final Hint hint, @NotNull final List<EventProcessor> eventProcessors) {
        for (final EventProcessor processor : eventProcessors) {
            try {
                feedbackEvent = processor.process(feedbackEvent, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, e, "An exception occurred while processing feedback event by processor: %s", processor.getClass().getName());
            }
            if (feedbackEvent == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Feedback event was dropped by a processor: %s", processor.getClass().getName());
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Feedback);
                break;
            }
        }
        return feedbackEvent;
    }
    
    @Override
    public void captureUserFeedback(@NotNull final UserFeedback userFeedback) {
        Objects.requireNonNull(userFeedback, "SentryEvent is required.");
        if (SentryId.EMPTY_ID.equals(userFeedback.getEventId())) {
            this.options.getLogger().log(SentryLevel.WARNING, "Capturing userFeedback without a Sentry Id.", new Object[0]);
            return;
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Capturing userFeedback: %s", userFeedback.getEventId());
        try {
            final SentryEnvelope envelope = this.buildEnvelope(userFeedback);
            this.sendEnvelope(envelope, null);
        }
        catch (final IOException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing user feedback %s failed.", userFeedback.getEventId());
        }
    }
    
    @NotNull
    private SentryEnvelope buildEnvelope(@NotNull final UserFeedback userFeedback) {
        final List<SentryEnvelopeItem> envelopeItems = new ArrayList<SentryEnvelopeItem>();
        final SentryEnvelopeItem userFeedbackItem = SentryEnvelopeItem.fromUserFeedback(this.options.getSerializer(), userFeedback);
        envelopeItems.add(userFeedbackItem);
        final SentryEnvelopeHeader envelopeHeader = new SentryEnvelopeHeader(userFeedback.getEventId(), this.options.getSdkVersion());
        return new SentryEnvelope(envelopeHeader, envelopeItems);
    }
    
    @NotNull
    private SentryEnvelope buildEnvelope(@NotNull final CheckIn checkIn, @Nullable final TraceContext traceContext) {
        final List<SentryEnvelopeItem> envelopeItems = new ArrayList<SentryEnvelopeItem>();
        final SentryEnvelopeItem checkInItem = SentryEnvelopeItem.fromCheckIn(this.options.getSerializer(), checkIn);
        envelopeItems.add(checkInItem);
        final SentryEnvelopeHeader envelopeHeader = new SentryEnvelopeHeader(checkIn.getCheckInId(), this.options.getSdkVersion(), traceContext);
        return new SentryEnvelope(envelopeHeader, envelopeItems);
    }
    
    @NotNull
    private SentryEnvelope buildEnvelope(@NotNull final SentryLogEvents logEvents) {
        final List<SentryEnvelopeItem> envelopeItems = new ArrayList<SentryEnvelopeItem>();
        final SentryEnvelopeItem logItem = SentryEnvelopeItem.fromLogs(this.options.getSerializer(), logEvents);
        envelopeItems.add(logItem);
        final SentryEnvelopeHeader envelopeHeader = new SentryEnvelopeHeader(null, this.options.getSdkVersion(), null);
        return new SentryEnvelope(envelopeHeader, envelopeItems);
    }
    
    @NotNull
    private SentryEnvelope buildEnvelope(@NotNull final SentryReplayEvent event, @Nullable final ReplayRecording replayRecording, @Nullable final TraceContext traceContext, final boolean cleanupReplayFolder) {
        final List<SentryEnvelopeItem> envelopeItems = new ArrayList<SentryEnvelopeItem>();
        final SentryEnvelopeItem replayItem = SentryEnvelopeItem.fromReplay(this.options.getSerializer(), this.options.getLogger(), event, replayRecording, cleanupReplayFolder);
        envelopeItems.add(replayItem);
        final SentryId sentryId = event.getEventId();
        final SentryEnvelopeHeader envelopeHeader = new SentryEnvelopeHeader(sentryId, this.options.getSessionReplay().getSdkVersion(), traceContext);
        return new SentryEnvelope(envelopeHeader, envelopeItems);
    }
    
    @TestOnly
    @Nullable
    Session updateSessionData(@NotNull final SentryEvent event, @NotNull final Hint hint, @Nullable final IScope scope) {
        Session clonedSession = null;
        if (HintUtils.shouldApplyScopeData(hint)) {
            if (scope != null) {
                clonedSession = scope.withSession(session -> {
                    if (session != null) {
                        Session.State status = null;
                        if (event.isCrashed()) {
                            status = Session.State.Crashed;
                        }
                        boolean crashedOrErrored = false;
                        if (Session.State.Crashed == status || event.isErrored()) {
                            crashedOrErrored = true;
                        }
                        String userAgent = null;
                        if (event.getRequest() != null && event.getRequest().getHeaders() != null && event.getRequest().getHeaders().containsKey("user-agent")) {
                            userAgent = event.getRequest().getHeaders().get("user-agent");
                        }
                        final Object sentrySdkHint = HintUtils.getSentrySdkHint(hint);
                        String abnormalMechanism = null;
                        if (sentrySdkHint instanceof AbnormalExit) {
                            abnormalMechanism = ((AbnormalExit)sentrySdkHint).mechanism();
                            status = Session.State.Abnormal;
                        }
                        if (session.update(status, userAgent, crashedOrErrored, abnormalMechanism) && session.isTerminated()) {
                            session.end();
                        }
                    }
                    else {
                        this.options.getLogger().log(SentryLevel.INFO, "Session is null on scope.withSession", new Object[0]);
                    }
                    return;
                });
            }
            else {
                this.options.getLogger().log(SentryLevel.INFO, "Scope is null on client.captureEvent", new Object[0]);
            }
        }
        return clonedSession;
    }
    
    @ApiStatus.Internal
    @Override
    public void captureSession(@NotNull final Session session, @Nullable final Hint hint) {
        Objects.requireNonNull(session, "Session is required.");
        if (session.getRelease() == null || session.getRelease().isEmpty()) {
            this.options.getLogger().log(SentryLevel.WARNING, "Sessions can't be captured without setting a release.", new Object[0]);
            return;
        }
        SentryEnvelope envelope;
        try {
            envelope = SentryEnvelope.from(this.options.getSerializer(), session, this.options.getSdkVersion());
        }
        catch (final IOException e) {
            this.options.getLogger().log(SentryLevel.ERROR, "Failed to capture session.", e);
            return;
        }
        this.captureEnvelope(envelope, hint);
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public SentryId captureEnvelope(@NotNull final SentryEnvelope envelope, @Nullable Hint hint) {
        Objects.requireNonNull(envelope, "SentryEnvelope is required.");
        if (hint == null) {
            hint = new Hint();
        }
        try {
            hint.clear();
            return this.sendEnvelope(envelope, hint);
        }
        catch (final IOException e) {
            this.options.getLogger().log(SentryLevel.ERROR, "Failed to capture envelope.", e);
            return SentryId.EMPTY_ID;
        }
    }
    
    @NotNull
    private SentryId sendEnvelope(@NotNull final SentryEnvelope envelope, @Nullable final Hint hint) throws IOException {
        final SentryOptions.BeforeEnvelopeCallback beforeEnvelopeCallback = this.options.getBeforeEnvelopeCallback();
        if (beforeEnvelopeCallback != null) {
            try {
                beforeEnvelopeCallback.execute(envelope, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, "The BeforeEnvelope callback threw an exception.", e);
            }
        }
        SentryIntegrationPackageStorage.getInstance().checkForMixedVersions(this.options.getLogger());
        if (hint == null) {
            this.transport.send(envelope);
        }
        else {
            this.transport.send(envelope, hint);
        }
        final SentryId id = envelope.getHeader().getEventId();
        return (id != null) ? id : SentryId.EMPTY_ID;
    }
    
    @NotNull
    @Override
    public SentryId captureTransaction(@NotNull SentryTransaction transaction, @Nullable final TraceContext traceContext, @Nullable final IScope scope, @Nullable Hint hint, @Nullable final ProfilingTraceData profilingTraceData) {
        Objects.requireNonNull(transaction, "Transaction is required.");
        if (hint == null) {
            hint = new Hint();
        }
        if (this.shouldApplyScopeData(transaction, hint)) {
            this.addScopeAttachmentsToHint(scope, hint);
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Capturing transaction: %s", transaction.getEventId());
        if (TracingUtils.isIgnored(this.options.getIgnoredTransactions(), transaction.getTransaction())) {
            this.options.getLogger().log(SentryLevel.DEBUG, "Transaction was dropped as transaction name %s is ignored", transaction.getTransaction());
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Transaction);
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Span, transaction.getSpans().size() + 1);
            return SentryId.EMPTY_ID;
        }
        SentryId sentryId = SentryId.EMPTY_ID;
        if (transaction.getEventId() != null) {
            sentryId = transaction.getEventId();
        }
        if (this.shouldApplyScopeData(transaction, hint)) {
            transaction = this.applyScope(transaction, scope);
            if (transaction != null && scope != null) {
                transaction = this.processTransaction(transaction, hint, scope.getEventProcessors());
            }
            if (transaction == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Transaction was dropped by applyScope", new Object[0]);
            }
        }
        if (transaction != null) {
            transaction = this.processTransaction(transaction, hint, this.options.getEventProcessors());
        }
        if (transaction == null) {
            this.options.getLogger().log(SentryLevel.DEBUG, "Transaction was dropped by Event processors.", new Object[0]);
            return SentryId.EMPTY_ID;
        }
        final int spanCountBeforeCallback = transaction.getSpans().size();
        transaction = this.executeBeforeSendTransaction(transaction, hint);
        final int spanCountAfterCallback = (transaction == null) ? 0 : transaction.getSpans().size();
        if (transaction == null) {
            this.options.getLogger().log(SentryLevel.DEBUG, "Transaction was dropped by beforeSendTransaction.", new Object[0]);
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Transaction);
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Span, spanCountBeforeCallback + 1);
            return SentryId.EMPTY_ID;
        }
        if (spanCountAfterCallback < spanCountBeforeCallback) {
            final int droppedSpanCount = spanCountBeforeCallback - spanCountAfterCallback;
            this.options.getLogger().log(SentryLevel.DEBUG, "%d spans were dropped by beforeSendTransaction.", droppedSpanCount);
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Span, droppedSpanCount);
        }
        try {
            final SentryEnvelope envelope = this.buildEnvelope(transaction, this.filterForTransaction(this.getAttachments(hint)), null, traceContext, profilingTraceData);
            hint.clear();
            if (envelope != null) {
                sentryId = this.sendEnvelope(envelope, hint);
            }
        }
        catch (final IOException | SentryEnvelopeException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing transaction %s failed.", sentryId);
            sentryId = SentryId.EMPTY_ID;
        }
        return sentryId;
    }
    
    @ApiStatus.Internal
    @NotNull
    @Override
    public SentryId captureProfileChunk(@NotNull final ProfileChunk profileChunk, @Nullable final IScope scope) {
        Objects.requireNonNull(profileChunk, "profileChunk is required.");
        this.options.getLogger().log(SentryLevel.DEBUG, "Capturing profile chunk: %s", profileChunk.getChunkId());
        SentryId sentryId = profileChunk.getChunkId();
        final DebugMeta debugMeta = DebugMeta.buildDebugMeta(profileChunk.getDebugMeta(), this.options);
        if (debugMeta != null) {
            profileChunk.setDebugMeta(debugMeta);
        }
        try {
            final SentryEnvelope envelope = new SentryEnvelope(new SentryEnvelopeHeader(sentryId, this.options.getSdkVersion(), null), Collections.singletonList(SentryEnvelopeItem.fromProfileChunk(profileChunk, this.options.getSerializer(), this.options.getProfilerConverter())));
            sentryId = this.sendEnvelope(envelope, null);
        }
        catch (final IOException | SentryEnvelopeException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing profile chunk %s failed.", sentryId);
            sentryId = SentryId.EMPTY_ID;
        }
        return sentryId;
    }
    
    @NotNull
    @Override
    public SentryId captureCheckIn(@NotNull CheckIn checkIn, @Nullable final IScope scope, @Nullable Hint hint) {
        if (hint == null) {
            hint = new Hint();
        }
        if (checkIn.getEnvironment() == null) {
            checkIn.setEnvironment(this.options.getEnvironment());
        }
        if (checkIn.getRelease() == null) {
            checkIn.setRelease(this.options.getRelease());
        }
        if (this.shouldApplyScopeData(checkIn, hint)) {
            checkIn = this.applyScope(checkIn, scope);
        }
        if (CheckInUtils.isIgnored(this.options.getIgnoredCheckIns(), checkIn.getMonitorSlug())) {
            this.options.getLogger().log(SentryLevel.DEBUG, "Check-in was dropped as slug %s is ignored", checkIn.getMonitorSlug());
            this.options.getClientReportRecorder().recordLostEvent(DiscardReason.EVENT_PROCESSOR, DataCategory.Monitor);
            return SentryId.EMPTY_ID;
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Capturing check-in: %s", checkIn.getCheckInId());
        SentryId sentryId = checkIn.getCheckInId();
        try {
            final TraceContext traceContext = this.getTraceContext(scope, hint, null);
            final SentryEnvelope envelope = this.buildEnvelope(checkIn, traceContext);
            hint.clear();
            sentryId = this.sendEnvelope(envelope, hint);
        }
        catch (final IOException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing check-in %s failed.", sentryId);
            sentryId = SentryId.EMPTY_ID;
        }
        return sentryId;
    }
    
    @NotNull
    @Override
    public SentryId captureFeedback(@NotNull final Feedback feedback, @Nullable Hint hint, @NotNull final IScope scope) {
        SentryEvent event = new SentryEvent();
        event.getContexts().setFeedback(feedback);
        if (hint == null) {
            hint = new Hint();
        }
        if (feedback.getUrl() == null) {
            feedback.setUrl(scope.getScreen());
        }
        this.options.getLogger().log(SentryLevel.DEBUG, "Capturing feedback: %s", event.getEventId());
        if (this.shouldApplyScopeData(event, hint)) {
            event = this.applyFeedbackScope(event, scope, hint);
            if (event == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Feedback was dropped by applyScope", new Object[0]);
                return SentryId.EMPTY_ID;
            }
        }
        event = this.processFeedbackEvent(event, hint, this.options.getEventProcessors());
        if (event != null) {
            event = this.executeBeforeSendFeedback(event, hint);
            if (event == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Event was dropped by beforeSend", new Object[0]);
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.Feedback);
            }
        }
        if (event == null) {
            return SentryId.EMPTY_ID;
        }
        SentryId sentryId = SentryId.EMPTY_ID;
        if (event.getEventId() != null) {
            sentryId = event.getEventId();
        }
        if (feedback.getReplayId() == null) {
            this.options.getReplayController().captureReplay(false);
            final SentryId replayId = scope.getReplayId();
            if (!replayId.equals(SentryId.EMPTY_ID)) {
                feedback.setReplayId(replayId);
            }
        }
        try {
            final TraceContext traceContext = this.getTraceContext(scope, hint, event);
            final List<Attachment> attachments = this.getAttachments(hint);
            final SentryEnvelope envelope = this.buildEnvelope(event, attachments, null, traceContext, null);
            hint.clear();
            if (envelope != null) {
                sentryId = this.sendEnvelope(envelope, hint);
            }
        }
        catch (final IOException | SentryEnvelopeException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing feedback %s failed.", sentryId);
            sentryId = SentryId.EMPTY_ID;
        }
        return sentryId;
    }
    
    @Nullable
    private TraceContext getTraceContext(@Nullable final IScope scope, @NotNull final Hint hint, @Nullable final SentryEvent event) {
        return this.getTraceContext(scope, hint, event, (event != null) ? event.getTransaction() : null);
    }
    
    @Nullable
    private TraceContext getTraceContext(@Nullable final IScope scope, @NotNull final Hint hint, @Nullable final SentryBaseEvent event, @Nullable final String txn) {
        TraceContext traceContext = null;
        final boolean isBackfillable = HintUtils.hasType(hint, Backfillable.class);
        if (isBackfillable) {
            if (event != null) {
                final Baggage baggage = Baggage.fromEvent(event, txn, this.options);
                traceContext = baggage.toTraceContext();
            }
        }
        else if (scope != null) {
            final ITransaction transaction = scope.getTransaction();
            if (transaction != null) {
                traceContext = transaction.traceContext();
            }
            else {
                final PropagationContext propagationContext = TracingUtils.maybeUpdateBaggage(scope, this.options);
                traceContext = propagationContext.traceContext();
            }
        }
        return traceContext;
    }
    
    @ApiStatus.Experimental
    @Override
    public void captureLog(@Nullable SentryLogEvent logEvent, @Nullable final IScope scope) {
        if (logEvent != null && scope != null) {
            logEvent = this.processLogEvent(logEvent, scope.getEventProcessors());
            if (logEvent == null) {
                return;
            }
        }
        if (logEvent != null) {
            logEvent = this.processLogEvent(logEvent, this.options.getEventProcessors());
            if (logEvent == null) {
                return;
            }
        }
        if (logEvent != null) {
            final SentryLogEvent tmpLogEvent = logEvent;
            logEvent = this.executeBeforeSendLog(logEvent);
            if (logEvent == null) {
                this.options.getLogger().log(SentryLevel.DEBUG, "Log Event was dropped by beforeSendLog", new Object[0]);
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.LogItem);
                final long logEventNumberOfBytes = JsonSerializationUtils.byteSizeOf(this.options.getSerializer(), this.options.getLogger(), tmpLogEvent);
                this.options.getClientReportRecorder().recordLostEvent(DiscardReason.BEFORE_SEND, DataCategory.LogByte, logEventNumberOfBytes);
                return;
            }
            this.loggerBatchProcessor.add(logEvent);
        }
    }
    
    @ApiStatus.Internal
    @Override
    public void captureBatchedLogEvents(@NotNull final SentryLogEvents logEvents) {
        try {
            final SentryEnvelope envelope = this.buildEnvelope(logEvents);
            this.sendEnvelope(envelope, null);
        }
        catch (final IOException e) {
            this.options.getLogger().log(SentryLevel.WARNING, e, "Capturing log failed.", new Object[0]);
        }
    }
    
    @Nullable
    private List<Attachment> filterForTransaction(@Nullable final List<Attachment> attachments) {
        if (attachments == null) {
            return null;
        }
        final List<Attachment> attachmentsToSend = new ArrayList<Attachment>();
        for (final Attachment attachment : attachments) {
            if (attachment.isAddToTransactions()) {
                attachmentsToSend.add(attachment);
            }
        }
        return attachmentsToSend;
    }
    
    @Nullable
    private SentryEvent applyScope(@NotNull SentryEvent event, @Nullable final IScope scope, @NotNull final Hint hint) {
        if (scope != null) {
            this.applyScope(event, scope);
            if (event.getTransaction() == null) {
                event.setTransaction(scope.getTransactionName());
            }
            if (event.getFingerprints() == null) {
                event.setFingerprints(scope.getFingerprint());
            }
            if (scope.getLevel() != null) {
                event.setLevel(scope.getLevel());
            }
            final ISpan span = scope.getSpan();
            if (event.getContexts().getTrace() == null) {
                if (span == null) {
                    event.getContexts().setTrace(TransactionContext.fromPropagationContext(scope.getPropagationContext()));
                }
                else {
                    event.getContexts().setTrace(span.getSpanContext());
                }
            }
            if (event.getContexts().getFeatureFlags() == null) {
                final FeatureFlags featureFlags = scope.getFeatureFlags();
                if (featureFlags != null) {
                    event.getContexts().setFeatureFlags(featureFlags);
                }
            }
            event = this.processEvent(event, hint, scope.getEventProcessors());
        }
        return event;
    }
    
    @Nullable
    private SentryEvent applyFeedbackScope(@NotNull SentryEvent event, @NotNull final IScope scope, @NotNull final Hint hint) {
        if (event.getUser() == null) {
            event.setUser(scope.getUser());
        }
        if (event.getTags() == null) {
            event.setTags(new HashMap<String, String>(scope.getTags()));
        }
        else {
            for (final Map.Entry<String, String> item : scope.getTags().entrySet()) {
                if (!event.getTags().containsKey(item.getKey())) {
                    event.getTags().put(item.getKey(), item.getValue());
                }
            }
        }
        final Contexts contexts = event.getContexts();
        for (final Map.Entry<String, Object> entry : new Contexts(scope.getContexts()).entrySet()) {
            if (!contexts.containsKey(entry.getKey())) {
                contexts.put(entry.getKey(), entry.getValue());
            }
        }
        final ISpan span = scope.getSpan();
        if (event.getContexts().getTrace() == null) {
            if (span == null) {
                event.getContexts().setTrace(TransactionContext.fromPropagationContext(scope.getPropagationContext()));
            }
            else {
                event.getContexts().setTrace(span.getSpanContext());
            }
        }
        event = this.processFeedbackEvent(event, hint, scope.getEventProcessors());
        return event;
    }
    
    @NotNull
    private CheckIn applyScope(@NotNull final CheckIn checkIn, @Nullable final IScope scope) {
        if (scope != null) {
            final ISpan span = scope.getSpan();
            if (checkIn.getContexts().getTrace() == null) {
                if (span == null) {
                    checkIn.getContexts().setTrace(TransactionContext.fromPropagationContext(scope.getPropagationContext()));
                }
                else {
                    checkIn.getContexts().setTrace(span.getSpanContext());
                }
            }
        }
        return checkIn;
    }
    
    @NotNull
    private SentryReplayEvent applyScope(@NotNull final SentryReplayEvent replayEvent, @Nullable final IScope scope) {
        if (scope != null) {
            if (replayEvent.getRequest() == null) {
                replayEvent.setRequest(scope.getRequest());
            }
            if (replayEvent.getUser() == null) {
                replayEvent.setUser(scope.getUser());
            }
            if (replayEvent.getTags() == null) {
                replayEvent.setTags(new HashMap<String, String>(scope.getTags()));
            }
            else {
                for (final Map.Entry<String, String> item : scope.getTags().entrySet()) {
                    if (!replayEvent.getTags().containsKey(item.getKey())) {
                        replayEvent.getTags().put(item.getKey(), item.getValue());
                    }
                }
            }
            final Contexts contexts = replayEvent.getContexts();
            for (final Map.Entry<String, Object> entry : new Contexts(scope.getContexts()).entrySet()) {
                if (!contexts.containsKey(entry.getKey())) {
                    contexts.put(entry.getKey(), entry.getValue());
                }
            }
            final ISpan span = scope.getSpan();
            if (replayEvent.getContexts().getTrace() == null) {
                if (span == null) {
                    replayEvent.getContexts().setTrace(TransactionContext.fromPropagationContext(scope.getPropagationContext()));
                }
                else {
                    replayEvent.getContexts().setTrace(span.getSpanContext());
                }
            }
        }
        return replayEvent;
    }
    
    @NotNull
    private <T extends SentryBaseEvent> T applyScope(@NotNull final T sentryBaseEvent, @Nullable final IScope scope) {
        if (scope != null) {
            if (sentryBaseEvent.getRequest() == null) {
                sentryBaseEvent.setRequest(scope.getRequest());
            }
            if (sentryBaseEvent.getUser() == null) {
                sentryBaseEvent.setUser(scope.getUser());
            }
            if (sentryBaseEvent.getTags() == null) {
                sentryBaseEvent.setTags(new HashMap<String, String>(scope.getTags()));
            }
            else {
                for (final Map.Entry<String, String> item : scope.getTags().entrySet()) {
                    if (!sentryBaseEvent.getTags().containsKey(item.getKey())) {
                        sentryBaseEvent.getTags().put(item.getKey(), item.getValue());
                    }
                }
            }
            if (sentryBaseEvent.getBreadcrumbs() == null) {
                sentryBaseEvent.setBreadcrumbs(new ArrayList<Breadcrumb>(scope.getBreadcrumbs()));
            }
            else {
                this.sortBreadcrumbsByDate(sentryBaseEvent, scope.getBreadcrumbs());
            }
            if (sentryBaseEvent.getExtras() == null) {
                sentryBaseEvent.setExtras(new HashMap<String, Object>(scope.getExtras()));
            }
            else {
                for (final Map.Entry<String, Object> item2 : scope.getExtras().entrySet()) {
                    if (!sentryBaseEvent.getExtras().containsKey(item2.getKey())) {
                        sentryBaseEvent.getExtras().put(item2.getKey(), item2.getValue());
                    }
                }
            }
            final Contexts contexts = sentryBaseEvent.getContexts();
            for (final Map.Entry<String, Object> entry : new Contexts(scope.getContexts()).entrySet()) {
                if (!contexts.containsKey(entry.getKey())) {
                    contexts.put(entry.getKey(), entry.getValue());
                }
            }
        }
        return sentryBaseEvent;
    }
    
    private void sortBreadcrumbsByDate(@NotNull final SentryBaseEvent event, @NotNull final Collection<Breadcrumb> breadcrumbs) {
        final List<Breadcrumb> sortedBreadcrumbs = event.getBreadcrumbs();
        if (sortedBreadcrumbs != null && !breadcrumbs.isEmpty()) {
            sortedBreadcrumbs.addAll(breadcrumbs);
            Collections.sort(sortedBreadcrumbs, this.sortBreadcrumbsByDate);
        }
    }
    
    @Nullable
    private SentryEvent executeBeforeSend(@NotNull SentryEvent event, @NotNull final Hint hint) {
        final SentryOptions.BeforeSendCallback beforeSend = this.options.getBeforeSend();
        if (beforeSend != null) {
            try {
                event = beforeSend.execute(event, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, "The BeforeSend callback threw an exception. It will be added as breadcrumb and continue.", e);
                event = null;
            }
        }
        return event;
    }
    
    @Nullable
    private SentryTransaction executeBeforeSendTransaction(@NotNull SentryTransaction transaction, @NotNull final Hint hint) {
        final SentryOptions.BeforeSendTransactionCallback beforeSendTransaction = this.options.getBeforeSendTransaction();
        if (beforeSendTransaction != null) {
            try {
                transaction = beforeSendTransaction.execute(transaction, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, "The BeforeSendTransaction callback threw an exception. It will be added as breadcrumb and continue.", e);
                transaction = null;
            }
        }
        return transaction;
    }
    
    @Nullable
    private SentryEvent executeBeforeSendFeedback(@NotNull SentryEvent event, @NotNull final Hint hint) {
        final SentryOptions.BeforeSendCallback beforeSendFeedback = this.options.getBeforeSendFeedback();
        if (beforeSendFeedback != null) {
            try {
                event = beforeSendFeedback.execute(event, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, "The BeforeSendFeedback callback threw an exception.", e);
                event = null;
            }
        }
        return event;
    }
    
    @Nullable
    private SentryReplayEvent executeBeforeSendReplay(@NotNull SentryReplayEvent event, @NotNull final Hint hint) {
        final SentryOptions.BeforeSendReplayCallback beforeSendReplay = this.options.getBeforeSendReplay();
        if (beforeSendReplay != null) {
            try {
                event = beforeSendReplay.execute(event, hint);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, "The BeforeSendReplay callback threw an exception. It will be added as breadcrumb and continue.", e);
                event = null;
            }
        }
        return event;
    }
    
    @Nullable
    private SentryLogEvent executeBeforeSendLog(@NotNull SentryLogEvent event) {
        final SentryOptions.Logs.BeforeSendLogCallback beforeSendLog = this.options.getLogs().getBeforeSend();
        if (beforeSendLog != null) {
            try {
                event = beforeSendLog.execute(event);
            }
            catch (final Throwable e) {
                this.options.getLogger().log(SentryLevel.ERROR, "The BeforeSendLog callback threw an exception. Dropping log event.", e);
                event = null;
            }
        }
        return event;
    }
    
    @Override
    public void close() {
        this.close(false);
    }
    
    @Override
    public void close(final boolean isRestarting) {
        this.options.getLogger().log(SentryLevel.INFO, "Closing SentryClient.", new Object[0]);
        try {
            this.flush(isRestarting ? 0L : this.options.getShutdownTimeoutMillis());
            this.loggerBatchProcessor.close(isRestarting);
            this.transport.close(isRestarting);
        }
        catch (final IOException e) {
            this.options.getLogger().log(SentryLevel.WARNING, "Failed to close the connection to the Sentry Server.", e);
        }
        for (final EventProcessor eventProcessor : this.options.getEventProcessors()) {
            if (eventProcessor instanceof Closeable) {
                try {
                    ((Closeable)eventProcessor).close();
                }
                catch (final IOException e2) {
                    this.options.getLogger().log(SentryLevel.WARNING, "Failed to close the event processor {}.", eventProcessor, e2);
                }
            }
        }
        this.enabled = false;
    }
    
    @Override
    public void flush(final long timeoutMillis) {
        this.loggerBatchProcessor.flush(timeoutMillis);
        this.transport.flush(timeoutMillis);
    }
    
    @Nullable
    @Override
    public RateLimiter getRateLimiter() {
        return this.transport.getRateLimiter();
    }
    
    @Override
    public boolean isHealthy() {
        return this.transport.isHealthy();
    }
    
    private boolean sample() {
        final Random random = (this.options.getSampleRate() == null) ? null : SentryRandom.current();
        if (this.options.getSampleRate() != null && random != null) {
            final double sampling = this.options.getSampleRate();
            return sampling >= random.nextDouble();
        }
        return true;
    }
    
    private static final class SortBreadcrumbsByDate implements Comparator<Breadcrumb>
    {
        @Override
        public int compare(@NotNull final Breadcrumb b1, @NotNull final Breadcrumb b2) {
            return b1.getTimestamp().compareTo(b2.getTimestamp());
        }
    }
}
