From 3b7c4d4683e1dfecb5609d5e1f59c904e53deb3e Mon Sep 17 00:00:00 2001 From: Rinjae Date: Wed, 22 Oct 2025 11:32:36 +0900 Subject: [PATCH] =?UTF-8?q?=EC=84=9C=EB=B2=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- .../eactive/ext/kjb/common/KjbProperty.java | 6 +- .../com/eactive/ext/kjb/ums/EmailJob.java | 10 +-- .../eactive/ext/kjb/ums/EmailSendModule.java | 30 ++++---- .../eactive/ext/kjb/ums/KjbUmsService.java | 70 ++++++++++++++++--- .../ext/kjb/ums/gson/UmsRequestPayload.java | 8 ++- .../com/eactive/ext/kjb/utils/JsonUtil.java | 10 +++ .../com/eactive/ext/kjb/utils/OsUtil.java | 24 +++++++ ...EmailSendModuleTests.java => UmsTest.java} | 51 ++++++++++---- src/test/resources/logback-test.xml | 6 +- 10 files changed, 170 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/eactive/ext/kjb/utils/JsonUtil.java create mode 100644 src/main/java/com/eactive/ext/kjb/utils/OsUtil.java rename src/test/java/com/eactive/ext/kjb/ums/test/{KjbEmailSendModuleTests.java => UmsTest.java} (53%) diff --git a/README.md b/README.md index dbb73ea..38e9c81 100644 --- a/README.md +++ b/README.md @@ -6,4 +6,5 @@ eapim-admin 에서 포함하여 테스트 이용시 느린 부팅 속도 떄문 ## 주요 기능 - EAI 배치 연계 -- 고객이메일발송 시스템 연계 \ No newline at end of file +- 고객이메일발송 시스템 연계 + diff --git a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java index ddb084a..850d074 100644 --- a/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java +++ b/src/main/java/com/eactive/ext/kjb/common/KjbProperty.java @@ -21,15 +21,15 @@ public class KjbProperty { @NotEmpty(message = "kjb.ums.eai_batch.send_dir 는 필수값입니다.") @Pattern(regexp = "^(?:[a-zA-Z]:\\\\|\\\\|\\/)(?:[\\w\\-\\.]+[\\\\\\/])*[\\w\\-\\.]+$", message = "kjb.ums.eai_batch.send_dir 은 절대 경로여야 합니다. 예: /Data/eapim/portal/sendmail/snd 또는 C:\\Data\\eapim\\portal\\sendmail\\snd") - private String eaiBatchSendDir; + private String umsEaiBatchSendDir; @NotEmpty(message = "kjb.ums.eai_batch.backup_dir 는 필수값입니다.") @Pattern(regexp = "^(?:[a-zA-Z]:\\\\|\\\\|\\/)(?:[\\w\\-\\.]+[\\\\\\/])*[\\w\\-\\.]+$", message = "kjb.ums.eai_batch.backup_dir 은 절대 경로여야 합니다. 예: /Data/eapim/portal/sendmail/bak 또는 C:\\Data\\eapim\\portal\\sendmail\\bak") - private String eaiBatchBackupDir; + private String umsEaiBatchBackupDir; @Pattern(regexp = "^[a-zA-Z0-9]{13}$", message = "kjb.ums.eai_batch.interface_id 는 12자리 영문대소문자 및 숫자 조합이어야 합니다.") - private String eaiBatchInterfaceId; + private String umsEaiBatchInterfaceId; @Pattern(regexp = "^(https?://[^\\s/$.?#][^\\s]*)|\\s*$", message = "kjb.ums.url 는 유효한 URL 형식이거나 공백이어야 합니다. 예: http://example.com/api") diff --git a/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java b/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java index 3e818cd..9f3cf8c 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java +++ b/src/main/java/com/eactive/ext/kjb/ums/EmailJob.java @@ -42,10 +42,10 @@ public class EmailJob { this.prop = prop; String filename = messageRequest.getServiceId() + "_" + DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS").format(now); - this.tempFilePath = Paths.get(prop.getEaiBatchSendDir(), filename + ".tmp"); - this.eaiFilePath = Paths.get(prop.getEaiBatchSendDir(), filename); - this.backupFilePath = Paths.get(prop.getEaiBatchBackupDir(), filename); - this.failedFilePath = Paths.get(prop.getEaiBatchBackupDir(), filename + ".failed"); + this.tempFilePath = Paths.get(prop.getUmsEaiBatchSendDir(), filename + ".tmp"); + this.eaiFilePath = Paths.get(prop.getUmsEaiBatchSendDir(), filename); + this.backupFilePath = Paths.get(prop.getUmsEaiBatchBackupDir(), filename); + this.failedFilePath = Paths.get(prop.getUmsEaiBatchBackupDir(), filename + ".failed"); this.messageRequestRepository = messageRequestRepository; @@ -148,7 +148,7 @@ public class EmailJob { private void eaiBatchCall() throws IOException { KjbEaiBatchModule eaiBatchModule = new KjbEaiBatchModule(prop); - EaiBatchMessage result = eaiBatchModule.call(prop.getEaiBatchInterfaceId(), eaiFilePath.getFileName().toString()); + EaiBatchMessage result = eaiBatchModule.call(prop.getUmsEaiBatchInterfaceId(), eaiFilePath.getFileName().toString()); if (!"1".equals(result.getResultCode())) { log.error("EAI Batch call failed: {}", result); diff --git a/src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java b/src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java index bdc2a73..4c40457 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java +++ b/src/main/java/com/eactive/ext/kjb/ums/EmailSendModule.java @@ -40,50 +40,50 @@ public class EmailSendModule { } log.info("EAI Batch URL: {}", prop.getEaiBatchUrl()); - log.info("EAI Batch Send Dir: {}", prop.getEaiBatchSendDir()); + log.info("EAI Batch Send Dir: {}", prop.getUmsEaiBatchSendDir()); // 디렉토리 검사(쓰기 권한 포함) - Path sendDirPath = Paths.get(prop.getEaiBatchSendDir()); + Path sendDirPath = Paths.get(prop.getUmsEaiBatchSendDir()); if (!sendDirPath.toFile().exists()) { // 생성 시도 boolean created = sendDirPath.toFile().mkdirs(); if (!created) { - log.error("EAI_BATCH_SEND_DIR 디렉토리를 생성할 수 없음: {}", prop.getEaiBatchSendDir()); - throw new IllegalStateException("EAI_BATCH_SEND_DIR 디렉토리를 생성할 수 없음: " + prop.getEaiBatchSendDir()); + log.error("EAI_BATCH_SEND_DIR 디렉토리를 생성할 수 없음: {}", prop.getUmsEaiBatchSendDir()); + throw new IllegalStateException("EAI_BATCH_SEND_DIR 디렉토리를 생성할 수 없음: " + prop.getUmsEaiBatchSendDir()); } } if (!sendDirPath.toFile().isDirectory()) { - log.error("EAI_BATCH_SEND_DIR 가 디렉토리가 아님: {}", prop.getEaiBatchSendDir()); - throw new IllegalStateException("EAI_BATCH_SEND_DIR 가 디렉토리가 아님: " + prop.getEaiBatchSendDir()); + log.error("EAI_BATCH_SEND_DIR 가 디렉토리가 아님: {}", prop.getUmsEaiBatchSendDir()); + throw new IllegalStateException("EAI_BATCH_SEND_DIR 가 디렉토리가 아님: " + prop.getUmsEaiBatchSendDir()); } if (!sendDirPath.toFile().canWrite()) { - log.error("EAI_BATCH_SEND_DIR 에 쓰기 권한이 없음: {}", prop.getEaiBatchSendDir()); - throw new IllegalStateException("EAI_BATCH_SEND_DIR 에 쓰기 권한이 없음: " + prop.getEaiBatchSendDir()); + log.error("EAI_BATCH_SEND_DIR 에 쓰기 권한이 없음: {}", prop.getUmsEaiBatchSendDir()); + throw new IllegalStateException("EAI_BATCH_SEND_DIR 에 쓰기 권한이 없음: " + prop.getUmsEaiBatchSendDir()); } // 백업 디렉토리 - Path backupDirPath = Paths.get(prop.getEaiBatchBackupDir()); + Path backupDirPath = Paths.get(prop.getUmsEaiBatchBackupDir()); if (!backupDirPath.toFile().exists()) { // 생성 시도 boolean created = backupDirPath.toFile().mkdirs(); if (!created) { - log.error("EAI_BATCH_BACKUP_DIR 디렉토리를 생성할 수 없음: {}", prop.getEaiBatchBackupDir()); - throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 디렉토리를 생성할 수 없음: " + prop.getEaiBatchBackupDir()); + log.error("EAI_BATCH_BACKUP_DIR 디렉토리를 생성할 수 없음: {}", prop.getUmsEaiBatchBackupDir()); + throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 디렉토리를 생성할 수 없음: " + prop.getUmsEaiBatchBackupDir()); } } if (!backupDirPath.toFile().isDirectory()) { - log.error("EAI_BATCH_BACKUP_DIR 가 디렉토리가 아님: {}", prop.getEaiBatchBackupDir()); - throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 가 디렉토리가 아님: " + prop.getEaiBatchBackupDir()); + log.error("EAI_BATCH_BACKUP_DIR 가 디렉토리가 아님: {}", prop.getUmsEaiBatchBackupDir()); + throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 가 디렉토리가 아님: " + prop.getUmsEaiBatchBackupDir()); } if (!backupDirPath.toFile().canWrite()) { - log.error("EAI_BATCH_BACKUP_DIR 에 쓰기 권한이 없음: {}", prop.getEaiBatchBackupDir()); - throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 에 쓰기 권한이 없음: " + prop.getEaiBatchBackupDir()); + log.error("EAI_BATCH_BACKUP_DIR 에 쓰기 권한이 없음: {}", prop.getUmsEaiBatchBackupDir()); + throw new IllegalStateException("EAI_BATCH_BACKUP_DIR 에 쓰기 권한이 없음: " + prop.getUmsEaiBatchBackupDir()); } log.info("KjbEmailSendModule - Environment check passed."); diff --git a/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java b/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java index 853fb6a..defe9e8 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java +++ b/src/main/java/com/eactive/ext/kjb/ums/KjbUmsService.java @@ -6,9 +6,12 @@ import com.eactive.ext.kjb.common.KjbProperty; import com.eactive.ext.kjb.ums.gson.IUmsGsonObject; import com.eactive.ext.kjb.ums.gson.UmsRequestPayload; import com.eactive.ext.kjb.ums.gson.VerifyPhoneUmsTemplate; +import com.eactive.ext.kjb.utils.JsonUtil; import com.eactive.ext.kjb.utils.ValidationUtil; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.config.RequestConfig; @@ -16,13 +19,17 @@ import org.apache.hc.client5.http.entity.EntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.ProtocolException; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.util.Timeout; +import org.springframework.util.StreamUtils; import java.io.IOException; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Arrays; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; @@ -104,13 +111,25 @@ public class KjbUmsService { .msgSndChnlCd(UmsRequestPayload.ChannelCode.KM) .build(); - return callUms(SEND_URL, payload, (messageId, ex) -> { + try { + ValidationUtil.validateOrThrow(payload); + } catch (Exception ex) { + log.debug("UMS Request payload validation failed - {}", JsonUtil.toPrettyString(payload)); + throw ex; + } + + return callUms(SEND_URL, payload, (result, ex) -> { if (ex != null) { - log.error("UMS Call error", ex); + log.error("UMS 발송 실패", ex); throw new RuntimeException(ex); } - log.info("UMS Call success. ({})", messageId); + if (result == null || !result.isSuccess()) { + log.error("UMS 발송 실패(return) - {}", JsonUtil.toPrettyString(result)); + throw new RuntimeException("UMS 발송실패 - " + JsonUtil.toPrettyString(result != null ? result.getThrowable().getMessage() : "UMS 발송실패 - 알수 없는 오류")); + } + + log.info("UMS Call success. ({})", result); }).join().getUmsMessageId(); } @@ -126,19 +145,52 @@ public class KjbUmsService { httpPost.setHeader("Authorization", String.format(AUTHORIZATION_FMT, property.getUmsApiKey())); httpPost.setEntity(EntityBuilder.create().setText(json).setContentType(ContentType.APPLICATION_JSON).build()); + if (log.isDebugEnabled()) { + try { + log.debug("Header - {}", httpPost.getHeader("Content-Type")); + log.debug("Header - {}", httpPost.getHeader("Authorization")); + log.debug("Request Body : {}", StreamUtils.copyToString(httpPost.getEntity().getContent(), Charset.defaultCharset())); + } catch (ProtocolException | IOException e) { + throw new RuntimeException(e); + } + } + httpPost.setConfig(requestConfig); AtomicReference result = new AtomicReference<>(); if (isRealMode) { try { httpClient.execute(httpPost, res -> { - String body = EntityUtils.toString(res.getEntity()); - result.set(gson.fromJson(body, UmsCallResult.class)); - result.get().setUmsMessageId(requestPayload.getMessageIdentityNo()); - result.get().setSuccess(true); - return result; + try { + + String body = EntityUtils.toString(res.getEntity()); + if (log.isDebugEnabled()) { + log.debug("Response - {}", body); + } + result.set(gson.fromJson(body, UmsCallResult.class)); + result.get().setUmsMessageId(requestPayload.getMessageIdentityNo()); + result.get().setSuccess(true); + return result; + } finally { + log.debug("Response status - {}", res.getCode()); + + if (res.getCode() != 200) { + Arrays.asList(res.getHeaders()).forEach(o -> { + log.warn("[Header] {}: {}", o.getName(), o.getValue()); + }); + } + } }); - } catch (IOException e) { + } catch (JsonSyntaxException e) { + log.error("UMS Call error(JSON Syntax)", e); + result.set( + UmsCallResult.builder() + .umsMessageId(requestPayload.getMessageIdentityNo()) + .isSuccess(false) + .throwable(e) + .build() + ); + } catch (Exception e) { log.error("UMS Call error", e); result.set( UmsCallResult.builder() diff --git a/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java b/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java index 31836d2..9dade0e 100644 --- a/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java +++ b/src/main/java/com/eactive/ext/kjb/ums/gson/UmsRequestPayload.java @@ -41,6 +41,7 @@ public class UmsRequestPayload { @Pattern(regexp = "^[0-9]{14}$") @NotEmpty @SerializedName("SND_RESR_DTTM") + @Builder.Default private String sendReservationDatetime = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()); // 휴대전화번호 / 44 / 휴대폰번호(평문, 암호화 둘다가능) @@ -57,27 +58,32 @@ public class UmsRequestPayload { // SND_BRCD string 발송부점코드 4 O 0333 @NotEmpty @SerializedName("SND_BRCD") + @Builder.Default private String sendBranchCode = "0990"; // 발송직원번호 / 7 / ECEB057(텔러번호?) // SND_EMPNO string 발송직원번호 7 O ? 넣긴해야함 @NotEmpty @SerializedName("SND_EMPNO") + @Builder.Default private String sendEmployeeNo = "ECEB057"; // SRVC_ID string 서비스ID 11 요청단 측 서비스ID @NotEmpty @SerializedName("SRVC_ID") + @Builder.Default private String serviceId = "eAPIM"; // MSG_SND_CHNL_CD string 메시지발송채널코드 2 @SerializedName("MSG_SND_CHNL_CD") - @NotEmpty + @NotNull + @Builder.Default private ChannelCode msgSndChnlCd = ChannelCode.KM; // VALDTN_RSLT_CD string 검증결과코드 2 00으로 고정 @SerializedName("VALDTN_RSLT_CD") @NotEmpty + @Builder.Default private String validationResultCode = "00"; public enum ChannelCode { diff --git a/src/main/java/com/eactive/ext/kjb/utils/JsonUtil.java b/src/main/java/com/eactive/ext/kjb/utils/JsonUtil.java new file mode 100644 index 0000000..bf2033e --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/utils/JsonUtil.java @@ -0,0 +1,10 @@ +package com.eactive.ext.kjb.utils; + +import com.google.gson.GsonBuilder; + +public class JsonUtil { + public static String toPrettyString(Object obj) { + if (obj == null) { return "null"; } + return new GsonBuilder().setPrettyPrinting().create().toJson(obj); + } +} diff --git a/src/main/java/com/eactive/ext/kjb/utils/OsUtil.java b/src/main/java/com/eactive/ext/kjb/utils/OsUtil.java new file mode 100644 index 0000000..de9679a --- /dev/null +++ b/src/main/java/com/eactive/ext/kjb/utils/OsUtil.java @@ -0,0 +1,24 @@ +package com.eactive.ext.kjb.utils; + +import java.net.InetAddress; + +public class OsUtil { + public static boolean isWindows() { + return System.getProperty("os.name").toLowerCase().contains("win"); + } + + public static boolean isLinux() { + String os = System.getProperty("os.name").toLowerCase(); + return os.contains("nux") || os.contains("nix") || os.contains("aix") ; + } + + public static String getHostName() { + try { + return InetAddress.getLocalHost().getHostName(); + } catch (Exception e) { + String name = System.getProperty("COMPUTERNAME"); + if (name == null) name = System.getenv("HOSTNAME"); + return name != null ? name : "unknown"; + } + } +} diff --git a/src/test/java/com/eactive/ext/kjb/ums/test/KjbEmailSendModuleTests.java b/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java similarity index 53% rename from src/test/java/com/eactive/ext/kjb/ums/test/KjbEmailSendModuleTests.java rename to src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java index eb087f7..5c838a7 100644 --- a/src/test/java/com/eactive/ext/kjb/ums/test/KjbEmailSendModuleTests.java +++ b/src/test/java/com/eactive/ext/kjb/ums/test/UmsTest.java @@ -5,29 +5,53 @@ import com.eactive.apim.portal.template.entity.MessageRequest; import com.eactive.ext.kjb.common.KjbProperty; import com.eactive.ext.kjb.ums.EmailSendModule; import com.eactive.ext.kjb.ums.KjbUmsService; +import com.eactive.ext.kjb.utils.OsUtil; import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import java.util.UUID; import java.util.concurrent.CompletableFuture; @Slf4j -public class KjbEmailSendModuleTests { +public class UmsTest { + private static KjbProperty prop = null; -// @Test + @BeforeAll + public static void init() { + prop = new KjbProperty(); + prop.setUmsEaiBatchInterfaceId("UAGFF00001UIE"); + + if (OsUtil.isWindows()) { + prop.setUmsEaiBatchSendDir("c:\\kjb-logs\\sendmail\\snd"); + prop.setUmsEaiBatchBackupDir("c:\\kjb-logs\\sendmail\\bak"); + } + + if (OsUtil.isLinux()) { + prop.setUmsEaiBatchSendDir("/Data/eapim/portal/sendmail/snd"); + prop.setUmsEaiBatchBackupDir("/Data/eapim/portal/sendmail/bak"); + } + } + + @Test public void testSms() { - KjbProperty prop = new KjbProperty(); - prop.setEaiBatchSendDir("c:\\kjb-logs\\sendmail\\snd"); - prop.setEaiBatchBackupDir("c:\\kjb-logs\\sendmail\\bak"); - prop.setEaiBatchInterfaceId("UAGFF00001UIE"); + Assumptions.assumeTrue( + "true".equalsIgnoreCase(System.getenv("KJB_EAPIM_TEST_ENABLED")), + "OS 환경변수 내 KJB_EAPIM_TEST_ENABLED=TRUE 설정되어 있어야 동작함"); + + prop.setUmsUrl("http://172.31.35.144:9021/ums/openapi/send"); + prop.setUmsApiKey("7cca6d58-e4f7-474d-9edd-525806bc99ff"); MessageRequest messageRequest = new MessageRequest(); messageRequest.setId(UUID.randomUUID().toString()); messageRequest.setMessageCode(MessageCode.USER_VERIFICATION_MOBILEPHONE); + messageRequest.setPhone("01099750188"); messageRequest.setEmail("dearmai@dearmai.net"); messageRequest.setUsername("김광은"); messageRequest.setSubject("제목 - test"); - messageRequest.setMessage("내용 - test"); + messageRequest.setMessage("junit test"); KjbUmsService umsService = new KjbUmsService(prop); umsService.send(messageRequest); @@ -35,14 +59,17 @@ public class KjbEmailSendModuleTests { log.info("done"); } -// @Test + @Test public void testEmail() { - KjbProperty prop = new KjbProperty(); + Assumptions.assumeTrue( + "true".equalsIgnoreCase(System.getenv("KJB_EAPIM_TEST_ENABLED")), + "OS 환경변수 내 KJB_EAPIM_TEST_ENABLED=TRUE 설정되어 있어야 동작함"); + // prop.setEaiBatchUrl("http://172.31.32.111:10230/BATAppWeb/EaiBatCall"); prop.setEaiBatchUrl(""); - prop.setEaiBatchSendDir("c:\\kjb-logs\\sendmail\\snd"); - prop.setEaiBatchBackupDir("c:\\kjb-logs\\sendmail\\bak"); - prop.setEaiBatchInterfaceId("UAGFF00001UIE"); + prop.setUmsEaiBatchSendDir("c:\\kjb-logs\\sendmail\\snd"); + prop.setUmsEaiBatchBackupDir("c:\\kjb-logs\\sendmail\\bak"); + prop.setUmsEaiBatchInterfaceId("UAGFF00001UIE"); EmailSendModule service = EmailSendModule.getInstance(prop, null); MessageRequest messageRequest = new MessageRequest(); diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml index c7a3a0d..b4222d8 100644 --- a/src/test/resources/logback-test.xml +++ b/src/test/resources/logback-test.xml @@ -1,9 +1,10 @@ - + - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + %d{yyyy-MM-dd HH:mm:ss.SSS} %magenta([%thread]) %highlight([%-3level]) %logger - %msg %n @@ -13,6 +14,7 @@ +