Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
8a8320c
Merge pull request #119 from q-asker/main
GulSauce Jan 20, 2026
74c3444
[ICC-230] 로깅 추가
GulSauce Jan 20, 2026
e01bcbf
[ICC-230] 에러 체이닝 조정
GulSauce Jan 21, 2026
5b242dc
[ICC-230] 구현 완료
GulSauce Jan 21, 2026
69df4b6
Merge pull request #120 from q-asker/ICC-230-env
GulSauce Jan 21, 2026
51db1ad
chore: bump version to 1.6.5
github-actions[bot] Jan 21, 2026
479fd15
[ICC-210] properties 정리
GulSauce Jan 22, 2026
09b7ae8
[ICC-210] RDB 엔티티로 변경
GulSauce Jan 22, 2026
2043b44
[ICC-210] RDBMS로 이전 완료
GulSauce Jan 22, 2026
e13e40e
[ICC-210] 필요한 파일만 남김
GulSauce Jan 22, 2026
4ee0915
[ICC-210] /generation에 userId 넣도록 함
GulSauce Jan 22, 2026
e521b31
[ICC-210] 첫번째 제안 반영
GulSauce Jan 24, 2026
781d102
[ICC-210] 동시성 문제 해결
GulSauce Jan 24, 2026
b0eb675
[ICC-210] 리프레시 토큰 없을 때 예외처리
GulSauce Jan 24, 2026
1ba5828
[ICC-210] 불필요한 쿼리문 삭제
GulSauce Jan 24, 2026
4ebfe1f
[ICC-210] 불필요한 참조 제거
GulSauce Jan 24, 2026
8cd913b
[ICC-210] 오타 수정
GulSauce Jan 24, 2026
eaa1523
[ICC-210] problemset의 title 제거
GulSauce Jan 24, 2026
9f1b29f
[ICC-232] 리팩터링
GulSauce Jan 25, 2026
2575b3c
[ICC-232] Flux를 통한 비동기 응답 구조 작성 완료
GulSauce Jan 25, 2026
f7d71f4
Merge pull request #122 from q-asker/ICC-210-login
lhoju0158 Jan 26, 2026
afb4835
Merge branch 'develop' into ICC-233-server-sent-quiz
GulSauce Jan 26, 2026
5883f86
[ICC-242] 스카우터 스크립트 작성, 파일 추가
GulSauce Jan 27, 2026
7bc5c8a
[ICC-242] 코드래빗 파일 이동
GulSauce Jan 27, 2026
b849c87
[ICC-242] 오타 수정
GulSauce Jan 27, 2026
f376d42
[ICC-242] 배포 스크립트 수정
GulSauce Jan 27, 2026
e8bc189
[ICC-242] 보안 설정
GulSauce Jan 27, 2026
c925d18
[ICC-242] 첫번째 시도
GulSauce Jan 27, 2026
8ad47be
[ICC-242] 두번째 시도
GulSauce Jan 27, 2026
fbc106c
[ICC-242] 세번째 시도
GulSauce Jan 27, 2026
817bdb0
[ICC-242] 네번째 시도
GulSauce Jan 27, 2026
08347e6
[ICC-242] 여섯번째 시도
GulSauce Jan 27, 2026
2bfd524
[ICC-242] 일곱번째 시도
GulSauce Jan 27, 2026
4f84d5e
[ICC-242] 여덟번째 시도
GulSauce Jan 27, 2026
c09831e
[ICC-242] NoSuchError를 404로
GulSauce Jan 27, 2026
5c3824a
[ICC-242] 배포 스크립트에서 제외
GulSauce Jan 27, 2026
22bd6ac
[ICC-242] 아홉번째 시도
GulSauce Jan 27, 2026
ff43503
[ICC-242] 스카우터 수동 설치를 위한 롤백
GulSauce Jan 27, 2026
c3f30dc
[ICC-242] 도커 컴포즈 롤백
GulSauce Jan 28, 2026
8ccb33a
Merge pull request #125 from q-asker/ICC-242-scouter
lhoju0158 Jan 29, 2026
e12a275
[ICC-232] 이벤트 큐 기반webclient가 아닌 스레드 블로킹 기반 httpclient로 교쳬
GulSauce Jan 29, 2026
508ef59
[ICC-232] 가상스레드기반 동기 클라이언트로 바꿈
GulSauce Jan 29, 2026
ea3bfd8
[ICC-232] 오류 처리 세분화
GulSauce Jan 30, 2026
64069cc
[ICC-242] 함수 이름 더 알아 듣게 변경
GulSauce Jan 30, 2026
00eab87
[ICC-232] enableasync
GulSauce Jan 31, 2026
f937685
[ICC-232] 올바른 스트리밍 중 오류 메시지 처리
GulSauce Jan 31, 2026
80d2a4b
[ICC-232] 패키지명 오류 수정
GulSauce Jan 31, 2026
b674c86
[ICC-232] 코드래빗 리뷰 반영
GulSauce Jan 31, 2026
322e67e
[ICC-232] 코드래빗 리뷰 반영
GulSauce Jan 31, 2026
cdb886e
[ICC-232] 프론트엔드 DTO 의존성 해결
GulSauce Jan 31, 2026
30cdb61
Merge pull request #124 from q-asker/ICC-232-server-sent-quiz
GulSauce Feb 1, 2026
8f222c6
resolve conflict
GulSauce Feb 1, 2026
5765702
chore: bump version to 1.7.0
github-actions[bot] Feb 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/resources/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,6 @@ send_slack ">>> Nginx 트래픽 전환 ($TARGET_CONTAINER)..."
echo "set \$service_url http://$TARGET_CONTAINER:8080;" > ./nginx/conf.d/service-url.inc

IS_NGINX_RUNNING=$(docker ps | grep nginx)

if [ -z "$IS_NGINX_RUNNING" ]; then
send_slack ">>> Nginx가 실행 중이지 않습니다. Nginx 시작..."
docker compose up -d nginx
Expand All @@ -133,7 +132,8 @@ if [ -n "$CURRENT_PROFILE" ]; then
send_slack ">>> 🛑 구 버전 컨테이너 중지 완료: ${STOP_DURATION}초 소요 ($STOP_MSG)"
fi

send_slack ">>> 사용하지 않는 Docker 이미지 정리(Prune)..."
send_slack ">>> 사용하지 않는 Docker 이미지 정리..."
docker images qasker/api --format "{{.Repository}}:{{.Tag}}" | grep -v ":latest" | xargs docker rmi
docker image prune -f

TOTAL_END_TIME=$(date +%s)
Expand Down
2 changes: 1 addition & 1 deletion .github/resources/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ services:
environment:
- SPRING_PROFILES_ACTIVE=prod,green
ports:
- "${GREEN_PORT}:8080"
- "${GREEN_PORT}:8080"
10 changes: 6 additions & 4 deletions .github/workflows/prod_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Docker Hub Push & 배포 서버 EC2 배포
on:
push:
branches:
- main
- ICC-242-scouter
workflow_dispatch:

jobs:
Expand All @@ -20,7 +20,6 @@ jobs:

- name: 환경변수들 등록
run: |
echo "${{ env.NEWRELIC_YML }}" > app/newrelic/newrelic.yml
echo "${{ env.APPLICATION_PROD_YML }}" > app/src/main/resources/application-prod.yml
echo "${{ env.GRADLE_PROPERTIES }}" > app/gradle.properties

Expand All @@ -34,7 +33,7 @@ jobs:
uses: gradle/actions/setup-gradle@v4

- name: Jib로 Docker 이미지 빌드 및 푸시
run: ./gradlew jib -PPROFILE=prod --build-cache
run: ./gradlew jib --build-cache

deploy:
name: EC2 배포
Expand All @@ -46,7 +45,6 @@ jobs:
EC2_KEY: ${{ secrets.EC2_KEY }}
BLUE_PORT: 8001
GREEN_PORT: 8002
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

