From tnh-tools
Go 백엔드 서비스에 /HealthCheck 엔드포인트를 추가하거나, 기존 엔드포인트의 응답 형식을 TNH 표준 스키마에 맞게 수정합니다. 엔드포인트가 없으면 신규 생성, 있으면 스키마 대조 후 누락/불일치 필드를 수정합니다. 'healthcheck 추가', 'healthcheck 수정', 'health check endpoint', '/HealthCheck' 키워드로 트리거됩니다.
How this skill is triggered — by the user, by Claude, or both
Slash command
/tnh-tools:go-healthcheckThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Go 백엔드 서비스에 `GET /HealthCheck` 엔드포인트를 추가하거나,
Go 백엔드 서비스에 GET /HealthCheck 엔드포인트를 추가하거나,
기존 엔드포인트가 있다면 TNH 표준 스키마와 대조해 불일치 부분을 수정합니다.
Arguments: $ARGUMENTS
{
"serviceName": "vegas-was",
"version": "2.12.30.0",
"region": "b1",
"timestamp": "20260612162023",
"db": { ... },
"surveyMQ": { ... },
"redis": { ... },
"nats": { ... },
"kafka": { ... }
}
| 필드 | 타입 | 설명 | 값 출처 |
|---|---|---|---|
serviceName | string | 서비스 이름 | 환경변수 탐색 순서: SERVICENAME → SERVICE_NAME → APP_NAME → SERVICE |
version | string | 배포 버전 태그 | 환경변수 VERSION |
region | string | 물리 서버의 지리적 위치 (예: b1=분당, gn=강남) | 환경변수 탐색 순서: REGION → DEPLOY_REGION → SERVER_REGION. 값을 찾을 수 없으면 "unknown"으로 설정하고 주석으로 TODO 표시 |
timestamp | string | 응답 생성 시각. 14자리 숫자 문자열 | time.Now().Format("20060102150405") |
db | object | omit | DB 연결 상태. DB를 사용하지 않으면 필드 자체 생략 | 런타임 측정값 |
{서비스명}MQ | object | omit | RabbitMQ 연결 상태. 키 이름은 {serviceName}MQ 형식. 사용하지 않으면 생략 | 런타임 측정값 |
redis | object | omit | Redis 연결 상태. 사용하지 않으면 생략 | 런타임 측정값 |
nats | object | omit | NATS 연결 상태. 사용하지 않으면 생략 | 런타임 측정값 |
{infraName} | object | omit | 사용자 정의 추가 인프라 상태(예: Kafka, Elasticsearch, S3). 추가 사용자 정의 인프라 참고. 사용하지 않으면 생략 | 런타임 측정값 |
db){
"status": "UP",
"dbName": "h99701",
"allConnections": 0,
"freeConnections": 0,
"limitConnections": 64,
"waitingConnections": 0,
"acquireMs": 1,
"queryMs": 0
}
| 필드 | 타입 | 설명 |
|---|---|---|
status | "UP" | "DOWN" | UP: 연결 획득 및 쿼리 정상. DOWN: 연결 실패/타임아웃 |
dbName | string | null | healthCheck에 사용된 DB 이름. null이면 기본 커넥션으로 SELECT 1만 수행 |
allConnections | int | 풀에 생성된 총 커넥션 수 (pool._allConnections.length) |
freeConnections | int | 유휴 커넥션 수. 0이면 모든 커넥션 사용 중 |
limitConnections | int | 풀 최대 커넥션 수 (connectionLimit) |
waitingConnections | int | 커넥션 대기 요청 수. 높으면 풀 포화 |
acquireMs | int | null | 커넥션 획득 소요 시간(ms). 타임아웃 시 null |
queryMs | int | null | 쿼리 실행 소요 시간(ms). 타임아웃 시 null |
errorMessage | string | null | 실패 원인 메시지. status가 DOWN일 때만 존재 |
{serviceName}MQ){
"status": "UP",
"isConnected": true,
"isChannelOpen": true,
"isReady": true,
"isBlockedBroker": false,
"reconnectCount": 0,
"lastConnectedAt": "2026-06-10 08:45:41",
"lastConsumeAgoMs": 3200,
"lastPingMs": 2,
"passiveOk": true,
"pingOk": true,
"consumOk": false
}
| 필드 | 타입 | 설명 |
|---|---|---|
status | "UP" | "DEGRADED" | "DOWN" | UP: 모든 점검 정상. DEGRADED: 연결은 되나 ping/passive/소비 지연 경고. DOWN: 연결 끊김 또는 채널 닫힘 |
isConnected | bool | AMQP 브로커와 TCP 연결 활성 여부. connect() 성공 후 close 이벤트 미발생 시 true |
isChannelOpen | bool | 기본 채널이 열려있는지 여부. false이면 메시지 송수신 불가 |
isReady | bool | assertExchange → assertQueue → bindQueue → consume 초기화가 모두 완료된 상태 |
isBlockedBroker | bool | 브로커가 connection.blocked 이벤트로 송신 차단 중인지 여부 (디스크/메모리 부족 등) |
reconnectCount | int | 재연결 시도 누적 횟수 |
lastConnectedAt | string | null | 마지막 연결 성공 시각 (YYYY-MM-DD HH:mm:ss). 재연결 시 갱신 |
lastConsumeAgoMs | int | null | 마지막 메시지 소비 후 경과 시간(ms). null이면 아직 소비 이력 없음 |
lastPingMs | int | null | pingCheck publish→confirm 왕복 시간(ms). null이면 최근 ping 실패 또는 수행 이력 없음 |
passiveOk | bool | passive check(exchange/queue 존재 확인) 성공 여부. false면 exchange 또는 queue가 삭제/변경된 가능성 |
pingOk | bool | pingCheck publish→confirm이 정상 응답됐는지 여부 |
consumOk | bool | true = 소비 지연/정지 상태(consumeStaleMs 초과). false = 정상 소비 중 |
redis){
"status": "UP",
"redisStatus": "ready",
"pingOk": true,
"pingMs": 1,
"errorMessage": null
}
| 필드 | 타입 | 설명 |
|---|---|---|
status | "UP" | "DEGRADED" | "DOWN" | UP: 정상. DEGRADED: 연결은 되나 ping 비정상. DOWN: 연결 불가 |
redisStatus | string | 클라이언트 내부 상태 (ready, connecting, end, reconnecting) |
pingOk | bool | ping 명령 응답 성공 여부 |
pingMs | int | null | ping 왕복 시간(ms). 실패 시 null |
errorMessage | string | null | 에러 메시지. DOWN/DEGRADED 시 존재 |
nats){
"status": "UP",
"reports": [
{
"name": "TRM",
"isConnected": true,
"rttMs": 1,
"server": "b1-nats3.trustnhope.kr:4222",
"sysPing": null,
"jetstream": null
}
]
}
| 필드 | 타입 | 설명 |
|---|---|---|
status | "UP" | "DOWN" | reports 중 하나라도 DOWN이면 DOWN |
reports | array | 연결된 NATS 계정별 상태 목록 |
reports[].name | string | 연결에 사용한 NATS Account 이름 |
reports[].isConnected | bool | 연결 여부 |
reports[].rttMs | int | null | RTT(ms) |
reports[].server | string | 연결된 서버 URL. 클러스터면 url1,url2,url3 형식 |
reports[].sysPing | any | null | 시스템 ping 결과 (미사용 시 null) |
reports[].jetstream | any | null | JetStream 상태 (미사용 시 null) |
{infraName})DB/RabbitMQ/Redis/NATS 외에 추가로 측정하고 싶은 인프라(예: Kafka, Elasticsearch, MongoDB, S3, 외부 gRPC 서비스 등)는 같은 패턴으로 최상위에 필드를 추가합니다.
키 이름 규칙: 인프라 이름 lowerCamelCase. 예: kafka, elasticsearch, mongoDb, searchApi, s3
기본 응답 구조 (공통 필드):
{
"status": "UP",
"isConnected": true,
"pingMs": 3,
"errorMessage": null
}
| 필드 | 타입 | 설명 | 필수 |
|---|---|---|---|
status | "UP" | "DEGRADED" | "DOWN" | UP: 정상. DEGRADED: 연결은 되나 일부 점검 비정상. DOWN: 연결 불가 | ✅ |
isConnected | bool | 인프라와의 연결 활성 여부 | ✅ |
pingMs | int | null | health/ping 명령 왕복 시간(ms). 실패 시 null | ⚪ |
errorMessage | string | null | 에러 메시지. status가 DOWN/DEGRADED일 때만 존재 | ⚪ |
인프라별 특화 필드는 위 공통 필드 뒤에 추가합니다. 예시:
consumerGroup, lagMs, partitionCountclusterStatus ("green"/"yellow"/"red"), indexHealthreplicaSetState, primaryReachableendpoint, statusCode, responseMsHealthCheckResponse 및 하위 구조체를 아래와 같이 정의합니다.
파일 위치: 기존 model, dto, types 패키지 → 없으면 핸들러와 같은 패키지에 healthcheck_types.go 생성.
type HealthCheckResponse struct {
ServiceName string `json:"serviceName"`
Version string `json:"version"`
Region string `json:"region"`
Timestamp string `json:"timestamp"`
DB *DBStatus `json:"db,omitempty"`
// MQ 필드는 키 이름이 동적이므로 map 또는 별도 필드로 처리 — 아래 '동작 흐름 3단계' 참고
Redis *RedisStatus `json:"redis,omitempty"`
NATS *NATSStatus `json:"nats,omitempty"`
}
type DBStatus struct {
Status string `json:"status"`
DBName *string `json:"dbName"`
AllConnections int `json:"allConnections"`
FreeConnections int `json:"freeConnections"`
LimitConnections int `json:"limitConnections"`
WaitingConnections int `json:"waitingConnections"`
AcquireMs *int `json:"acquireMs"`
QueryMs *int `json:"queryMs"`
ErrorMessage *string `json:"errorMessage,omitempty"`
}
type RedisStatus struct {
Status string `json:"status"`
RedisStatus string `json:"redisStatus"`
PingOk bool `json:"pingOk"`
PingMs *int `json:"pingMs"`
ErrorMessage *string `json:"errorMessage"`
}
type NATSStatus struct {
Status string `json:"status"`
Reports []NATSReport `json:"reports"`
}
type NATSReport struct {
Name string `json:"name"`
IsConnected bool `json:"isConnected"`
RttMs *int `json:"rttMs"`
Server string `json:"server"`
SysPing interface{} `json:"sysPing"`
Jetstream interface{} `json:"jetstream"`
}
// MQ: 키 이름이 "{serviceName}MQ" 형식이므로 HealthCheckResponse에 정적 필드로 추가할 때
// json 태그를 서비스명 기반으로 직접 지정합니다. 예: `json:"surveyMQ,omitempty"`
type MQStatus struct {
Status string `json:"status"` // "UP" | "DEGRADED" | "DOWN"
IsConnected bool `json:"isConnected"`
IsChannelOpen bool `json:"isChannelOpen"`
IsReady bool `json:"isReady"`
IsBlockedBroker bool `json:"isBlockedBroker"`
ReconnectCount int `json:"reconnectCount"`
LastConnectedAt *string `json:"lastConnectedAt"`
LastConsumeAgoMs *int `json:"lastConsumeAgoMs"`
LastPingMs *int `json:"lastPingMs"`
PassiveOk bool `json:"passiveOk"`
PingOk bool `json:"pingOk"`
// consumOk: true=소비 지연/정지(consumeStaleMs 초과), false=정상 소비 중
ConsumOk bool `json:"consumOk"`
}
// 추가 사용자 정의 인프라 템플릿.
// 인프라마다 별도 구조체로 정의(예: KafkaStatus, ElasticsearchStatus)하고
// HealthCheckResponse에 정적 필드로 추가합니다.
// 예: `Kafka *KafkaStatus `json:"kafka,omitempty"``
//
// 공통 필드(Status/IsConnected/PingMs/ErrorMessage) 뒤에 인프라별 특화 필드를 덧붙입니다.
type KafkaStatus struct {
Status string `json:"status"` // "UP" | "DEGRADED" | "DOWN"
IsConnected bool `json:"isConnected"`
PingMs *int `json:"pingMs,omitempty"`
ErrorMessage *string `json:"errorMessage,omitempty"`
// 아래는 인프라별 특화 필드 예시 — 실제로는 사용자가 요청한 측정 항목에 맞춰 정의
ConsumerGroup string `json:"consumerGroup,omitempty"`
LagMs *int `json:"lagMs,omitempty"`
}
$ARGUMENTS 에 경로가 있으면 그 디렉토리 기준, 없으면 현재 작업 디렉토리 기준go.mod 위치로 모듈명 확인**/*router*.go, **/*route*.go, **/*handler*.go, **/*server*.gogin.Default(), gin.New(), echo.New(), http.NewServeMux(), chi.NewRouter()"/HealthCheck", "/health", "/ping", "HealthCheck"| import 패턴 | 프레임워크 |
|---|---|
"github.com/gin-gonic/gin" | Gin |
"github.com/labstack/echo" | Echo |
"github.com/go-chi/chi" | Chi |
"net/http" only | 표준 net/http |
특정 불가 시 사용자에게 확인합니다.
코드베이스를 Grep으로 스캔해서 각 인프라 사용 여부를 판단합니다.
| 인프라 | 탐지 패턴 |
|---|---|
| DB (MySQL) | sql.Open, gorm.Open, xorm, sqlx, database/sql import |
| RabbitMQ | amqp.Dial, rabbitmq, streadway/amqp, rabbitmq/amqp091-go |
| Redis | redis.NewClient, go-redis, redigo |
| NATS | nats.Connect, nats-io/nats.go |
MQ 키 이름 결정: MQ를 사용한다면 serviceName 환경변수로 가져온 값에 MQ를 붙여 키를 만듭니다.
HealthCheckResponse에 MQ를 정적 필드로 추가하되, json 태그는 "{서비스명}MQ" 형식으로 설정SurveyMQ *MQStatus \json:"surveyMQ,omitempty"``자동 탐지된 인프라 외에 HealthCheck 응답에 포함하고 싶은 추가 인프라가 있는지 사용자에게 묻습니다.
자동 탐지된 인프라: DB(MySQL), Redis
이 외에 HealthCheck 응답에 포함할 인프라가 더 있나요? (예: Kafka, Elasticsearch, MongoDB, S3, 외부 gRPC 서비스 등)
포함하고 싶다면 아래 형식으로 알려주세요:
- 인프라명(json 키, lowerCamelCase): 예) `kafka`
- 측정하고 싶은 항목: 공통 필드(status, isConnected, pingMs, errorMessage) 외에 추가로 필요한 필드. 예) Kafka의 `lagMs`, `consumerGroup`
없으면 "없음"이라고 답해주세요.
사용자가 추가 인프라를 지정하면:
{InfraName}Status 구조체 정의HealthCheckResponse에 {InfraName} *{InfraName}Status \json:"{infraName},omitempty"`` 형식으로 정적 필드 추가기존 /HealthCheck 핸들러가 있을 때의 흐름입니다.
아래를 모두 Read 합니다:
현재 응답 구조와 TNH 표준 스키마를 필드별로 대조해 아래 4가지 문제 유형을 찾습니다.
① 누락된 필드 — 스키마에는 있지만 현재 구조체에 없는 필드
| 누락 필드 | 소속 구조체 | 조치 |
|---|---|---|
(예) region | 최상위 | 구조체에 필드 추가 + 핸들러에서 env 읽어 할당 |
(예) lastConsumeAgoMs | MQStatus | 구조체에 필드 추가 + 값 할당 로직 추가 |
② json 태그 불일치 — 필드는 있지만 JSON 키 이름이 다른 경우
| Go 필드명 | 현재 json 태그 | 표준 json 태그 | 조치 |
|---|---|---|---|
(예) ServiceName | "service_name" | "serviceName" | 태그 수정 |
③ 타입 불일치 — 필드는 있지만 nullable 처리가 다른 경우
| 필드 | 현재 타입 | 표준 타입 | 조치 |
|---|---|---|---|
(예) AcquireMs | int | *int | 포인터로 변경, 관련 할당 코드 수정 |
④ 값 출처 불일치 — 필드는 있지만 할당 방식이 다른 경우
| 필드 | 현재 할당 | 표준 할당 | 조치 |
|---|---|---|---|
(예) timestamp | time.Now().Format(time.RFC3339) | time.Now().Format("20060102150405") | 포맷 수정 |
(예) serviceName | "hardcoded-name" | os.Getenv("SERVICENAME") 폴백 체인 | env 읽기로 변경 |
⑤ 추가 인프라 신규 포함 — 3-1단계에서 사용자가 추가 인프라 측정을 요청한 경우
| 추가 인프라 | json 키 | 측정 항목 | 조치 |
|---|---|---|---|
| (예) Kafka | kafka | 공통 필드 + lagMs, consumerGroup | 구조체 신규 정의 + HealthCheckResponse 필드 추가 + 핸들러 수집 로직 추가 |
변경 예정 내용을 먼저 보여주고 확인을 받습니다:
기존 /HealthCheck 핸들러를 발견했습니다. TNH 표준 스키마와 대조한 결과:
🔴 누락된 필드 (추가 필요):
- 최상위: region, timestamp
- DBStatus: waitingConnections, acquireMs(*int), queryMs(*int), errorMessage
🟡 json 태그 불일치 (수정 필요):
- ServiceName: "service_name" → "serviceName"
- DbName: "db_name" → "dbName"
🟡 타입 불일치 (수정 필요):
- DBStatus.AcquireMs: int → *int
🟡 값 출처 불일치 (수정 필요):
- timestamp: RFC3339 형식 → "20060102150405" (14자리) 형식
✅ 이미 스키마와 일치하는 필드:
- serviceName 할당 (env), version (env), db.status, db.allConnections ...
변경된 파일 예정:
- internal/handler/healthcheck.go
- internal/model/healthcheck.go
수정을 진행할까요?
사용자 확인 후 아래 순서로 수정합니다:
int → *int 등)으로 인한 컴파일 오류 파악 — 관련 할당 코드도 함께 수정기존 동작하는 코드는 최소한으로만 건드립니다. 이미 올바른 필드는 수정하지 않습니다.
✅ /HealthCheck 스키마 수정 완료
수정된 파일:
- [구조체 파일]: [수정된 항목 목록]
- [핸들러 파일]: [수정된 항목 목록]
⚠️ 확인 필요:
- region 값: REGION 환경변수가 없으면 "unknown" 반환 — 인프라팀에 환경변수 주입 요청 필요
테스트:
curl http://localhost:[PORT]/HealthCheck
기존 /HealthCheck 핸들러가 없을 때의 흐름입니다.
internal/handler/, handler/, api/ 디렉토리가 있으면 그 안에 healthcheck.gomodel, dto, types, domain 패키지가 있으면 그 안에 추가환경변수 읽기:
func getEnvFallback(keys ...string) string {
for _, k := range keys {
if v := os.Getenv(k); v != "" {
return v
}
}
return ""
}
serviceName := getEnvFallback("SERVICENAME", "SERVICE_NAME", "APP_NAME", "SERVICE")
version := os.Getenv("VERSION")
region := getEnvFallback("REGION", "DEPLOY_REGION", "SERVER_REGION")
if region == "" {
region = "unknown" // TODO: REGION 환경변수 주입 필요 — 인프라팀 확인
}
timestamp := time.Now().Format("20060102150405")
func HealthCheck(c *gin.Context) {
c.JSON(http.StatusOK, buildHealthCheckResponse())
}
func HealthCheck(c echo.Context) error {
return c.JSON(http.StatusOK, buildHealthCheckResponse())
}
func HealthCheck(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(buildHealthCheckResponse())
}
buildHealthCheckResponse()는 각 인프라 상태를 수집해 HealthCheckResponse를 반환하는 함수입니다.
실제 DB/Redis/NATS 연결 객체는 의존성 주입 방식(파라미터 또는 receiver)으로 전달합니다.
기존 코드에서 DB/Redis/NATS 인스턴스가 어떻게 관리되는지 확인한 뒤, 그 패턴을 그대로 따릅니다.
3-1단계에서 사용자가 지정한 추가 인프라가 있다면, 해당 인프라 클라이언트도 같은 방식으로 의존성 주입을 받아 buildHealthCheckResponse() 내부에서 상태를 수집해 응답에 포함합니다.
기존 라우트 등록 패턴을 그대로 따릅니다:
r.GET("/HealthCheck", handler.HealthCheck)
라우트 그룹이 있어도 /HealthCheck는 인증 미들웨어 밖(전역)에 등록합니다.
그룹 안에 넣어야 하는 사유가 있으면 사용자에게 먼저 확인합니다.
✅ /HealthCheck 엔드포인트 신규 생성 완료
생성/수정된 파일:
- [라우터 파일]: GET /HealthCheck 라우트 등록
- [핸들러 파일]: HealthCheck 핸들러 구현
- [타입 파일]: HealthCheckResponse 구조체 정의
⚠️ 확인 필요:
- region 값: REGION 환경변수가 없으면 "unknown" 반환 — 인프라팀에 환경변수 주입 요청 필요
테스트:
curl http://localhost:[PORT]/HealthCheck
omitempty를 활용해 사용하지 않는 인프라 필드는 응답에서 자동 생략합니다Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub shjhwoo/tnh-dev-plugin --plugin tnh-tools