Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sentry-servlet-jakarta module #1987

Merged
merged 3 commits into from
Apr 21, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .craft.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ targets:
maven:io.sentry:sentry-spring:
maven:io.sentry:sentry-spring-boot-starter:
maven:io.sentry:sentry-servlet:
maven:io.sentry:sentry-servlet-jakarta:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing it on this PR will fail the publish release since it does not exist under https://github.com/getsentry/sentry-release-registry yet

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created a PR for adding it there: getsentry/sentry-release-registry#69

maven:io.sentry:sentry-logback:
maven:io.sentry:sentry-log4j2:
maven:io.sentry:sentry-jul:
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/bug_report_java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ body:
- sentry-apollo
- sentry-kotlin-extensions
- sentry-servlet
- sentry-servlet-jakarta
- sentry-spring-boot-starter
- sentry-spring
- sentry-logback
Expand Down
1 change: 1 addition & 0 deletions buildSrc/src/main/java/Config.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ object Config {
val springAop = "org.springframework:spring-aop"
val aspectj = "org.aspectj:aspectjweaver"
val servletApi = "javax.servlet:javax.servlet-api:3.1.0"
val servletApiJakarta = "jakarta.servlet:jakarta.servlet-api:5.0.0"

val apacheHttpClient = "org.apache.httpcomponents.client5:httpclient5:5.0.4"

Expand Down
12 changes: 12 additions & 0 deletions sentry-servlet-jakarta/api/sentry-servlet-jakarta.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
public class io/sentry/servlet/jakarta/SentryServletContainerInitializer : jakarta/servlet/ServletContainerInitializer {
public fun <init> ()V
public fun onStartup (Ljava/util/Set;Ljakarta/servlet/ServletContext;)V
}

public class io/sentry/servlet/jakarta/SentryServletRequestListener : jakarta/servlet/ServletRequestListener {
public fun <init> ()V
public fun <init> (Lio/sentry/IHub;)V
public fun requestDestroyed (Ljakarta/servlet/ServletRequestEvent;)V
public fun requestInitialized (Ljakarta/servlet/ServletRequestEvent;)V
}

98 changes: 98 additions & 0 deletions sentry-servlet-jakarta/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import io.spring.gradle.dependencymanagement.dsl.DependencyManagementExtension
import net.ltgt.gradle.errorprone.errorprone
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.springframework.boot.gradle.plugin.SpringBootPlugin

plugins {
`java-library`
kotlin("jvm")
jacoco
id(Config.QualityPlugins.errorProne)
id(Config.QualityPlugins.gradleVersions)
id(Config.BuildPlugins.springBoot) version Config.springBootVersion apply false
id(Config.BuildPlugins.springDependencyManagement) version Config.BuildPlugins.springDependencyManagementVersion
}

the<DependencyManagementExtension>().apply {
imports {
mavenBom(SpringBootPlugin.BOM_COORDINATES)
}
}

configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString()
kotlinOptions.languageVersion = Config.springKotlinCompatibleLanguageVersion
}

dependencies {
api(projects.sentry)
compileOnly(Config.Libs.servletApiJakarta)

compileOnly(Config.CompileOnly.nopen)
errorprone(Config.CompileOnly.nopenChecker)
errorprone(Config.CompileOnly.errorprone)
errorprone(Config.CompileOnly.errorProneNullAway)
compileOnly(Config.CompileOnly.jetbrainsAnnotations)

// tests
testImplementation(Config.Libs.servletApiJakarta)
testImplementation(projects.sentryTestSupport)
testImplementation(kotlin(Config.kotlinStdLib))
testImplementation(Config.TestLibs.kotlinTestJunit)
testImplementation(Config.TestLibs.mockitoKotlin)
testImplementation(Config.TestLibs.awaitility)
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pulling in spring-boot-starter-test:3.0.0-M2 would have required Java 17

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.


configure<SourceSetContainer> {
test {
java.srcDir("src/test/java")
}
}

jacoco {
toolVersion = Config.QualityPlugins.Jacoco.version
}

tasks.jacocoTestReport {
reports {
xml.required.set(true)
html.required.set(false)
}
}

tasks {
jacocoTestCoverageVerification {
violationRules {
rule { limit { minimum = Config.QualityPlugins.Jacoco.minimumCoverage } }
}
}
check {
dependsOn(jacocoTestCoverageVerification)
dependsOn(jacocoTestReport)
}
}
repositories {
mavenCentral()
}
val compileKotlin: KotlinCompile by tasks
compileKotlin.kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
languageVersion = Config.springKotlinCompatibleLanguageVersion
}
val compileTestKotlin: KotlinCompile by tasks
compileTestKotlin.kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
languageVersion = Config.springKotlinCompatibleLanguageVersion
}

tasks.withType<JavaCompile>().configureEach {
options.errorprone {
check("NullAway", net.ltgt.gradle.errorprone.CheckSeverity.ERROR)
option("NullAway:AnnotatedPackages", "io.sentry")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package io.sentry.servlet.jakarta;

import io.sentry.EventProcessor;
import io.sentry.SentryEvent;
import io.sentry.protocol.Request;
import io.sentry.util.Objects;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/** Attaches information about HTTP request to {@link SentryEvent}. */
final class SentryRequestHttpServletRequestProcessor implements EventProcessor {
private static final List<String> SENSITIVE_HEADERS =
Arrays.asList("X-FORWARDED-FOR", "AUTHORIZATION", "COOKIE");

private final @NotNull HttpServletRequest httpRequest;

public SentryRequestHttpServletRequestProcessor(@NotNull HttpServletRequest httpRequest) {
this.httpRequest = Objects.requireNonNull(httpRequest, "httpRequest is required");
}

// httpRequest.getRequestURL() returns StringBuffer which is considered an obsolete class.
@SuppressWarnings("JdkObsolete")
@Override
public @NotNull SentryEvent process(
@NotNull SentryEvent event, @Nullable Map<String, Object> hint) {
final Request sentryRequest = new Request();
sentryRequest.setMethod(httpRequest.getMethod());
sentryRequest.setQueryString(httpRequest.getQueryString());
sentryRequest.setUrl(httpRequest.getRequestURL().toString());
sentryRequest.setHeaders(resolveHeadersMap(httpRequest));

event.setRequest(sentryRequest);
return event;
}

private @NotNull Map<String, String> resolveHeadersMap(
final @NotNull HttpServletRequest request) {
final Map<String, String> headersMap = new HashMap<>();
for (String headerName : Collections.list(request.getHeaderNames())) {
// do not copy personal information identifiable headers
if (!SENSITIVE_HEADERS.contains(headerName.toUpperCase(Locale.ROOT))) {
headersMap.put(headerName, toString(request.getHeaders(headerName)));
}
}
return headersMap;
}

private static @Nullable String toString(final @Nullable Enumeration<String> enumeration) {
return enumeration != null ? String.join(",", Collections.list(enumeration)) : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.sentry.servlet.jakarta;

import com.jakewharton.nopen.annotation.Open;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import java.util.Set;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Servlet container initializer used to add the {@link SentryServletRequestListener} to the {@link
* ServletContext}.
*/
@Open
public class SentryServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> c, @NotNull ServletContext ctx)
throws ServletException {
ctx.addListener(SentryServletRequestListener.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.sentry.servlet.jakarta;

import static io.sentry.TypeCheckHint.SERVLET_REQUEST;

import com.jakewharton.nopen.annotation.Open;
import io.sentry.Breadcrumb;
import io.sentry.HubAdapter;
import io.sentry.IHub;
import io.sentry.util.Objects;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletRequestEvent;
import jakarta.servlet.ServletRequestListener;
import jakarta.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import org.jetbrains.annotations.NotNull;

/**
* This request listener pushes a new scope into sentry that enriches a Sentry event with the
* details about the current request upon sending.
*/
@Open
public class SentryServletRequestListener implements ServletRequestListener {

private final IHub hub;

public SentryServletRequestListener(@NotNull IHub hub) {
this.hub = Objects.requireNonNull(hub, "hub is required");
}

public SentryServletRequestListener() {
this(HubAdapter.getInstance());
}

@Override
public void requestDestroyed(@NotNull ServletRequestEvent servletRequestEvent) {
hub.popScope();
}

@Override
public void requestInitialized(@NotNull ServletRequestEvent servletRequestEvent) {
hub.pushScope();

final ServletRequest servletRequest = servletRequestEvent.getServletRequest();
if (servletRequest instanceof HttpServletRequest) {
final HttpServletRequest httpRequest = (HttpServletRequest) servletRequest;

final Map<String, Object> hintMap = new HashMap<>();
hintMap.put(SERVLET_REQUEST, httpRequest);

hub.addBreadcrumb(
Breadcrumb.http(httpRequest.getRequestURI(), httpRequest.getMethod()), hintMap);

hub.configureScope(
scope -> {
scope.addEventProcessor(new SentryRequestHttpServletRequestProcessor(httpRequest));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.sentry.servlet.jakarta.SentryServletContainerInitializer
Loading