diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index b567959..1958123 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -64,7 +64,21 @@ jobs: - name: Build, tag, and push image to Amazon ECR run: | docker build --build-arg PASSWORD=$PASSWORD -t polabo:${{steps.current-time.outputs.formattedTime}} . - docker tag polabo:${{steps.current-time.outputs.formattedTime}} 058264417437.dkr.ecr.ap-northeast-2.amazonaws.com/polabo:${{steps.current-time.outputs.formattedTime}} - docker push 058264417437.dkr.ecr.ap-northeast-2.amazonaws.com/polabo:${{steps.current-time.outputs.formattedTime}} + docker tag polabo:${{steps.current-time.outputs.formattedTime}} ${{ secrets.ECR_REGISTRY }}/polabo:${{steps.current-time.outputs.formattedTime}} + docker push ${{ secrets.ECR_REGISTRY }}/polabo:${{steps.current-time.outputs.formattedTime}} env: - PASSWORD: ${{ secrets.JASYPT_ENCRYPTOR_PASSWORD }} \ No newline at end of file + PASSWORD: ${{ secrets.JASYPT_ENCRYPTOR_PASSWORD }} + + - name: Deploy + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_KEY }} + script: | + aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin ${{ steps.login-ecr.outputs.registry }}/${{ secrets.ECR_REPOSITORY }} + docker stop polabo-dev + docker rm polabo-dev + docker pull ${{ secrets.ECR_REGISTRY }}/polabo:${{steps.current-time.outputs.formattedTime}} + docker run -d -v /etc/localtime:/etc/localtime:ro -v /usr/share/zoneinfo/Asia/Seoul:/etc/timezone:ro -e ENVIRONMENT_VALUE=-Dspring.profiles.active=dev --name polabo-dev -p 8080:8080 --restart=always --network host ${{ secrets.ECR_REGISTRY }}/polabo:${{steps.current-time.outputs.formattedTime}} + diff --git a/build.gradle.kts b/build.gradle.kts index 350dd8f..5ce6603 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -89,7 +89,8 @@ dependencies { implementation("software.amazon.awssdk:s3:2.20.68") implementation("com.amazonaws:aws-java-sdk-s3:1.12.561") implementation("com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5") - + implementation("org.springframework.boot:spring-boot-starter-security") + implementation("io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64") } tasks.withType { diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/SonnyPolaboBeApplication.kt b/src/main/kotlin/com/ddd/sonnypolabobe/SonnyPolaboBeApplication.kt index bfdbd6a..bafe9f3 100644 --- a/src/main/kotlin/com/ddd/sonnypolabobe/SonnyPolaboBeApplication.kt +++ b/src/main/kotlin/com/ddd/sonnypolabobe/SonnyPolaboBeApplication.kt @@ -3,7 +3,9 @@ package com.ddd.sonnypolabobe import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication +import org.springframework.scheduling.annotation.EnableScheduling +@EnableScheduling @SpringBootApplication class SonnyPolaboBeApplication inline fun T.logger() = LoggerFactory.getLogger(T::class.java)!! diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/domain/board/repository/BoardJooqRepositoryImpl.kt b/src/main/kotlin/com/ddd/sonnypolabobe/domain/board/repository/BoardJooqRepositoryImpl.kt index f3163dd..1b4cb74 100644 --- a/src/main/kotlin/com/ddd/sonnypolabobe/domain/board/repository/BoardJooqRepositoryImpl.kt +++ b/src/main/kotlin/com/ddd/sonnypolabobe/domain/board/repository/BoardJooqRepositoryImpl.kt @@ -2,6 +2,7 @@ package com.ddd.sonnypolabobe.domain.board.repository import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardCreateRequest import com.ddd.sonnypolabobe.domain.board.controller.dto.BoardGetResponse +import com.ddd.sonnypolabobe.global.util.DateConverter import com.ddd.sonnypolabobe.global.util.UuidConverter import com.ddd.sonnypolabobe.global.util.UuidGenerator import com.ddd.sonnypolabobe.jooq.polabo.tables.Board @@ -22,7 +23,7 @@ class BoardJooqRepositoryImpl( val insertValue = jBoard.newRecord().apply { this.id = id this.title = request.title - this.createdAt = LocalDateTime.now() + this.createdAt = DateConverter.convertToKst(LocalDateTime.now()) this.yn = 1 this.activeyn = 1 } diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/domain/polaroid/repository/PolaroidJooqRepositoryImpl.kt b/src/main/kotlin/com/ddd/sonnypolabobe/domain/polaroid/repository/PolaroidJooqRepositoryImpl.kt index e17400b..b62be16 100644 --- a/src/main/kotlin/com/ddd/sonnypolabobe/domain/polaroid/repository/PolaroidJooqRepositoryImpl.kt +++ b/src/main/kotlin/com/ddd/sonnypolabobe/domain/polaroid/repository/PolaroidJooqRepositoryImpl.kt @@ -3,6 +3,7 @@ package com.ddd.sonnypolabobe.domain.polaroid.repository import com.ddd.sonnypolabobe.domain.polaroid.controller.dto.PolaroidCreateRequest import com.ddd.sonnypolabobe.global.exception.ApplicationException import com.ddd.sonnypolabobe.global.exception.CustomErrorCode +import com.ddd.sonnypolabobe.global.util.DateConverter import com.ddd.sonnypolabobe.jooq.polabo.tables.Polaroid import com.ddd.sonnypolabobe.jooq.polabo.tables.records.PolaroidRecord import org.jooq.DSLContext @@ -17,7 +18,7 @@ class PolaroidJooqRepositoryImpl(private val dslContext: DSLContext) : PolaroidJ this.boardId = boardId this.imageKey = request.imageKey this.oneLineMessage = request.oneLineMessage - this.createdAt = LocalDateTime.now() + this.createdAt = DateConverter.convertToKst(LocalDateTime.now()) this.yn = 1 this.activeyn = 1 } diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/config/SecurityConfig.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/config/SecurityConfig.kt new file mode 100644 index 0000000..521e9ea --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/config/SecurityConfig.kt @@ -0,0 +1,50 @@ +package com.ddd.sonnypolabobe.global.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity +import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.web.SecurityFilterChain +import org.springframework.security.web.util.matcher.AntPathRequestMatcher +import org.springframework.security.web.util.matcher.RequestMatcher +import org.springframework.web.cors.CorsConfiguration +import org.springframework.web.cors.UrlBasedCorsConfigurationSource + +@Configuration +@EnableMethodSecurity +class SecurityConfig() { + + @Bean + fun filterChain(http: HttpSecurity): SecurityFilterChain { + return http + .cors { + it.configurationSource(corsConfigurationSource()) + } + .csrf{ + it.disable() + } + .httpBasic { + it.disable() + } + .formLogin { it.disable() } + .authorizeHttpRequests { + it.anyRequest().permitAll() + } + .build() + } + + fun corsConfigurationSource(): UrlBasedCorsConfigurationSource { + val configuration = CorsConfiguration() + configuration.allowedOrigins = listOf("http://localhost:3000") // Allow all origins + configuration.allowedMethods = + listOf("GET", "POST", "PUT", "DELETE", "OPTIONS") // Allow common methods + configuration.allowedHeaders = listOf("*") // Allow all headers + configuration.allowCredentials = true // Allow credentials + val source = UrlBasedCorsConfigurationSource() + source.registerCorsConfiguration( + "/**", + configuration + ) // Apply configuration to all endpoints + return source + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/config/WebConfig.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/config/WebConfig.kt new file mode 100644 index 0000000..86f8df7 --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/config/WebConfig.kt @@ -0,0 +1,16 @@ +package com.ddd.sonnypolabobe.global.config + +import com.ddd.sonnypolabobe.global.security.RateLimitingInterceptor +import org.springframework.context.annotation.Configuration +import org.springframework.web.servlet.config.annotation.InterceptorRegistry +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer + +@Configuration +class WebConfig(private val rateLimitingInterceptor: RateLimitingInterceptor) : WebMvcConfigurer { + + override fun addInterceptors(registry: InterceptorRegistry) { + registry.addInterceptor(rateLimitingInterceptor) + .addPathPatterns("/api/v1/boards") + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/exception/GlobalExceptionHandler.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/exception/GlobalExceptionHandler.kt index 7d7478a..9700834 100644 --- a/src/main/kotlin/com/ddd/sonnypolabobe/global/exception/GlobalExceptionHandler.kt +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/exception/GlobalExceptionHandler.kt @@ -1,6 +1,7 @@ package com.ddd.sonnypolabobe.global.exception import com.ddd.sonnypolabobe.global.response.ApplicationResponse +import com.ddd.sonnypolabobe.global.util.DiscordApiClient import com.ddd.sonnypolabobe.logger import org.springframework.http.ResponseEntity import org.springframework.web.bind.MethodArgumentNotValidException @@ -8,10 +9,16 @@ import org.springframework.web.bind.annotation.ExceptionHandler import org.springframework.web.bind.annotation.RestControllerAdvice @RestControllerAdvice -class GlobalExceptionHandler { +class GlobalExceptionHandler( + private val discordApiClient: DiscordApiClient +) { @ExceptionHandler(ApplicationException::class) fun applicationException(ex: ApplicationException): ResponseEntity> { logger().info("error : ${ex.error}") + this.discordApiClient.sendErrorTrace( + ex.error.code, ex.message, + ex.stackTrace.contentToString() + ) return ResponseEntity.status(ex.error.status).body(ApplicationResponse.error(ex.error)) } @@ -19,12 +26,21 @@ class GlobalExceptionHandler { fun validationException(ex: MethodArgumentNotValidException): ResponseEntity> { logger().info("error : ${ex.bindingResult.allErrors[0].defaultMessage}") return ResponseEntity.status(CustomErrorCode.INVALID_VALUE_EXCEPTION.status) - .body(ApplicationResponse.error(CustomErrorCode.INVALID_VALUE_EXCEPTION, ex.bindingResult.allErrors[0].defaultMessage!!)) + .body( + ApplicationResponse.error( + CustomErrorCode.INVALID_VALUE_EXCEPTION, + ex.bindingResult.allErrors[0].defaultMessage!! + ) + ) } @ExceptionHandler(RuntimeException::class) fun runtimeException(ex: RuntimeException): ResponseEntity> { logger().info("error : ${ex.message}") + this.discordApiClient.sendErrorTrace( + "500", ex.message, + ex.stackTrace.contentToString() + ) return ResponseEntity.status(CustomErrorCode.INTERNAL_SERVER_EXCEPTION.status) .body(ApplicationResponse.error(CustomErrorCode.INTERNAL_SERVER_EXCEPTION)) } diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/security/LoggingFilter.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/security/LoggingFilter.kt new file mode 100644 index 0000000..39e89a6 --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/security/LoggingFilter.kt @@ -0,0 +1,124 @@ +package com.ddd.sonnypolabobe.global.security + +import com.ddd.sonnypolabobe.global.util.DiscordApiClient +import com.ddd.sonnypolabobe.global.util.HttpLog +import com.ddd.sonnypolabobe.logger +import jakarta.servlet.FilterChain +import jakarta.servlet.ServletRequest +import jakarta.servlet.ServletResponse +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.stereotype.Component +import org.springframework.web.filter.GenericFilterBean +import org.springframework.web.util.ContentCachingRequestWrapper +import org.springframework.web.util.ContentCachingResponseWrapper +import org.springframework.web.util.WebUtils +import java.io.UnsupportedEncodingException +import java.util.* + +@Component +class LoggingFilter( + private val discordApiClient: DiscordApiClient +) : GenericFilterBean() { + private val excludedUrls = setOf("/actuator", "/swagger-ui") + + override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) { + val requestWrapper: ContentCachingRequestWrapper = + ContentCachingRequestWrapper(request as HttpServletRequest) + val responseWrapper: ContentCachingResponseWrapper = + ContentCachingResponseWrapper(response as HttpServletResponse) + if (excludeLogging(request.requestURI)) { + chain.doFilter(request, response) + } else { + val startedAt = System.currentTimeMillis() + chain.doFilter(requestWrapper, responseWrapper) + val endedAt = System.currentTimeMillis() + + logger().info( + "\n" + + "[REQUEST] ${request.method} - ${request.requestURI} ${responseWrapper.status} - ${(endedAt - startedAt) / 10000.0} \n" + + "Headers : ${getHeaders(request)} \n" + + "Parameters : ${getRequestParams(request)} \n" + + "Request body : ${getRequestBody(requestWrapper)} \n" + + "Response body : ${getResponseBody(responseWrapper)}" + ) + + if(responseWrapper.status >= 400) { + this.discordApiClient.sendErrorLog( + HttpLog( + request.method, + request.requestURI, + responseWrapper.status, + (endedAt - startedAt) / 10000.0, + getHeaders(request), + getRequestParams(request), + getRequestBody(requestWrapper), + getResponseBody(responseWrapper) + ) + ) + } + } + } + + private fun excludeLogging(requestURI: String): Boolean { + return excludedUrls.contains(requestURI) + } + + private fun getResponseBody(response: ContentCachingResponseWrapper): String { + var payload: String? = null + response.characterEncoding = "utf-8" + val wrapper = + WebUtils.getNativeResponse(response, ContentCachingResponseWrapper::class.java) + if (wrapper != null) { + val buf = wrapper.contentAsByteArray + if (buf.isNotEmpty()) { + payload = String(buf, 0, buf.size, charset(wrapper.characterEncoding)) + wrapper.copyBodyToResponse() + } + } + return payload ?: " - " + } + + private fun getRequestBody(request: ContentCachingRequestWrapper): String { + request.characterEncoding = "utf-8" + val wrapper = WebUtils.getNativeRequest( + request, + ContentCachingRequestWrapper::class.java + ) + if (wrapper != null) { + val buf = wrapper.contentAsByteArray + if (buf.isNotEmpty()) { + return try { + String(buf, 0, buf.size, charset(wrapper.characterEncoding)) + } catch (e: UnsupportedEncodingException) { + " - " + } + } + } + return " - " + } + + private fun getRequestParams(request: HttpServletRequest): Map { + val parameterMap: MutableMap = HashMap() + request.characterEncoding = "utf-8" + val parameterArray: Enumeration<*> = request.parameterNames + + while (parameterArray.hasMoreElements()) { + val parameterName = parameterArray.nextElement() as String + parameterMap[parameterName] = request.getParameter(parameterName) + } + + return parameterMap + } + + private fun getHeaders(request: HttpServletRequest): Map { + val headerMap: MutableMap = HashMap() + + val headerArray: Enumeration<*> = request.headerNames + while (headerArray.hasMoreElements()) { + val headerName = headerArray.nextElement() as String + headerMap[headerName] = request.getHeader(headerName) + } + return headerMap + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/security/RateLimitingInterceptor.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/security/RateLimitingInterceptor.kt new file mode 100644 index 0000000..95d96fb --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/security/RateLimitingInterceptor.kt @@ -0,0 +1,24 @@ +package com.ddd.sonnypolabobe.global.security + +import jakarta.servlet.http.HttpServletRequest +import jakarta.servlet.http.HttpServletResponse +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Component +import org.springframework.web.servlet.HandlerInterceptor + +@Component +class RateLimitingInterceptor(private val rateLimitingService: RateLimitingService) : + HandlerInterceptor { + + override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean { + // 특정 URL 패턴을 필터링합니다. + if (request.requestURI == "/api/v1/boards" && request.method == "POST") { + if (!rateLimitingService.incrementRequestCount()) { + response.status = HttpStatus.TOO_MANY_REQUESTS.value() + response.writer.write("Daily request limit exceeded") + return false + } + } + return true + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/security/RateLimitingService.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/security/RateLimitingService.kt new file mode 100644 index 0000000..b7dbfd2 --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/security/RateLimitingService.kt @@ -0,0 +1,35 @@ +package com.ddd.sonnypolabobe.global.security + +import com.ddd.sonnypolabobe.logger +import org.springframework.beans.factory.annotation.Value +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import java.util.concurrent.ConcurrentHashMap + +@Service +class RateLimitingService( + @Value("\${limit.count}") + private val limit: Int +) { + + private val requestCounts = ConcurrentHashMap() + private val LIMIT = limit + private val REQUEST_KEY = "api_request_count" + + fun incrementRequestCount(): Boolean { + val currentCount = requestCounts.getOrDefault(REQUEST_KEY, 0) + + if (currentCount >= LIMIT) { + return false + } + + requestCounts[REQUEST_KEY] = currentCount + 1 + logger().info("Request count: ${requestCounts[REQUEST_KEY]}") + return true + } + + @Scheduled(cron = "0 0 0 * * *", zone = "Asia/Seoul") + fun resetRequestCount() { + requestCounts.clear() + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/util/DateConverter.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/util/DateConverter.kt new file mode 100644 index 0000000..5ca7b2a --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/util/DateConverter.kt @@ -0,0 +1,11 @@ +package com.ddd.sonnypolabobe.global.util + +import java.time.LocalDateTime + +object DateConverter { + + fun convertToKst(date: LocalDateTime): LocalDateTime { + return date.plusHours(9) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/util/DiscordApiClient.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/util/DiscordApiClient.kt new file mode 100644 index 0000000..d00a866 --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/util/DiscordApiClient.kt @@ -0,0 +1,99 @@ +package com.ddd.sonnypolabobe.global.util + +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.MediaType +import org.springframework.http.client.reactive.ReactorClientHttpConnector +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import reactor.netty.http.client.HttpClient +import java.time.Duration + + +@Component +class DiscordApiClient( + @Value("\${logging.discord.webhook-uri}") + private val discordWebhookUri: String +) { + fun sendDiscordComm(): WebClient = WebClient.builder().baseUrl(discordWebhookUri) + .clientConnector( + ReactorClientHttpConnector( + HttpClient.create().responseTimeout(Duration.ofMillis(2500)) + ) + ) + .build() + + fun sendErrorLog(req: HttpLog) { + val embedData: MutableMap = HashMap() + + embedData["title"] = "서버 에러 발생" + + val field1: MutableMap = HashMap() + field1["name"] = "요청 정보" + field1["value"] = req.requestMethod + " " + req.requestURI + " " + req.elapsedTime + "ms" + + val field2: MutableMap = HashMap() + field2["name"] = "응답 코드" + field2["value"] = req.responseStatus.toString() + + val field3: MutableMap = HashMap() + field3["name"] = "요청 헤더" + field3["value"] = req.headers.map { it.key + " : " + it.value }.joinToString("\n") + + val field4: MutableMap = HashMap() + field4["name"] = "요청 본문" + field4["value"] = req.requestBody + + val field5: MutableMap = HashMap() + field5["name"] = "요청 파람" + field5["value"] = req.parameters.map { it.key + " : " + it.value }.joinToString("\n") + + val field6: MutableMap = HashMap() + field6["name"] = "응답 본문" + field6["value"] = req.responseBody + + embedData["fields"] = listOf>(field1, field2, field3, field4, field5, field6) + + val payload: MutableMap = HashMap() + payload["embeds"] = arrayOf(embedData) + + sendDiscordComm() + .post() + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(payload) + .retrieve() + .bodyToMono(Void::class.java) + .block() + } + + fun sendErrorTrace(errorCode: String, message: String?, trace: String) { + val embedData: MutableMap = HashMap() + + embedData["title"] = "서버 에러 발생" + + val field1: MutableMap = HashMap() + field1["name"] = "트레이스" + field1["value"] = trace + + val field2: MutableMap = HashMap() + field2["name"] = "에러 코드" + field2["value"] = errorCode + + val field3: MutableMap = HashMap() + field3["name"] = "메시지" + field3["value"] = message ?: "메시지 없음" + + embedData["fields"] = listOf>(field1) + + val payload: MutableMap = HashMap() + payload["embeds"] = arrayOf(embedData) + + sendDiscordComm() + .post() + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(payload) + .retrieve() + .bodyToMono(Void::class.java) + .block() + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/ddd/sonnypolabobe/global/util/HttpLog.kt b/src/main/kotlin/com/ddd/sonnypolabobe/global/util/HttpLog.kt new file mode 100644 index 0000000..a9cfe8a --- /dev/null +++ b/src/main/kotlin/com/ddd/sonnypolabobe/global/util/HttpLog.kt @@ -0,0 +1,12 @@ +package com.ddd.sonnypolabobe.global.util + +data class HttpLog( + val requestMethod : String, + val requestURI : String, + val responseStatus : Int, + val elapsedTime : Double, + val headers : Map, + val parameters : Map, + val requestBody : String, + val responseBody : String +) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index febe92d..db2a54a 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -31,4 +31,8 @@ cloud: static: ap-northeast-2 running: - name: dev \ No newline at end of file + name: dev + +logging: + discord: + webhook-uri: ENC(yfeX3WHXQdxkVtasNl5WLv6M/YlN+dVFUurjxGIddstjjipt+KryWKvLu1wDmdGjpuEhUHyaABg4gFWRMk9gNlxSQEE/G1twbuvkOvT0pyFWycVVJ6ryU/v9pDBOS1PSKJY7L3NP66gOGnam6nOvf0Y+F45zZvXj8/sdtR6N798U6fGjFDxOLQ==) \ No newline at end of file diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 6756b6e..e58fda8 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -31,4 +31,8 @@ cloud: static: ap-northeast-2 running: - name: local \ No newline at end of file + name: local + +logging: + discord: + webhook-uri: ENC(yfeX3WHXQdxkVtasNl5WLv6M/YlN+dVFUurjxGIddstjjipt+KryWKvLu1wDmdGjpuEhUHyaABg4gFWRMk9gNlxSQEE/G1twbuvkOvT0pyFWycVVJ6ryU/v9pDBOS1PSKJY7L3NP66gOGnam6nOvf0Y+F45zZvXj8/sdtR6N798U6fGjFDxOLQ==) \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 1355e31..91403d0 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -12,4 +12,7 @@ logging: jasypt: encryptor: - bean: jasyptStringEncryptor \ No newline at end of file + bean: jasyptStringEncryptor + +limit: + count: 100 \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..f8d6274 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,46 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta(%-4relative) --- [${appName}, %blue(%X{traceId}), %green(%X{spanId}) %X{sessionId}] %cyan(%logger{20}) : %msg%n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file