steps:
- name: gradle.properties 값 환경 변수로 등록 및 마스킹
Expand All @@ -67,6 +65,10 @@ jobs:
# DOCKER_CONTAINER_NAME 생성, 등록
DOCKER_CONTAINER_NAME_VALUE="${DOCKER_ID_VALUE}-${DOCKER_IMAGE_NAME_VALUE}"
echo "DOCKER_CONTAINER_NAME=$DOCKER_CONTAINER_NAME_VALUE" >> $GITHUB_ENV

# SLACK_WEBHOOK_URL 추출, 마스킹, 환경 변수 등록
SLACK_WEBHOOK_URL_VALUE=$(echo "${{ secrets.GRADLE_PROPERTIES }}" | grep '^SLACK_WEBHOOK_URL=' | cut -d'=' -f2-)
echo "SLACK_WEBHOOK_URL=$SLACK_WEBHOOK_URL_VALUE" >> $GITHUB_ENV

- name: 코드 체크아웃
uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ out/
.env
app/gradle.properties
app/newrelic/newrelic.yml
/heapdump
**/heapdump
monitor_downtime.sh
53 changes: 43 additions & 10 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ dependencies {
implementation project(':auth:auth-impl')
implementation project(':aws:aws-impl')
implementation project(':quiz:quiz-impl')
implementation project(':util:util-impl')
implementation project(':global')

implementation "org.springframework.boot:spring-boot-starter-actuator"
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}

