Skip to content

Commit

Permalink
Set tags on SentryOptions through external configuration (#1066)
Browse files Browse the repository at this point in the history
Add an option to set tags on SentryOptions through external configuration.

Fixes gh-981
  • Loading branch information
maciejwalkowiak committed Nov 26, 2020
1 parent 8160083 commit bb58d45
Show file tree
Hide file tree
Showing 16 changed files with 252 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

* Enhancement: Set transaction name on events and transactions sent using Spring integration (#1067)
* Fix: Set current thread only if there are no exceptions
* Enhancement: Set global tags on SentryOptions and load them from external configuration (#1066)

# 4.0.0-alpha.1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ class SentryAutoConfigurationTest {
"sentry.proxy.port=8090",
"sentry.proxy.user=proxy-user",
"sentry.proxy.pass=proxy-pass",
"sentry.enable-tracing=true"
"sentry.enable-tracing=true",
"sentry.tags.tag1=tag1-value",
"sentry.tags.tag2=tag2-value"
).run {
val options = it.getBean(SentryProperties::class.java)
assertThat(options.readTimeoutMillis).isEqualTo(10)
Expand All @@ -130,6 +132,7 @@ class SentryAutoConfigurationTest {
assertThat(options.proxy!!.user).isEqualTo("proxy-user")
assertThat(options.proxy!!.pass).isEqualTo("proxy-pass")
assertThat(options.isEnableTracing).isTrue()
assertThat(options.tags).containsEntry("tag1", "tag1-value").containsEntry("tag2", "tag2-value")
}
}

Expand Down
2 changes: 2 additions & 0 deletions sentry/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ tasks {
}
test {
environment["SENTRY_TEST_PROPERTY"] = "\"some-value\""
environment["SENTRY_TEST_MAP_KEY1"] = "\"value1\""
environment["SENTRY_TEST_MAP_KEY2"] = "value2"
}
}

Expand Down
7 changes: 7 additions & 0 deletions sentry/src/main/java/io/sentry/MainEventProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.sentry.util.Objects;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
Expand Down Expand Up @@ -89,6 +90,12 @@ private void processNonCachedEvent(final @NotNull SentryEvent event) {
event.setSdk(options.getSdkVersion());
}

for (final Map.Entry<String, String> tag : options.getTags().entrySet()) {
if (event.getTag(tag.getKey()) == null) {
event.setTag(tag.getKey(), tag.getValue());
}
}

if (event.getThreads() == null) {
// collecting threadIds that came from the exception mechanism, so we can mark threads as
// crashed properly
Expand Down
33 changes: 33 additions & 0 deletions sentry/src/main/java/io/sentry/SentryOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
import io.sentry.transport.NoOpTransportGate;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLSocketFactory;
Expand Down Expand Up @@ -240,6 +243,9 @@ public class SentryOptions {
*/
private boolean enableExternalConfiguration;

/** Tags applied to every event and transaction */
private final @NotNull Map<String, String> tags = new ConcurrentHashMap<>();

/**
* Creates {@link SentryOptions} from properties provided by a {@link PropertiesProvider}.
*
Expand All @@ -253,6 +259,10 @@ public class SentryOptions {
options.setRelease(propertiesProvider.getProperty("release"));
options.setDist(propertiesProvider.getProperty("dist"));
options.setServerName(propertiesProvider.getProperty("servername"));
final Map<String, String> tags = propertiesProvider.getMap("tags");
for (final Map.Entry<String, String> tag : tags.entrySet()) {
options.setTag(tag.getKey(), tag.getValue());
}

final String proxyHost = propertiesProvider.getProperty("proxy.host");
final String proxyUser = propertiesProvider.getProperty("proxy.user");
Expand Down Expand Up @@ -1118,6 +1128,25 @@ public void setEnableExternalConfiguration(boolean enableExternalConfiguration)
this.enableExternalConfiguration = enableExternalConfiguration;
}

/**
* Returns tags applied to all events and transactions.
*
* @return the tags map
*/
public @NotNull Map<String, String> getTags() {
return tags;
}

/**
* Sets a tag that is applied to all events and transactions.
*
* @param key the key
* @param value the value
*/
public void setTag(final @NotNull String key, final @NotNull String value) {
this.tags.put(key, value);
}

/** The BeforeSend callback */
public interface BeforeSendCallback {

Expand Down Expand Up @@ -1203,6 +1232,10 @@ void merge(final @NotNull SentryOptions options) {
if (options.getProxy() != null) {
setProxy(options.getProxy());
}
final Map<String, String> tags = new HashMap<>(options.getTags());
for (final Map.Entry<String, String> tag : tags.entrySet()) {
this.tags.put(tag.getKey(), tag.getValue());
}
}

private @NotNull SdkVersion createSdkVersion() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.sentry.config;

import io.sentry.util.Objects;
import io.sentry.util.StringUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Base class for properties provider resolving properties from {@link Properties} based sources.
*/
abstract class AbstractPropertiesProvider implements PropertiesProvider {
private final @NotNull String prefix;
private final @NotNull Properties properties;

protected AbstractPropertiesProvider(
final @NotNull String prefix, final @NotNull Properties properties) {
this.prefix = Objects.requireNonNull(prefix, "prefix is required");
this.properties = Objects.requireNonNull(properties, "properties are required");
}

protected AbstractPropertiesProvider(final @NotNull Properties properties) {
this("", properties);
}

@Override
public @Nullable String getProperty(final @NotNull String property) {
return StringUtils.removeSurrounding(properties.getProperty(prefix + property), "\"");
}

/**
* Gets property map based on the property name, where property name is a prefix for the expected
* property name. For example, when {@link #prefix} is set to {@code "sentry."} and following
* properties are set: sentry.tags.tag1=value1 and sentry.tags.tag2=value2, calling {@code
* getMap("tags")} returns a map containing pairs "tag1" => "value1" and "tag2" => "value2".
*
* @param property the property name
* @return the map or empty if no matching environment variables found.
*/
@Override
public @NotNull Map<String, String> getMap(final @NotNull String property) {
final String prefix = this.prefix + property + ".";

final Map<String, String> result = new HashMap<>();
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
final String key = (String) entry.getKey();
if (key.startsWith(prefix)) {
final String value = StringUtils.removeSurrounding((String) entry.getValue(), "\"");
result.put(key.substring(prefix.length()), value);
}
}
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.sentry.config;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -26,4 +28,15 @@ public CompositePropertiesProvider(@NotNull List<PropertiesProvider> providers)
}
return null;
}

@Override
public @NotNull Map<String, String> getMap(final @NotNull String property) {
for (final PropertiesProvider provider : providers) {
final Map<String, String> result = provider.getMap(property);
if (!result.isEmpty()) {
return result;
}
}
return new ConcurrentHashMap<>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import io.sentry.util.StringUtils;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -15,6 +17,34 @@ final class EnvironmentVariablePropertiesProvider implements PropertiesProvider
@Override
public @Nullable String getProperty(@NotNull String property) {
return StringUtils.removeSurrounding(
System.getenv(PREFIX + "_" + property.replace(".", "_").toUpperCase(Locale.ROOT)), "\"");
System.getenv(propertyToEnvironmentVariableName(property)), "\"");
}

/**
* Gets property map based on the property name, where property name is a prefix for the expected
* environment variable name. For example, when following environment variables are set:
* SENTRY_TAGS_TAG_1=value1 and SENTRY_TAGS_TAG_2=value2, calling {@code getMap("tags")} returns a
* map containing pairs "tag1" => "value1" and "tag2" => "value2".
*
* @param property the property name
* @return the map or empty if no matching environment variables found.
*/
@Override
public @NotNull Map<String, String> getMap(final @NotNull String property) {
final String prefix = propertyToEnvironmentVariableName(property) + "_";

final Map<String, String> result = new ConcurrentHashMap<>();
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
final String key = entry.getKey();
if (key.startsWith(prefix)) {
final String value = StringUtils.removeSurrounding(entry.getValue(), "\"");
result.put(key.substring(prefix.length()).toLowerCase(Locale.ROOT), value);
}
}
return result;
}

private @NotNull String propertyToEnvironmentVariableName(final @NotNull String property) {
return PREFIX + "_" + property.replace(".", "_").toUpperCase(Locale.ROOT);
}
}
10 changes: 10 additions & 0 deletions sentry/src/main/java/io/sentry/config/PropertiesProvider.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.sentry.config;

import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

Expand All @@ -13,6 +14,15 @@ public interface PropertiesProvider {
@Nullable
String getProperty(@NotNull String property);

/**
* Resolves a map for a property given by it's name.
*
* @param property - the property name
* @return the map or empty map if not found
*/
@NotNull
Map<String, String> getMap(final @NotNull String property);

/**
* Resolves property given by it's name.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
package io.sentry.config;

import io.sentry.util.StringUtils;
import java.util.Properties;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** {@link PropertiesProvider} implementation that delegates to {@link Properties}. */
final class SimplePropertiesProvider implements PropertiesProvider {
private final @NotNull Properties properties;
final class SimplePropertiesProvider extends AbstractPropertiesProvider {

public SimplePropertiesProvider(final @NotNull Properties properties) {
this.properties = properties;
}

@Override
public @Nullable String getProperty(@NotNull String property) {
return StringUtils.removeSurrounding(properties.getProperty(property), "\"");
super(properties);
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
package io.sentry.config;

import io.sentry.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* {@link PropertiesProvider} implementation that resolves properties from Java system properties.
*/
final class SystemPropertyPropertiesProvider implements PropertiesProvider {
private static final String PREFIX = "sentry";
final class SystemPropertyPropertiesProvider extends AbstractPropertiesProvider {
private static final String PREFIX = "sentry.";

@Override
public @Nullable String getProperty(@NotNull String property) {
return StringUtils.removeSurrounding(System.getProperty(PREFIX + "." + property), "\"");
public SystemPropertyPropertiesProvider() {
super(PREFIX, System.getProperties());
}
}
22 changes: 21 additions & 1 deletion sentry/src/test/java/io/sentry/MainEventProcessorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ class MainEventProcessorTest {
version = "1.2.3"
}
}
fun getSut(attachThreads: Boolean = true, attachStackTrace: Boolean = true, environment: String? = "environment"): MainEventProcessor {
fun getSut(attachThreads: Boolean = true, attachStackTrace: Boolean = true, environment: String? = "environment", tags: Map<String, String> = emptyMap()): MainEventProcessor {
sentryOptions.isAttachThreads = attachThreads
sentryOptions.isAttachStacktrace = attachStackTrace
sentryOptions.environment = environment
tags.forEach { sentryOptions.setTag(it.key, it.value) }
return MainEventProcessor(sentryOptions)
}
}
Expand Down Expand Up @@ -207,6 +208,25 @@ class MainEventProcessorTest {
assertEquals("custom", event.environment)
}

@Test
fun `sets tags from SentryOptions`() {
val sut = fixture.getSut(tags = mapOf("tag1" to "value1", "tag2" to "value2"))
val event = SentryEvent()
sut.process(event, null)
assertEquals("value1", event.tags["tag1"])
assertEquals("value2", event.tags["tag2"])
}

@Test
fun `when event has a tag set with the same name as SentryOptions tags, the tag value from the event is retained`() {
val sut = fixture.getSut(tags = mapOf("tag1" to "value1", "tag2" to "value2"))
val event = SentryEvent()
event.setTag("tag2", "event-tag-value")
sut.process(event, null)
assertEquals("value1", event.tags["tag1"])
assertEquals("event-tag-value", event.tags["tag2"])
}

private fun generateCrashedEvent(crashedThread: Thread = Thread.currentThread()) = SentryEvent().apply {
val mockThrowable = mock<Throwable>()
val actualThrowable = UncaughtExceptionHandlerIntegration.getUnhandledThrowable(crashedThread, mockThrowable)
Expand Down
Loading

0 comments on commit bb58d45

Please sign in to comment.