diff --git a/.gitignore b/.gitignore index 9aa665f6a..f82f9c3bd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,8 @@ gradle/ /src/main/resources/*.yml !/src/main/resources/application.yml src/main/java/page/clab/api/global/auth/application/DataLoader.java -src/main/java/page/clab/api/auth/service/DataLoader.java +src/main/java/page/clab/api/global/auth/service/DataLoader.java +src/main/java/page/clab/api/global/config/SecurityProperties.java /config/whitelist.json ### STS ### diff --git a/build.gradle b/build.gradle index 994c349f2..9509e0aab 100644 --- a/build.gradle +++ b/build.gradle @@ -41,13 +41,39 @@ repositories { dependencies { // Spring Project - implementation 'org.springframework.boot:spring-boot-starter-actuator' // 모니터링 implementation 'org.springframework.boot:spring-boot-starter-web' // 웹 MVC implementation 'org.springframework.boot:spring-boot-starter-validation' // 유효성 검사 implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' // 템플릿 엔진 implementation 'org.springframework.boot:spring-boot-starter-webflux' // WebFlux developmentOnly 'org.springframework.boot:spring-boot-devtools' // 개발 도구 + // Security + implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security + implementation 'com.warrenstrange:googleauth:1.5.0' // Google Authenticator + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' // JWT 라이브러리 + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' // JWT 구현체 + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JWT Jackson 모듈 + + // Monitoring + implementation 'org.springframework.boot:spring-boot-starter-actuator' // Spring Boot Actuator + implementation 'io.micrometer:micrometer-registry-prometheus' // Prometheus + implementation 'ch.qos.logback:logback-classic:1.5.6' // Logback + implementation 'ch.qos.logback:logback-core:1.5.6' // Logback + + // DB + implementation 'org.postgresql:postgresql:42.7.1' // PostgreSQL JDBC Driver + implementation 'org.springframework.boot:spring-boot-starter-data-redis' // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Spring Data JPA + implementation 'org.springframework.boot:spring-boot-starter-validation' // Hibernate Validator + implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' // Bean Validation + implementation 'jakarta.validation:jakarta.validation-api:3.0.2' // Jakarta Bean Validation + + // QueryDSL + implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.1.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + // Util compileOnly 'org.projectlombok:lombok' // 롬복 annotationProcessor 'org.projectlombok:lombok' // 롬복 @@ -68,28 +94,6 @@ dependencies { implementation 'com.slack.api:slack-api-client:1.39.0' implementation 'com.slack.api:slack-app-backend:1.39.0' - - // DB - implementation 'org.postgresql:postgresql:42.7.1' // PostgreSQL JDBC Driver - implementation 'org.springframework.boot:spring-boot-starter-data-redis' // Redis - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Spring Data JPA - implementation 'org.springframework.boot:spring-boot-starter-validation' // Hibernate Validator - implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' // Bean Validation - implementation 'jakarta.validation:jakarta.validation-api:3.0.2' // Jakarta Bean Validation - - // QueryDSL - implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' - annotationProcessor "com.querydsl:querydsl-apt:5.1.0:jakarta" - annotationProcessor "jakarta.annotation:jakarta.annotation-api" - annotationProcessor "jakarta.persistence:jakarta.persistence-api" - - // Security - implementation 'org.springframework.boot:spring-boot-starter-security' // Spring Security - implementation 'com.warrenstrange:googleauth:1.5.0' // Google Authenticator - implementation 'io.jsonwebtoken:jjwt-api:0.11.5' // JWT 라이브러리 - runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' // JWT 구현체 - runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JWT Jackson 모듈 - // XSS Filter implementation 'com.navercorp.lucy:lucy-xss-servlet:2.0.1' // Lucy XSS Servlet Filter implementation 'com.navercorp.lucy:lucy-xss:1.6.3' // Lucy XSS Filter diff --git a/jenkins/Jenkinsfile b/jenkins/Jenkinsfile index 2f5c5d9ed..f0ce1ac78 100644 --- a/jenkins/Jenkinsfile +++ b/jenkins/Jenkinsfile @@ -14,19 +14,30 @@ pipeline { DOCKER_HUB_USER = credentials('dockerhub_user') DOCKER_HUB_PASSWORD = credentials('dockerhub_password') - SERVER_CONFIG = credentials('server_config') - SERVER_CLOUD = credentials('server_cloud') + EXTERNAL_SERVER_CONFIG_PATH = credentials('external_server_config_path') + EXTERNAL_SERVER_CLOUD_PATH = credentials('external_server_cloud_path') + EXTERNAL_SERVER_LOGS_PATH = credentials('external_server_logs_path') + + INTERNAL_SERVER_CONFIG_PATH = credentials('internal_server_config_path') + INTERNAL_SERVER_CLOUD_PATH = credentials('internal_server_cloud_path') + INTERNAL_SERVER_LOGS_PATH = credentials('internal_server_logs_path') BLUE_CONTAINER = credentials('blue_container') GREEN_CONTAINER = credentials('green_container') BLUE_URL = credentials('blue_url') GREEN_URL = credentials('green_url') IMAGE_NAME = credentials('image_name') - NETWORK_NAME = credentials('network_name') + APPLICATION_NETWORK = credentials('application_network') + MONITORING_NETWORK = credentials('monitoring_network') + + PROFILE = credentials('profile') PORT_A = credentials('port_a') PORT_B = credentials('port_b') + WHITELIST_ADMIN_USERNAME = credentials('whitelist_admin_username') + WHITELIST_ADMIN_PASSWORD = credentials('whitelist_admin_password') + DOCKERFILE_PATH = "${env.WORKSPACE}/jenkins/Dockerfile" NGINX_CONTAINER_NAME = 'nginx' POSTGRESQL_CONTAINER_NAME = 'postgresql' @@ -81,7 +92,9 @@ pipeline { stage('애플리케이션 빌드') { steps { - sh './gradlew clean build -Penv=stage --stacktrace --info' + script { + buildApplication() + } } } @@ -134,7 +147,9 @@ pipeline { } def sendSlackNotification(message, color) { - withEnv(["SLACK_WEBHOOK_URL=${env.SLACK_WEBHOOK_URL}"]) { + withEnv([ + "SLACK_WEBHOOK_URL=${env.SLACK_WEBHOOK_URL}" + ]) { def payload = """{ "attachments": [ { @@ -169,7 +184,12 @@ def getChangeLog() { def backupPostgres() { def BACKUP_FILE = "postgres_backup_${new Date().format('yyyy-MM-dd_HH-mm-ss')}.sql" - withEnv(["BACKUP_DIR=${env.BACKUP_DIR}", "POSTGRESQL_CONTAINER_NAME=${env.POSTGRESQL_CONTAINER_NAME}", "PG_PASSWORD=${env.PG_PASSWORD}", "PG_USER=${env.PG_USER}"]) { + withEnv([ + "BACKUP_DIR=${env.BACKUP_DIR}", + "POSTGRESQL_CONTAINER_NAME=${env.POSTGRESQL_CONTAINER_NAME}", + "PG_PASSWORD=${env.PG_PASSWORD}", + "PG_USER=${env.PG_USER}" + ]) { sh """ echo "Backing up PostgreSQL database to ${BACKUP_DIR}/${BACKUP_FILE}..." docker exec -e PGPASSWORD=${PG_PASSWORD} ${POSTGRESQL_CONTAINER_NAME} sh -c 'pg_dumpall -c -U ${PG_USER} > ${BACKUP_DIR}/${BACKUP_FILE}' @@ -189,7 +209,14 @@ def dockerLogin() { def determineContainers() { script { - withEnv(["BLUE_CONTAINER=${env.BLUE_CONTAINER}", "GREEN_CONTAINER=${env.GREEN_CONTAINER}", "BLUE_URL=${env.BLUE_URL}", "GREEN_URL=${env.GREEN_URL}", "PORT_A=${env.PORT_A}", "PORT_B=${env.PORT_B}"]) { + withEnv([ + "BLUE_CONTAINER=${env.BLUE_CONTAINER}", + "GREEN_CONTAINER=${env.GREEN_CONTAINER}", + "BLUE_URL=${env.BLUE_URL}", + "GREEN_URL=${env.GREEN_URL}", + "PORT_A=${env.PORT_A}", + "PORT_B=${env.PORT_B}" + ]) { def blueRunning = sh(script: "docker ps --filter 'name=${BLUE_CONTAINER}' --format '{{.Names}}' | grep -q '${BLUE_CONTAINER}'", returnStatus: true) == 0 if (blueRunning) { env.CURRENT_CONTAINER = BLUE_CONTAINER @@ -209,8 +236,24 @@ def determineContainers() { } } +def buildApplication() { + withEnv([ + "PROFILE=${env.PROFILE}" + ]) { + sh """ + echo "Building application with profile ${PROFILE}..." + ./gradlew clean build -Penv=${PROFILE} --stacktrace --info + """ + } +} + def buildAndPushDockerImage() { - withEnv(["DOCKER_HUB_REPO=${env.DOCKER_HUB_REPO}", "DEPLOY_CONTAINER=${env.DEPLOY_CONTAINER}", "DOCKERFILE_PATH=${env.DOCKERFILE_PATH}", "IMAGE_NAME=${env.IMAGE_NAME}"]) { + withEnv([ + "DOCKER_HUB_REPO=${env.DOCKER_HUB_REPO}", + "DEPLOY_CONTAINER=${env.DEPLOY_CONTAINER}", + "DOCKERFILE_PATH=${env.DOCKERFILE_PATH}", + "IMAGE_NAME=${env.IMAGE_NAME}" + ]) { sh """ docker build -f ${DOCKERFILE_PATH} -t ${IMAGE_NAME}:${DEPLOY_CONTAINER} . docker tag ${IMAGE_NAME}:${DEPLOY_CONTAINER} ${DOCKER_HUB_REPO}:${DEPLOY_CONTAINER} @@ -220,7 +263,20 @@ def buildAndPushDockerImage() { } def deployNewInstance() { - withEnv(["NEW_PORT=${env.NEW_PORT}", "NETWORK_NAME=${env.NETWORK_NAME}", "SERVER_CONFIG=${env.SERVER_CONFIG}", "SERVER_CLOUD=${env.SERVER_CLOUD}", "DEPLOY_CONTAINER=${env.DEPLOY_CONTAINER}", "IMAGE_NAME=${env.IMAGE_NAME}"]) { + withEnv([ + "PROFILE=${env.PROFILE}", + "NEW_PORT=${env.NEW_PORT}", + "APPLICATION_NETWORK=${env.APPLICATION_NETWORK}", + "MONITORING_NETWORK=${env.MONITORING_NETWORK}", + "EXTERNAL_SERVER_CONFIG_PATH=${env.EXTERNAL_SERVER_CONFIG_PATH}", + "EXTERNAL_SERVER_CLOUD_PATH=${env.EXTERNAL_SERVER_CLOUD_PATH}", + "EXTERNAL_SERVER_LOGS_PATH=${env.EXTERNAL_SERVER_LOGS_PATH}", + "INTERNAL_SERVER_CONFIG_PATH=${env.INTERNAL_SERVER_CONFIG_PATH}", + "INTERNAL_SERVER_CLOUD_PATH=${env.INTERNAL_SERVER_CLOUD_PATH}", + "INTERNAL_SERVER_LOGS_PATH=${env.INTERNAL_SERVER_LOGS_PATH}", + "DEPLOY_CONTAINER=${env.DEPLOY_CONTAINER}", + "IMAGE_NAME=${env.IMAGE_NAME}" + ]) { sh """ echo "Stopping and removing existing container if it exists" if docker ps | grep -q ${DEPLOY_CONTAINER}; then @@ -231,11 +287,22 @@ def deployNewInstance() { echo "Running new container ${DEPLOY_CONTAINER} with image ${IMAGE_NAME}:${DEPLOY_CONTAINER}" docker run -d --name ${DEPLOY_CONTAINER} \\ -p ${NEW_PORT}:8080 \\ - --network ${NETWORK_NAME} \\ - -v ${SERVER_CONFIG}:/config \\ - -v ${SERVER_CLOUD}:/cloud \\ + --network ${APPLICATION_NETWORK} \\ + -v ${EXTERNAL_SERVER_CONFIG_PATH}:${INTERNAL_SERVER_CONFIG_PATH} \\ + -v ${EXTERNAL_SERVER_CLOUD_PATH}:${INTERNAL_SERVER_CLOUD_PATH} \\ + -v ${EXTERNAL_SERVER_LOGS_PATH}:${INTERNAL_SERVER_LOGS_PATH} \\ + -e LOG_PATH=${INTERNAL_SERVER_LOGS_PATH} \\ + -e SPRING_PROFILES_ACTIVE=${PROFILE} \\ ${IMAGE_NAME}:${DEPLOY_CONTAINER} + echo "Checking if monitoring network ${MONITORING_NETWORK} exists" + if docker network ls --format '{{.Name}}' | grep -q '^${MONITORING_NETWORK}\$'; then + echo "Connecting to monitoring network ${MONITORING_NETWORK}" + docker network connect ${MONITORING_NETWORK} ${DEPLOY_CONTAINER} + else + echo "Monitoring network ${MONITORING_NETWORK} does not exist. Skipping connection." + fi + echo "Listing all containers" docker ps -a """ @@ -244,32 +311,49 @@ def deployNewInstance() { } def performHealthCheck() { - def PUBLIC_IP = sh(script: "curl -s ifconfig.me", returnStdout: true).trim() - echo "Public IP address: ${PUBLIC_IP}" - - def start_time = System.currentTimeMillis() - def timeout = start_time + 240000 // 4 minutes - - while (System.currentTimeMillis() < timeout) { - def elapsed = (System.currentTimeMillis() - start_time) / 1000 - echo "Checking health... ${elapsed} seconds elapsed." - if (sh(script: "curl -s http://${PUBLIC_IP}:${env.NEW_PORT}/actuator/health | grep 'UP'", returnStatus: true) == 0) { - echo "New application started successfully after ${elapsed} seconds." - break + withEnv([ + "WHITELIST_ADMIN_USERNAME=${env.WHITELIST_ADMIN_USERNAME}", + "WHITELIST_ADMIN_PASSWORD=${env.WHITELIST_ADMIN_PASSWORD}" + ]) { + def PUBLIC_IP = sh(script: "curl -s ifconfig.me", returnStdout: true).trim() + echo "Public IP address: ${PUBLIC_IP}" + + def start_time = System.currentTimeMillis() + def timeout = start_time + 240000 // 4 minutes + + while (System.currentTimeMillis() < timeout) { + def elapsed = (System.currentTimeMillis() - start_time) / 1000 + echo "Checking health... ${elapsed} seconds elapsed." + def status = sh( + script: """curl -s -u ${WHITELIST_ADMIN_USERNAME}:${WHITELIST_ADMIN_PASSWORD} \ + http://${PUBLIC_IP}:${env.NEW_PORT}/actuator/health | grep 'UP'""", + returnStatus: true + ) + if (status == 0) { + echo "New application started successfully after ${elapsed} seconds." + return + } + sleep 5 } - sleep 1 - } - if (System.currentTimeMillis() >= timeout) { - sendSlackNotification(":scream_cat: New Staging application did not start successfully within 4 minutes.", env.SLACK_COLOR_FAILURE) - sh "docker stop ${env.DEPLOY_CONTAINER}" - sh "docker rm ${env.DEPLOY_CONTAINER}" - error "Health check failed" + if (System.currentTimeMillis() >= timeout) { + sendSlackNotification(":scream_cat: New Staging application did not start successfully within 4 minutes.", env.SLACK_COLOR_FAILURE) + sh "docker stop ${env.DEPLOY_CONTAINER}" + sh "docker rm ${env.DEPLOY_CONTAINER}" + error "Health check failed" + } } } def switchTrafficAndCleanup() { - withEnv(["NEW_PORT=${env.NEW_PORT}", "OLD_PORT=${env.OLD_PORT}", "NEW_TARGET=${env.NEW_TARGET}", "CURRENT_CONTAINER=${env.CURRENT_CONTAINER}", "DEPLOY_CONTAINER=${env.DEPLOY_CONTAINER}", "NGINX_CONTAINER_NAME=${env.NGINX_CONTAINER_NAME}"]) { + withEnv([ + "NEW_PORT=${env.NEW_PORT}", + "OLD_PORT=${env.OLD_PORT}", + "NEW_TARGET=${env.NEW_TARGET}", + "CURRENT_CONTAINER=${env.CURRENT_CONTAINER}", + "DEPLOY_CONTAINER=${env.DEPLOY_CONTAINER}", + "NGINX_CONTAINER_NAME=${env.NGINX_CONTAINER_NAME}" + ]) { sh """ echo "Switching traffic to ${DEPLOY_CONTAINER} on port ${NEW_PORT}." docker exec ${NGINX_CONTAINER_NAME} bash -c ' diff --git a/src/main/java/page/clab/api/global/auth/application/WhitelistService.java b/src/main/java/page/clab/api/global/auth/application/WhitelistService.java index ce4cc73a1..cc9d39bd5 100644 --- a/src/main/java/page/clab/api/global/auth/application/WhitelistService.java +++ b/src/main/java/page/clab/api/global/auth/application/WhitelistService.java @@ -17,10 +17,10 @@ @Slf4j public class WhitelistService { - @Value("${security.swagger.whitelist.enabled}") + @Value("${security.whitelist.enabled}") private boolean whitelistEnabled; - @Value("${security.swagger.whitelist.path}") + @Value("${security.whitelist.path}") private String whitelistPath; public List loadWhitelistIps() { diff --git a/src/main/java/page/clab/api/global/config/AuthenticationConfig.java b/src/main/java/page/clab/api/global/config/AuthenticationConfig.java index 66467cb89..e929a953a 100644 --- a/src/main/java/page/clab/api/global/config/AuthenticationConfig.java +++ b/src/main/java/page/clab/api/global/config/AuthenticationConfig.java @@ -23,7 +23,7 @@ public class AuthenticationConfig { private final CustomUserDetailsService customUserDetailsService; - private final OpenApiAccountProperties openApiAccountProperties; + private final WhitelistAccountProperties whitelistAccountProperties; @Bean public AuthenticationManager authenticationManager() throws Exception { @@ -32,9 +32,9 @@ public AuthenticationManager authenticationManager() throws Exception { @Bean public InMemoryUserDetailsManager userDetailsService() { - UserDetails user = User.withUsername(openApiAccountProperties.getUsername()) - .password(passwordEncoder().encode(openApiAccountProperties.getPassword())) - .roles(openApiAccountProperties.getRole()) + UserDetails user = User.withUsername(whitelistAccountProperties.getUsername()) + .password(passwordEncoder().encode(whitelistAccountProperties.getPassword())) + .roles(whitelistAccountProperties.getRole()) .build(); return new InMemoryUserDetailsManager(user); } diff --git a/src/main/java/page/clab/api/global/config/SecurityConfig.java b/src/main/java/page/clab/api/global/config/SecurityConfig.java index 58322354b..62beef913 100644 --- a/src/main/java/page/clab/api/global/config/SecurityConfig.java +++ b/src/main/java/page/clab/api/global/config/SecurityConfig.java @@ -59,9 +59,9 @@ public class SecurityConfig { private final AuthenticationConfig authenticationConfig; - private final OpenApiAccountProperties openApiAccountProperties; + private final WhitelistAccountProperties whitelistAccountProperties; - private final OpenApiPatternsProperties OpenApiPatternsProperties; + private final WhitelistPatternsProperties WhitelistPatternsProperties; private final IPInfoConfig ipInfoConfig; @@ -115,7 +115,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { private ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry configureRequests(ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry authorizeRequests) { return authorizeRequests - .requestMatchers(OpenApiPatternsProperties.getPatterns()).hasRole(openApiAccountProperties.getRole()) + .requestMatchers(WhitelistPatternsProperties.getPatterns()).hasRole(whitelistAccountProperties.getRole()) .requestMatchers(SecurityConstants.PERMIT_ALL).permitAll() .requestMatchers(HttpMethod.GET, SecurityConstants.PERMIT_ALL_API_ENDPOINTS_GET).permitAll() .requestMatchers(HttpMethod.POST, SecurityConstants.PERMIT_ALL_API_ENDPOINTS_POST).permitAll() diff --git a/src/main/java/page/clab/api/global/config/OpenApiAccountProperties.java b/src/main/java/page/clab/api/global/config/WhitelistAccountProperties.java similarity index 75% rename from src/main/java/page/clab/api/global/config/OpenApiAccountProperties.java rename to src/main/java/page/clab/api/global/config/WhitelistAccountProperties.java index 448567eab..10606a0fc 100644 --- a/src/main/java/page/clab/api/global/config/OpenApiAccountProperties.java +++ b/src/main/java/page/clab/api/global/config/WhitelistAccountProperties.java @@ -8,8 +8,8 @@ @Setter @Getter @Configuration -@ConfigurationProperties(prefix = "security.account.swagger") -public class OpenApiAccountProperties { +@ConfigurationProperties(prefix = "security.account.whitelist-admin") +public class WhitelistAccountProperties { private String username; diff --git a/src/main/java/page/clab/api/global/config/OpenApiPatternsProperties.java b/src/main/java/page/clab/api/global/config/WhitelistPatternsProperties.java similarity index 74% rename from src/main/java/page/clab/api/global/config/OpenApiPatternsProperties.java rename to src/main/java/page/clab/api/global/config/WhitelistPatternsProperties.java index 91000146c..906d3c3f4 100644 --- a/src/main/java/page/clab/api/global/config/OpenApiPatternsProperties.java +++ b/src/main/java/page/clab/api/global/config/WhitelistPatternsProperties.java @@ -8,8 +8,8 @@ @Setter @Getter @Configuration -@ConfigurationProperties(prefix = "security.swagger") -public class OpenApiPatternsProperties { +@ConfigurationProperties(prefix = "security.whitelist") +public class WhitelistPatternsProperties { private String[] patterns; diff --git a/src/main/java/page/clab/api/global/util/SwaggerUtil.java b/src/main/java/page/clab/api/global/util/SwaggerUtil.java index 26d19f6ba..78e716255 100644 --- a/src/main/java/page/clab/api/global/util/SwaggerUtil.java +++ b/src/main/java/page/clab/api/global/util/SwaggerUtil.java @@ -3,7 +3,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import page.clab.api.global.config.OpenApiPatternsProperties; +import page.clab.api.global.config.WhitelistPatternsProperties; import java.util.regex.Pattern; @@ -14,11 +14,11 @@ public class SwaggerUtil implements InitializingBean { private static String[] swaggerPatterns; @Autowired - private OpenApiPatternsProperties openApiPatternsProperties; + private WhitelistPatternsProperties whitelistPatternsProperties; @Override public void afterPropertiesSet() throws Exception { - swaggerPatterns = openApiPatternsProperties.getPatterns(); + swaggerPatterns = whitelistPatternsProperties.getPatterns(); } public static boolean isSwaggerRequest(String path) { diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 000000000..9dad69843 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,57 @@ + + + + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + ${LOG_PATH}/stage/application.%d{yyyy-MM-dd}.log + + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + ${LOG_PATH}/prod/application.%d{yyyy-MM-dd}.log + + 30 + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + + + + + + + + +