diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index c1a02871..ba404960 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -34,6 +34,8 @@ jobs: shell: bash - name: build + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} run: | chmod +x gradlew ./gradlew build -x test diff --git a/build.gradle b/build.gradle index 53a0677d..cae3102d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,69 +1,77 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.3.1' - id 'io.spring.dependency-management' version '1.1.5' + id 'java' + id 'org.springframework.boot' version '3.3.1' + id 'io.spring.dependency-management' version '1.1.5' + id "io.sentry.jvm.gradle" version "5.1.0" } group = 'com.tiki' version = '0.0.1-SNAPSHOT' java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() +} + +sentry { + includeSourceContext = true + org = "ti-ki" + projectName = "java-spring-boot" + authToken = System.getenv("SENTRY_AUTH_TOKEN") } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-actuator' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'org.postgresql:postgresql' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testImplementation 'org.springframework.security:spring-security-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' - - //jwt - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' - implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' - implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' - - // slack logback - implementation 'com.github.maricn:logback-slack-appender:1.4.0' - - // s3 - implementation("software.amazon.awssdk:bom:2.21.0") - implementation("software.amazon.awssdk:s3:2.21.0") - - // s3 - implementation("software.amazon.awssdk:bom:2.21.0") - implementation("software.amazon.awssdk:s3:2.21.0") - - // swagger - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' - - // mail - implementation 'org.springframework.boot:spring-boot-starter-mail' - - //Redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - implementation 'io.lettuce:lettuce-core:6.2.1.RELEASE' - - //thymeleaf - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + compileOnly 'org.projectlombok:lombok' + runtimeOnly 'org.postgresql:postgresql' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + //jwt + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // slack logback + implementation 'com.github.maricn:logback-slack-appender:1.4.0' + + // s3 + implementation("software.amazon.awssdk:bom:2.21.0") + implementation("software.amazon.awssdk:s3:2.21.0") + + // s3 + implementation("software.amazon.awssdk:bom:2.21.0") + implementation("software.amazon.awssdk:s3:2.21.0") + + // swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + // mail + implementation 'org.springframework.boot:spring-boot-starter-mail' + + //Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + implementation 'io.lettuce:lettuce-core:6.2.1.RELEASE' + + //thymeleaf + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' //Validation implementation 'commons-validator:commons-validator:1.7' @@ -73,5 +81,5 @@ dependencies { } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/com/tiki/server/common/handler/ErrorHandler.java b/src/main/java/com/tiki/server/common/handler/ErrorHandler.java index e0d0b82c..69f7b9ab 100644 --- a/src/main/java/com/tiki/server/common/handler/ErrorHandler.java +++ b/src/main/java/com/tiki/server/common/handler/ErrorHandler.java @@ -7,6 +7,7 @@ import com.tiki.server.email.verification.exception.EmailVerificationException; import com.tiki.server.folder.exception.FolderException; import com.tiki.server.note.exception.NoteException; +import io.sentry.Sentry; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -36,6 +37,7 @@ public class ErrorHandler { public ResponseEntity memberException(MemberException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -43,6 +45,7 @@ public ResponseEntity memberException(MemberException exception) { public ResponseEntity teamException(TeamException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -50,6 +53,7 @@ public ResponseEntity teamException(TeamException exception) { public ResponseEntity memberTeamManagerException(MemberTeamManagerException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -57,6 +61,7 @@ public ResponseEntity memberTeamManagerException(MemberTeamManager public ResponseEntity timeBlockException(TimeBlockException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -64,6 +69,7 @@ public ResponseEntity timeBlockException(TimeBlockException except public ResponseEntity documentException(DocumentException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -71,6 +77,7 @@ public ResponseEntity documentException(DocumentException exceptio public ResponseEntity noteException(NoteException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -78,6 +85,7 @@ public ResponseEntity noteException(NoteException exception) { public ResponseEntity externalException(ExternalException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -85,6 +93,7 @@ public ResponseEntity externalException(ExternalException exceptio public ResponseEntity mailVerificationException(EmailVerificationException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -92,6 +101,7 @@ public ResponseEntity mailVerificationException(EmailVerificationE public ResponseEntity mailSenderException(EmailSenderException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -99,6 +109,7 @@ public ResponseEntity mailSenderException(EmailSenderException exc public ResponseEntity teamInvitationException(TeamInvitationException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -106,6 +117,7 @@ public ResponseEntity teamInvitationException(TeamInvitationExcept public ResponseEntity folderException(FolderException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } @@ -113,6 +125,7 @@ public ResponseEntity folderException(FolderException exception) { public ResponseEntity authException(AuthException exception) { log.error(exception.getMessage()); val errorCode = exception.getErrorCode(); + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body( ErrorCodeResponse.of(errorCode.getCode(), errorCode.getMessage())); } @@ -120,6 +133,7 @@ public ResponseEntity authException(AuthException exception) { @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity httpMessageNotReadableException(HttpMessageNotReadableException exception) { log.error(exception.getMessage()); + Sentry.captureException(exception); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body( ErrorResponse.of(WRONG_INPUT)); } @@ -128,6 +142,7 @@ public ResponseEntity httpMessageNotReadableException(HttpMessageN public ResponseEntity exception(Exception exception) { log.error(exception.getMessage()); val errorCode = UNCAUGHT_SERVER_EXCEPTION; + Sentry.captureException(exception); return ResponseEntity.status(errorCode.getHttpStatus()).body(ErrorResponse.of(errorCode.getMessage())); } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 73524f90..69bb6fab 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -58,3 +58,7 @@ aws-property: bucket: ${AWS_PROPERTY.BUCKET.dev} aws-region: ap-northeast-2 s3-url: ${AWS_PROPERTY.S3_URL.dev} + +sentry: + dsn: ${SENTRY_PROPERTY.dsn} + traces-sample-rate: 1.0 \ No newline at end of file