diff --git a/sentry/src/main/java/io/sentry/metrics/MetricsApi.java b/sentry/src/main/java/io/sentry/metrics/MetricsApi.java index 7b878de3d2..24ea873dda 100644 --- a/sentry/src/main/java/io/sentry/metrics/MetricsApi.java +++ b/sentry/src/main/java/io/sentry/metrics/MetricsApi.java @@ -1,7 +1,28 @@ package io.sentry.metrics; +import io.sentry.HostnameCache; +import io.sentry.IScope; +import io.sentry.ISpan; +import io.sentry.PropagationContext; import io.sentry.Scopes; +import io.sentry.SentryAttribute; +import io.sentry.SentryAttributeType; +import io.sentry.SentryAttributes; +import io.sentry.SentryDate; +import io.sentry.SentryLevel; +import io.sentry.SentryLogEventAttributeValue; +import io.sentry.SentryMetricsEvent; +import io.sentry.SentryOptions; +import io.sentry.SpanId; +import io.sentry.logger.SentryLogParameters; +import io.sentry.protocol.SdkVersion; +import io.sentry.protocol.SentryId; +import io.sentry.protocol.User; +import io.sentry.util.Platform; +import io.sentry.util.TracingUtils; +import java.util.HashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public final class MetricsApi implements IMetricsApi { @@ -13,6 +34,196 @@ public MetricsApi(final @NotNull Scopes scopes) { @Override public void count(@NotNull String name) { - scopes.getOptions(); + captureMetrics(SentryLogParameters.create(null, null), name, "counter", 1.0); + } + + @SuppressWarnings("AnnotateFormatMethod") + private void captureMetrics( + final @NotNull SentryLogParameters params, + final @Nullable String name, + final @Nullable String type, + final @Nullable Double value) { + final @NotNull SentryOptions options = scopes.getOptions(); + try { + if (!scopes.isEnabled()) { + options + .getLogger() + .log(SentryLevel.WARNING, "Instance is disabled and this 'metrics' call is a no-op."); + return; + } + + if (!options.getMetrics().isEnabled()) { + options + .getLogger() + .log( + SentryLevel.WARNING, + "Sentry Metrics is disabled and this 'metrics' call is a no-op."); + return; + } + + if (name == null) { + return; + } + + if (type == null) { + return; + } + + if (value == null) { + return; + } + + final @Nullable SentryDate timestamp = params.getTimestamp(); + final @NotNull SentryDate timestampToUse = + timestamp == null ? options.getDateProvider().now() : timestamp; + + final @NotNull IScope combinedScope = scopes.getCombinedScopeView(); + final @NotNull PropagationContext propagationContext = combinedScope.getPropagationContext(); + final @Nullable ISpan span = combinedScope.getSpan(); + if (span == null) { + TracingUtils.maybeUpdateBaggage(combinedScope, options); + } + final @NotNull SentryId traceId = + span == null ? propagationContext.getTraceId() : span.getSpanContext().getTraceId(); + final @NotNull SpanId spanId = + span == null ? propagationContext.getSpanId() : span.getSpanContext().getSpanId(); + final SentryMetricsEvent metricsEvent = + new SentryMetricsEvent(traceId, timestampToUse, name, type, value); + metricsEvent.setSpanId(spanId); + metricsEvent.setAttributes(createAttributes(params)); + + scopes.getClient().captureMetric(metricsEvent, combinedScope); + } catch (Throwable e) { + options.getLogger().log(SentryLevel.ERROR, "Error while capturing log event", e); + } + } + + private @NotNull HashMap createAttributes( + final @NotNull SentryLogParameters params) { + final @NotNull HashMap attributes = new HashMap<>(); + final @NotNull String origin = params.getOrigin(); + if (!"manual".equalsIgnoreCase(origin)) { + attributes.put( + "sentry.origin", new SentryLogEventAttributeValue(SentryAttributeType.STRING, origin)); + } + + final @Nullable SentryAttributes incomingAttributes = params.getAttributes(); + + if (incomingAttributes != null) { + for (SentryAttribute attribute : incomingAttributes.getAttributes().values()) { + final @Nullable Object value = attribute.getValue(); + final @NotNull SentryAttributeType type = + attribute.getType() == null ? getType(value) : attribute.getType(); + attributes.put(attribute.getName(), new SentryLogEventAttributeValue(type, value)); + } + } + + final @Nullable SdkVersion sdkVersion = scopes.getOptions().getSdkVersion(); + if (sdkVersion != null) { + attributes.put( + "sentry.sdk.name", + new SentryLogEventAttributeValue(SentryAttributeType.STRING, sdkVersion.getName())); + attributes.put( + "sentry.sdk.version", + new SentryLogEventAttributeValue(SentryAttributeType.STRING, sdkVersion.getVersion())); + } + + final @Nullable String environment = scopes.getOptions().getEnvironment(); + if (environment != null) { + attributes.put( + "sentry.environment", + new SentryLogEventAttributeValue(SentryAttributeType.STRING, environment)); + } + + final @NotNull SentryId scopeReplayId = scopes.getCombinedScopeView().getReplayId(); + if (!SentryId.EMPTY_ID.equals(scopeReplayId)) { + attributes.put( + "sentry.replay_id", + new SentryLogEventAttributeValue(SentryAttributeType.STRING, scopeReplayId.toString())); + } else { + final @NotNull SentryId controllerReplayId = + scopes.getOptions().getReplayController().getReplayId(); + if (!SentryId.EMPTY_ID.equals(controllerReplayId)) { + attributes.put( + "sentry.replay_id", + new SentryLogEventAttributeValue( + SentryAttributeType.STRING, controllerReplayId.toString())); + attributes.put( + "sentry._internal.replay_is_buffering", + new SentryLogEventAttributeValue(SentryAttributeType.BOOLEAN, true)); + } + } + + final @Nullable String release = scopes.getOptions().getRelease(); + if (release != null) { + attributes.put( + "sentry.release", new SentryLogEventAttributeValue(SentryAttributeType.STRING, release)); + } + + if (Platform.isJvm()) { + setServerName(attributes); + } + + setUser(attributes); + + return attributes; + } + + private void setServerName( + final @NotNull HashMap attributes) { + final @NotNull SentryOptions options = scopes.getOptions(); + final @Nullable String optionsServerName = options.getServerName(); + if (optionsServerName != null) { + attributes.put( + "server.address", + new SentryLogEventAttributeValue(SentryAttributeType.STRING, optionsServerName)); + } else if (options.isAttachServerName()) { + final @Nullable String hostname = HostnameCache.getInstance().getHostname(); + if (hostname != null) { + attributes.put( + "server.address", + new SentryLogEventAttributeValue(SentryAttributeType.STRING, hostname)); + } + } + } + + private void setUser(final @NotNull HashMap attributes) { + final @Nullable User user = scopes.getCombinedScopeView().getUser(); + if (user == null) { + // In case no user is set, we should fallback to the distinct id, known as installation id, + // which is used on Android as default user id + final @Nullable String id = scopes.getOptions().getDistinctId(); + if (id != null) { + attributes.put("user.id", new SentryLogEventAttributeValue(SentryAttributeType.STRING, id)); + } + } else { + final @Nullable String id = user.getId(); + if (id != null) { + attributes.put("user.id", new SentryLogEventAttributeValue(SentryAttributeType.STRING, id)); + } + final @Nullable String username = user.getUsername(); + if (username != null) { + attributes.put( + "user.name", new SentryLogEventAttributeValue(SentryAttributeType.STRING, username)); + } + final @Nullable String email = user.getEmail(); + if (email != null) { + attributes.put( + "user.email", new SentryLogEventAttributeValue(SentryAttributeType.STRING, email)); + } + } + } + + private @NotNull SentryAttributeType getType(final @Nullable Object arg) { + if (arg instanceof Boolean) { + return SentryAttributeType.BOOLEAN; + } + if (arg instanceof Integer) { + return SentryAttributeType.INTEGER; + } + if (arg instanceof Number) { + return SentryAttributeType.DOUBLE; + } + return SentryAttributeType.STRING; } }