All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sentry.MainEventProcessor Maven / Gradle / Ivy

There is a newer version: 8.0.0-rc.3
Show newest version
package io.sentry;

import io.sentry.hints.AbnormalExit;
import io.sentry.hints.Cached;
import io.sentry.protocol.DebugImage;
import io.sentry.protocol.DebugMeta;
import io.sentry.protocol.SentryException;
import io.sentry.protocol.SentryTransaction;
import io.sentry.protocol.User;
import io.sentry.util.HintUtils;
import io.sentry.util.Objects;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.VisibleForTesting;

@ApiStatus.Internal
public final class MainEventProcessor implements EventProcessor, Closeable {

  private final @NotNull SentryOptions options;
  private final @NotNull SentryThreadFactory sentryThreadFactory;
  private final @NotNull SentryExceptionFactory sentryExceptionFactory;
  private volatile @Nullable HostnameCache hostnameCache = null;

  public MainEventProcessor(final @NotNull SentryOptions options) {
    this.options = Objects.requireNonNull(options, "The SentryOptions is required.");

    final SentryStackTraceFactory sentryStackTraceFactory =
        new SentryStackTraceFactory(this.options);

    sentryExceptionFactory = new SentryExceptionFactory(sentryStackTraceFactory);
    sentryThreadFactory = new SentryThreadFactory(sentryStackTraceFactory, this.options);
  }

  MainEventProcessor(
      final @NotNull SentryOptions options,
      final @NotNull SentryThreadFactory sentryThreadFactory,
      final @NotNull SentryExceptionFactory sentryExceptionFactory) {
    this.options = Objects.requireNonNull(options, "The SentryOptions is required.");
    this.sentryThreadFactory =
        Objects.requireNonNull(sentryThreadFactory, "The SentryThreadFactory is required.");
    this.sentryExceptionFactory =
        Objects.requireNonNull(sentryExceptionFactory, "The SentryExceptionFactory is required.");
  }

  @Override
  public @NotNull SentryEvent process(final @NotNull SentryEvent event, final @NotNull Hint hint) {
    setCommons(event);
    setExceptions(event);
    setDebugMeta(event);
    setModules(event);

    if (shouldApplyScopeData(event, hint)) {
      processNonCachedEvent(event);
      setThreads(event, hint);
    }

    return event;
  }

  private void setDebugMeta(final @NotNull SentryBaseEvent event) {
    final @NotNull List debugImages = new ArrayList<>();

    if (options.getProguardUuid() != null) {
      final DebugImage proguardMappingImage = new DebugImage();
      proguardMappingImage.setType(DebugImage.PROGUARD);
      proguardMappingImage.setUuid(options.getProguardUuid());
      debugImages.add(proguardMappingImage);
    }

    for (final @NotNull String bundleId : options.getBundleIds()) {
      final DebugImage sourceBundleImage = new DebugImage();
      sourceBundleImage.setType(DebugImage.JVM);
      sourceBundleImage.setDebugId(bundleId);
      debugImages.add(sourceBundleImage);
    }

    if (!debugImages.isEmpty()) {
      DebugMeta debugMeta = event.getDebugMeta();

      if (debugMeta == null) {
        debugMeta = new DebugMeta();
      }
      if (debugMeta.getImages() == null) {
        debugMeta.setImages(debugImages);
      } else {
        debugMeta.getImages().addAll(debugImages);
      }

      event.setDebugMeta(debugMeta);
    }
  }

  private void setModules(final @NotNull SentryEvent event) {
    final Map modules = options.getModulesLoader().getOrLoadModules();
    if (modules == null) {
      return;
    }

    final Map eventModules = event.getModules();
    if (eventModules == null) {
      event.setModules(modules);
    } else {
      eventModules.putAll(modules);
    }
  }

  private boolean shouldApplyScopeData(
      final @NotNull SentryBaseEvent event, final @NotNull Hint hint) {
    if (HintUtils.shouldApplyScopeData(hint)) {
      return true;
    } else {
      options
          .getLogger()
          .log(
              SentryLevel.DEBUG,
              "Event was cached so not applying data relevant to the current app execution/version: %s",
              event.getEventId());
      return false;
    }
  }

  private void processNonCachedEvent(final @NotNull SentryBaseEvent event) {
    setRelease(event);
    setEnvironment(event);
    setServerName(event);
    setDist(event);
    setSdk(event);
    setTags(event);
    mergeUser(event);
  }

  @Override
  public @NotNull SentryTransaction process(
      final @NotNull SentryTransaction transaction, final @NotNull Hint hint) {
    setCommons(transaction);
    setDebugMeta(transaction);

    if (shouldApplyScopeData(transaction, hint)) {
      processNonCachedEvent(transaction);
    }

    return transaction;
  }

  @Override
  public @NotNull SentryReplayEvent process(
      final @NotNull SentryReplayEvent event, final @NotNull Hint hint) {
    setCommons(event);
    // TODO: maybe later it's needed to deobfuscate something (e.g. view hierarchy), for now the
    // TODO: protocol does not support it
    // setDebugMeta(event);

    if (shouldApplyScopeData(event, hint)) {
      processNonCachedEvent(event);
    }
    return event;
  }