Expand All @@ -37,23 +38,55 @@ jib {
extraDirectories {
paths {
path {
setFrom(file("newrelic").toPath())
into = "/app/newrelic"
setFrom(file("./scouter").toPath())
into = "/app/scouter"
}
}
}
container {
def jvmHeapSize = project.property("JVM_HEAP_SIZE")

// 기본 JVM 플래그 설정
jvmFlags = ["-Xms${jvmHeapSize}", "-Xmx${jvmHeapSize}"]

def newRelicConfig = project.file("newrelic/newrelic.yml")
def newRelicJar = project.file("newrelic/newrelic.jar")
if (newRelicConfig.exists() && newRelicJar.exists()) {
jvmFlags = jvmFlags + [
"-Dnewrelic.config.file=/app/newrelic/newrelic.yml",
"-javaagent:/app/newrelic/newrelic.jar",
]
def scouterJar = project.file("./scouter/agent.java/scouter.agent.jar")

if (!scouterJar.exists()) {
throw new GradleException("Scouter agent file not found: ${scouterJar.absolutePath}")
}

// 1. 필수값 검증 (값이 없으면 여기서 빌드 실패함 🛑)
def requiredProps = ["SCOUTER_IP", "SCOUTER_PORT", "SCOUTER_OBJ_NAME"]
requiredProps.each { prop ->
if (!project.hasProperty(prop)) {
throw new GradleException("❌ [빌드 실패] ${prop} 프로퍼티가 누락되었습니다. (-P${prop}=값 필요)")
}
}

// 2. 값 할당 (검증 통과했으므로 안전하게 가져옴)
def collectorIp = project.property("SCOUTER_IP")
def objName = project.property("SCOUTER_OBJ_NAME")
def collectorPort = project.property("SCOUTER_PORT")

jvmFlags = jvmFlags + [
// [필수] 에이전트 로드
"-javaagent:/app/scouter/agent.java/scouter.agent.jar",

// [필수] Java 16+ 대응을 위한 모듈 접근 허용 옵션
"--add-opens=java.base/java.lang=ALL-UNNAMED",
"--add-opens=java.base/java.util=ALL-UNNAMED",

// [필수] conf 파일 대신 직접 설정 주입
"-Dnet_collector_ip=${collectorIp}",
"-Dnet_collector_udp_port=${collectorPort}",
"-Dnet_collector_tcp_port=${collectorPort}",

// [보안 설정: 권장] 클라이언트에서 제어 기능(HeapDump, Method Patch 등) 비활성화
"-Denable_mgr_agent=false",

// [권장] 어플리케이션 이름
"-Dobj_name=${objName}"

]
}
}
Binary file removed app/newrelic/newrelic.jar
Binary file not shown.
12 changes: 12 additions & 0 deletions app/scouter/agent.java/conf/scouter.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
### scouter java agent configuration sample
#obj_name=WAS-01
#net_collector_ip=127.0.0.1
#net_collector_udp_port=6100
#net_collector_tcp_port=6100
#hook_method_patterns=sample.mybiz.*Biz.*,sample.service.*Service.*
#trace_http_client_ip_header_key=X-Forwarded-For
#profile_spring_controller_method_parameter_enabled=false
#hook_exception_class_patterns=my.exception.TypedException
#profile_fullstack_hooked_exception_enabled=true
#hook_exception_handler_method_patterns=my.AbstractAPIController.fallbackHandler,my.ApiExceptionLoggingFilter.handleNotFoundErrorResponse
#hook_exception_hanlder_exclude_class_patterns=exception.BizException
1 change: 1 addition & 0 deletions app/scouter/agent.java/conf/testcase-scouter.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
objName=test-case-scouter
9 changes: 9 additions & 0 deletions app/scouter/agent.java/plugin/capture.plug
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[args]
// void capArgs(WrContext $ctx, HookArgs $hook)

[return]
// void capReturn(WrContext $ctx, HookReturn $hook)


[this]
// void capThis(WrContext $ctx, String $class, String $desc, Object $this)
2 changes: 2 additions & 0 deletions app/scouter/agent.java/plugin/counter.plug
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[counter]
//public void counter(scouter.lang.pack.PerfCounterPack $pack)
2 changes: 2 additions & 0 deletions app/scouter/agent.java/plugin/httpcall.plug
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[call]
// void call(WrContext $ctx, WrHttpCallRequest $req)
12 changes: 12 additions & 0 deletions app/scouter/agent.java/plugin/httpservice.plug
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[start]
// void start(WrContext $ctx, WrRequest $req, WrResponse $res)



[end]
// void end(WrContext $ctx, WrRequest $req, WrResponse $res)


[reject]
// boolean reject(WrContext $ctx, WrRequest $req, WrResponse $res)
return false;
4 changes: 4 additions & 0 deletions app/scouter/agent.java/plugin/jdbcpool.plug
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[url]
// String url(WrContext $ctx, String $msg, Object $pool)

return null;
138 changes: 138 additions & 0 deletions app/scouter/agent.java/plugin/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
## Javaagent Plugin
- Default File Location : ${directory of scouter.agent.jar}/plugin
- Dynamic application
- By java code
- Plugin 종류
- Http-service
- Service
- HttpCall
- Capture
- JDBC-Pool

### Http-service Plugin(httpservice.plug)

1. void start(WrContext $ctx, WrRequest $req, WrResponse $res) : Http Service 시작 시점
2. void end(WrContext $ctx, WrRequest $req, WrResponse $res) : Http Service 종료 시점
3. boolean reject(WrContext $ctx, WrRequest $req, WrResponse $res) : Http Service 시작 시점에 reject 조건 (default : false)

### Service Plugin(service.plug)
**추가적인 hooking 설정을 통해서만 동작**

1. void start(WrContext $ctx, HookArgs $hook) : Service 시작 시점
2. void end(WrContext $ctx) : Service 종료 시점

### HttpCall Plugin(httpcall.plug)

1. void call(WrContext $ctx, WrHttpCallRequest $req) : Http Call 요청 시점

### Capture Plugin(capture.plug)
**추가적인 hooking 설정을 통해서만 동작**

1. void capArgs(WrContext $ctx, HookArgs $hook) : Method 시작 시점
2. void capReturn(WrContext $ctx, HookReturn $hook) : Method Return 시점
3. void capThis(WrContext $ctx, String $class, String $desc, Object $this) : Constructor 생성 시점

### JDBC-Pool Plugin(jdbcpool.plug)

1. String url(WrContext $ctx, String $msg, Object $pool)
: DB Connection URL 요청 시점


## API

### Common API
- void log(Object c) : Logger를 통한 log
- void println(Object c) : System.out를 통한 log
- Object field(Object o, String field) : Object의 filed 값을 가져옴
- Object method(Object o, String method) : Object의 method를 강제invoke 함
- Object method1(Object o, String method) : Object의 method를 invoke 함
- Object method(Object o, String method, String param) : Object의 method를 String 파라미터와 함께 invoke 함
- String toString(Object o) : Object 를 toString 하여 반환
- String toString(Object o, String def) : Object 를 toString 하여 반환, null 이면 default string 반환
- void alert(char level, String title, String message) : Alert 을 보냄
- int syshash(Object o) : Object 의 identityHash 값 반환
- int syshash(HookArgs hook, int x) : Arguments의 i 인덱스의 identyHash 값 반환
- int syshash(HookArgs hook) : This 의 identyHash 값 반환
- void forward(WrContext wctx, int uuid) : Async Thread 를 App service로 연결
- void forwardThread(WrContext wctx, int uuid) : Async Thread 를 Background service로 연결
- void receive(WrContext ctx, int uuid) : 앞서 등록된 Service가 있으면 연결


### WrContext class API
- String service() : Service Name 을 반환
- void service(String name) : Service Name 을 set
- int serviceHash() : Service Hash 값을 반환
- void remoteIp(String ip) : Remote IP 을 set
- String remoteIp() : Remote IP를 반환
- void error(String err) : 임의의 error 를 주입
- boolean isError() : 에러 체크
- void group(String group) : 임의의 group을 set
- String group() : Group을 반환
- void login(String id) : 임의의 사용자 ID 를 set
- String login() : 사용자 ID를 반환
- void desc(String desc) : 임의의 Desc를 set
- String desc() : Desc를 반환
- String httpMethod() : Http Method를 반환
- String httpQuery() : Http Query를 반환
- String httpContentType() : Http Content-type을 반환
- String userAgent() : User-Agent를 반환
- void profile(String msg) : Msg 를 profile에 기록
- long txid() : txid 를 반환
- long gxid() : gxid 를 반환
- TraceContext inner() : context를 반환

### WrRequest class API
- String getCookie(String key) : Cookie 값을 반환
- String getRequestURI() : Request URI를 반환
- String getRemoteAddr() : Remote Address를 반환
- String getMethod() : Method 를 반환
- String getQueryString() : Query String을 반환
- String getParameter(String key) : Parameter를 반환
- Object getAttribute(String key) : Attribute를 반환
- String getHeader(String key) : Header값을 반환
- Enumeration getParameterNames() : Parameter 값들을 반환
- Enumeration getHeaderNames() : HeaderName들을 반환
- WrSession getSession() : WrSession객체를 반환
- Set getSessionNames() : Session Name들을 반환
- Object getSessionAttribute(String key) : Session 값을 반환
- Object inner() : Request Object를 반환
- boolean isOk() : Plugin 상태 확인
- Throwable error() : Error 확인

### WrResponse class API
- PrintWriter getWriter() : Writer를 반환
- String getContentType() : Content-type을 반환
- String getCharacterEncoding() : Character-encoding을 반환
- Object inner() : Response Object를 반환
- boolean isOk() : Plugin 상태 확인
- Throwable error() : Error 확인

### WrSession class API
- getAttribute(String key) : Attribute를 반환
- Enumeration getAttributeNames() : Attribute Names를 반환
- Object inner() : Session Object를 반환
- boolean isOk() : Plugin 상태 확인
- Throwable error() : Error 확인

### WrHttpCallRequest class API
- void header(Object key, Object value) : Header값 추가
- Object inner() : Request Object를 반환
- boolean isOk() : Plugin 상태 확인
- Throwable error() : Error 확인

### HookArgs class API
- String getClassName() : Class 이름 반환
- String getMethodName() : Method 이름 반환
- String getMethodDesc() : Method 의 Desc 반환
- Object getThis() : this object 반환
- Object[] getArgs() : Arguments 반환
- int getArgCount() : Argument 갯수 반환

### HookReturn class API
- String getClassName() : Class 이름 반환
- String getMethodName() : Method 이름 반환
- String getMethodDesc() : Method 의 Desc 반환
- Object getThis() : this object 반환
- Object getReturn() : Return 값 반환


Loading