diff --git a/main/build.gradle b/main/build.gradle index c64f1fa7..1faa3948 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -23,71 +23,82 @@ repositories { } dependencies { + // Spring Boot Dependencies implementation 'org.springframework.boot:spring-boot-starter-web' - // https://mvnrepository.com/artifact/org.springdoc/springdoc-openapi-ui implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' implementation 'org.springframework.boot:spring-boot-starter-webflux:3.1.5' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' - // lombok + // Lombok Dependencies (Compile-time code generation library) compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' + // Development Tools developmentOnly 'org.springframework.boot:spring-boot-devtools' + // Test Dependencies testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.testcontainers:testcontainers:1.17.3' + testImplementation 'org.testcontainers:junit-jupiter:1.16.2' + testImplementation 'org.testcontainers:postgresql:1.17.3' + testImplementation 'org.testcontainers:jdbc' + + // Apache Commons Dependencies + implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.14.0' + + // AOP (Aspect-Oriented Programming) for cross-cutting concerns + implementation 'org.springframework.boot:spring-boot-starter-aop' + + // Database and Persistence Dependencies implementation group: 'org.postgresql', name: 'postgresql', version: '42.6.0' implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.postgresql:postgresql:42.3.0' - // jsonb 타입 핸들링 위함 + // jsonb Type Handling implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.6.0' + // JWT Dependencies for Security and Authentication implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' implementation 'com.auth0:java-jwt:4.4.0' - // h2 + // H2 Database (for testing and in-memory DB) runtimeOnly 'com.h2database:h2' - // mac m1 setting + // macOS M1 Support (Network Resolver for macOS) implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64' + // Spring Cloud OpenFeign (Service-to-service communication) implementation 'org.springframework.cloud:spring-cloud-starter-openfeign:4.0.3' - // test container - testImplementation 'org.testcontainers:testcontainers:1.17.3' // TC 의존성 - testImplementation 'org.testcontainers:junit-jupiter:1.16.2' // TC 의존성 - testImplementation 'org.testcontainers:postgresql:1.17.3' // PostgreSQL 컨테이너 사용 - testImplementation 'org.testcontainers:jdbc' // DB와의 JDBC connection - implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.14.0' - - // aop - implementation 'org.springframework.boot:spring-boot-starter-aop' - - // MapStruct + // MapStruct (DTO transformation code generation) implementation 'org.mapstruct:mapstruct:1.5.3.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' - // queryDsl + // QueryDSL (JPA Query Generation) implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" - // AWS SDK for S3 + // AWS SDK for S3 (for file upload and download) implementation "software.amazon.awssdk:s3:2.27.0" - // csv 관련 + // CSV Processing Library implementation 'com.opencsv:opencsv:5.5.2' - // prometheus + // Prometheus (Monitoring and Metrics Collection) implementation 'io.micrometer:micrometer-registry-prometheus' + + // Sentry Logback (Error Tracking) + implementation 'io.sentry:sentry-logback:7.17.0' + + // Slack Webhook + implementation 'com.github.maricn:logback-slack-appender:1.4.0' } tasks.named('test') { @@ -157,4 +168,4 @@ jacocoTestCoverageVerification { ] } } -} \ No newline at end of file +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/advice/ControllerExceptionAdvice.java b/main/src/main/java/org/sopt/makers/crew/main/global/advice/ControllerExceptionAdvice.java index 5b361319..77bed3c7 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/global/advice/ControllerExceptionAdvice.java +++ b/main/src/main/java/org/sopt/makers/crew/main/global/advice/ControllerExceptionAdvice.java @@ -1,8 +1,10 @@ package org.sopt.makers.crew.main.global.advice; +import java.io.IOException; + import org.sopt.makers.crew.main.global.exception.BaseException; -import org.sopt.makers.crew.main.global.exception.ExceptionResponse; import org.sopt.makers.crew.main.global.exception.ErrorStatus; +import org.sopt.makers.crew.main.global.exception.ExceptionResponse; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -108,6 +110,14 @@ public ResponseEntity handleHttpRequestMethodNotSupportedExce ErrorStatus.INVALID_INPUT_VALUE.getErrorCode())); } + @ExceptionHandler(IOException.class) + public ResponseEntity handleIOException(IOException e) { + log.warn("{}", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(ExceptionResponse.fail( + ErrorStatus.IO_EXCEPTION.getErrorCode())); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { log.error("{}", e.getMessage()); @@ -115,5 +125,4 @@ public ResponseEntity handleException(Exception e) { .body(ExceptionResponse.fail( ErrorStatus.INTERNAL_SERVER_ERROR.getErrorCode())); } - } diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/config/SecurityConfig.java b/main/src/main/java/org/sopt/makers/crew/main/global/config/SecurityConfig.java index f810a2af..5cff0a6e 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/global/config/SecurityConfig.java +++ b/main/src/main/java/org/sopt/makers/crew/main/global/config/SecurityConfig.java @@ -52,7 +52,8 @@ private String[] getAuthWhitelist() { "/meeting/v2/org-user/**", "/auth/v2", "/auth/v2/**", - actuatorEndPoint + "/health" + actuatorEndPoint + "/health", + "/sentry" // prod에서 테스트 후 삭제 }; } @@ -103,4 +104,4 @@ CorsConfigurationSource corsConfigurationSource() { return source; } -} \ No newline at end of file +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/config/SentryConfig.java b/main/src/main/java/org/sopt/makers/crew/main/global/config/SentryConfig.java new file mode 100644 index 00000000..e18d9647 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/global/config/SentryConfig.java @@ -0,0 +1,29 @@ +package org.sopt.makers.crew.main.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import io.sentry.Sentry; +import jakarta.annotation.PostConstruct; + +@Configuration +public class SentryConfig { + + @Value("${sentry.dsn}") + private String sentryDsn; + + @Value("${sentry.environment}") + private String environment; + + @Value("${sentry.servername}") + private String serverName; + + @PostConstruct + public void initSentry() { + Sentry.init(options -> { + options.setDsn(sentryDsn); + options.setEnvironment(environment); + options.setServerName(serverName); + }); + } +} diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java index bb4470f8..90e5e61d 100644 --- a/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java +++ b/main/src/main/java/org/sopt/makers/crew/main/global/exception/ErrorStatus.java @@ -38,6 +38,7 @@ public enum ErrorStatus { CO_LEADER_CANNOT_APPLY("공동 모임장은 신청할 수 없습니다."), LEADER_CANNOT_BE_CO_LEADER_APPLY("모임장은 공동 모임장이 될 수 없습니다."), NOT_ALLOW_MEETING_APPLY("허용되지 않는 모임 신청입니다."), + IO_EXCEPTION("파일 입출력 오류가 발생했습니다."), /** * 401 UNAUTHORIZED diff --git a/main/src/main/java/org/sopt/makers/crew/main/global/sentry/SentryController.java b/main/src/main/java/org/sopt/makers/crew/main/global/sentry/SentryController.java new file mode 100644 index 00000000..d16b2235 --- /dev/null +++ b/main/src/main/java/org/sopt/makers/crew/main/global/sentry/SentryController.java @@ -0,0 +1,26 @@ +package org.sopt.makers.crew.main.global.sentry; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import io.sentry.Sentry; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequestMapping("/sentry") +public class SentryController { + + @GetMapping + public ResponseEntity testSentry() { // prod에서 테스트 후 삭제 + try { + throw new Exception("This is a test exception for Sentry."); + } catch (Exception e) { + Sentry.captureException(e); + log.error("Exception captured in Sentry", e); + return ResponseEntity.status(500).body("Exception captured in Sentry"); + } + } +} diff --git a/main/src/main/resources/application-dev.yml b/main/src/main/resources/application-dev.yml index 9c73fe89..9f73f98c 100644 --- a/main/src/main/resources/application-dev.yml +++ b/main/src/main/resources/application-dev.yml @@ -99,3 +99,16 @@ management: custom: paths: eventApply: ${DEV_EVENT_APPLY_PATH} + +sentry: + dsn: ${SENTRY_DSN} + environment: ${DEV_SENTRY_ENVIRONMENT} + servername: ${DEV_SENTRY_SERVERNAME} + +logging: + slack: + webhook-uri: ${SENTRY_SLACK_WEBHOOK_URI} + config: classpath:logback-spring.xml + sentry: + repository-uri: ${SENTRY_REPOSITORY_URI} + environment: dev diff --git a/main/src/main/resources/application-prod.yml b/main/src/main/resources/application-prod.yml index bba3a8ea..eaabdca2 100644 --- a/main/src/main/resources/application-prod.yml +++ b/main/src/main/resources/application-prod.yml @@ -99,3 +99,16 @@ management: custom: paths: eventApply: ${PROD_EVENT_APPLY_PATH} + +sentry: + dsn: ${SENTRY_DSN} + environment: ${PROD_SENTRY_ENVIRONMENT} + servername: ${PROD_SENTRY_SERVERNAME} + +logging: + slack: + webhook-uri: ${SENTRY_SLACK_WEBHOOK_URI} + config: classpath:logback-spring.xml + sentry: + repository-uri: ${SENTRY_REPOSITORY_URI} + environment: prod diff --git a/main/src/main/resources/application-test.yml b/main/src/main/resources/application-test.yml index a7e232fc..37ea31a7 100644 --- a/main/src/main/resources/application-test.yml +++ b/main/src/main/resources/application-test.yml @@ -98,3 +98,16 @@ management: custom: paths: eventApply: ${DEV_EVENT_APPLY_PATH} + +sentry: + dsn: ${SENTRY_DSN} + environment: ${DEV_SENTRY_ENVIRONMENT} + servername: ${DEV_SENTRY_SERVERNAME} + +logging: + slack: + webhook-uri: ${SENTRY_SLACK_WEBHOOK_URI} + config: classpath:logback-spring.xml + sentry: + repository-uri: ${SENTRY_REPOSITORY_URI} + environment: test diff --git a/main/src/main/resources/logback-spring.xml b/main/src/main/resources/logback-spring.xml index 3c008a2b..a96141ca 100644 --- a/main/src/main/resources/logback-spring.xml +++ b/main/src/main/resources/logback-spring.xml @@ -3,9 +3,38 @@ + + always + true + 1.0 + ERROR + DEBUG + + + + + + + ${SLACK_WEBHOOK_URI} + + *🚨[${ENVIRONMENT}] %d{yyyy-MM-dd HH:mm:ss.SSS} %-4relative [%thread] %-5level %class - %msg <${SENTRY_REPOSITORY_URI}|Go-To-Sentry>* + %n + + + posting bot + :robot_face: + true + + + + + + ERROR + + + - @@ -13,9 +42,10 @@ - + + @@ -23,6 +53,8 @@ + + @@ -32,4 +64,4 @@ - \ No newline at end of file +