  private void setCommons(final @NotNull SentryBaseEvent event) {
    setPlatform(event);
  }

  private void setPlatform(final @NotNull SentryBaseEvent event) {
    if (event.getPlatform() == null) {
      // this actually means JVM related.
      event.setPlatform(SentryBaseEvent.DEFAULT_PLATFORM);
    }
  }

  private void setRelease(final @NotNull SentryBaseEvent event) {
    if (event.getRelease() == null) {
      event.setRelease(options.getRelease());
    }
  }

  private void setEnvironment(final @NotNull SentryBaseEvent event) {
    if (event.getEnvironment() == null) {
      event.setEnvironment(options.getEnvironment());
    }
  }

  private void setServerName(final @NotNull SentryBaseEvent event) {
    if (event.getServerName() == null) {
      event.setServerName(options.getServerName());
    }

    if (options.isAttachServerName() && event.getServerName() == null) {
      ensureHostnameCache();
      if (hostnameCache != null) {
        event.setServerName(hostnameCache.getHostname());
      }
    }
  }

  private void ensureHostnameCache() {
    if (hostnameCache == null) {
      synchronized (this) {
        if (hostnameCache == null) {
          hostnameCache = HostnameCache.getInstance();
        }
      }
    }
  }

  private void setDist(final @NotNull SentryBaseEvent event) {
    if (event.getDist() == null) {
      event.setDist(options.getDist());
    }
  }

  private void setSdk(final @NotNull SentryBaseEvent event) {
    if (event.getSdk() == null) {
      event.setSdk(options.getSdkVersion());
    }
  }

  private void setTags(final @NotNull SentryBaseEvent event) {
    if (event.getTags() == null) {
      event.setTags(new HashMap<>(options.getTags()));
    } else {
      for (Map.Entry item : options.getTags().entrySet()) {
        if (!event.getTags().containsKey(item.getKey())) {
          event.setTag(item.getKey(), item.getValue());
        }
      }
    }
  }

  private void mergeUser(final @NotNull SentryBaseEvent event) {
    @Nullable User user = event.getUser();
    if (user == null) {
      user = new User();
      event.setUser(user);
    }
    if (user.getIpAddress() == null) {
      user.setIpAddress(IpAddressUtils.DEFAULT_IP_ADDRESS);
    }
  }

  private void setExceptions(final @NotNull SentryEvent event) {
    final Throwable throwable = event.getThrowableMechanism();
    if (throwable != null) {
      event.setExceptions(sentryExceptionFactory.getSentryExceptions(throwable));
    }
  }

  private void setThreads(final @NotNull SentryEvent event, final @NotNull Hint hint) {
    if (event.getThreads() == null) {
      // collecting threadIds that came from the exception mechanism, so we can mark threads as
      // crashed properly
      List mechanismThreadIds = null;

      final List eventExceptions = event.getExceptions();

      if (eventExceptions != null && !eventExceptions.isEmpty()) {
        for (final SentryException item : eventExceptions) {
          if (item.getMechanism() != null && item.getThreadId() != null) {
            if (mechanismThreadIds == null) {
              mechanismThreadIds = new ArrayList<>();
            }
            mechanismThreadIds.add(item.getThreadId());
          }
        }
      }

      // typically Abnormal exits can be tackled by looking at the thread dump (e.g. ANRs), hence
      // we force attach threads regardless of the config
      if (options.isAttachThreads() || HintUtils.hasType(hint, AbnormalExit.class)) {
        final Object sentrySdkHint = HintUtils.getSentrySdkHint(hint);
        boolean ignoreCurrentThread = false;
        if (sentrySdkHint instanceof AbnormalExit) {
          ignoreCurrentThread = ((AbnormalExit) sentrySdkHint).ignoreCurrentThread();
        }
        event.setThreads(
            sentryThreadFactory.getCurrentThreads(mechanismThreadIds, ignoreCurrentThread));
      } else if (options.isAttachStacktrace()
          && (eventExceptions == null || eventExceptions.isEmpty())
          && !isCachedHint(hint)) {
        // when attachStacktrace is enabled, we attach only the current thread and its stack traces,
        // if there are no exceptions, exceptions have its own stack traces.
        event.setThreads(sentryThreadFactory.getCurrentThread());
      }
    }
  }

  /**
   * If the event has a Cached Hint, it means that it came from the EnvelopeFileObserver. We don't
   * want to append the current thread to the event.
   *
   * @param hint the Hints
   * @return true if Cached or false otherwise
   */
  private boolean isCachedHint(final @NotNull Hint hint) {
    return HintUtils.hasType(hint, Cached.class);
  }

  @Override
  public void close() throws IOException {
    if (hostnameCache != null) {
      hostnameCache.close();
    }
  }

  boolean isClosed() {
    if (hostnameCache != null) {
      return hostnameCache.isClosed();
    } else {
      return true;
    }
  }

  @VisibleForTesting
  @Nullable
  HostnameCache getHostnameCache() {
    return hostnameCache;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy