
๐ ๋ค์ด๊ฐ๋ฉฐ
์ด์ ๊ธ์์ ๊ตฌ์ถํ ๋ชจ๋ํฐ๋ง ์คํ(Prometheus + Loki + Grafana)์ ํ์ฉํด ์ฅ์ ์ํฉ์ ์ค์๊ฐ์ผ๋ก Slack์ผ๋ก ์๋ฆผ๋ฐ๋ ๋ฐฉ๋ฒ์ ๊ณต์ ํฉ๋๋ค.
์ด ๊ธ์์ ๋ค๋ฃฐ ๋ด์ฉ์ ์๋์ ๊ฐ์ต๋๋ค.
- Slack Webhook ์ฐ๋ (Contact Point ์ค์ )
- ์๋ฒ ๋ค์ด ์๋ฆผ (Prometheus Alert)
- ERROR ๋ก๊ทธ ์๋ฆผ (Loki Alert)
- ์ถ๊ฐํ๋ฉด ์ข์ ์๋ฆผ ๋ชจ์
๐ 1. Slack ์๋ฆผ ์ค์
Grafana์ Alert Rules์์ ์กฐ๊ฑด์ ์ค์ ํ๊ณ , Contact Points์์ Slack Webhook URL์ ๋ฑ๋กํ๋ฉด ์๋ฆผ์ ๋ฐ์ ์ ์์ต๋๋ค.
Slack ์์ Webhook URL ๋ฐ๊ธ๋ฐ๊ธฐ

Slack Marketplace ์ ์

incoming webhook ๊ฒ์

Slack์ ์ถ๊ฐ ํด๋ฆญ

์๋ฆผ์ ๋ฐ๊ณ ์ ํ๋ ์ฑ๋ ์ ํ

์นํํฌ URL ๋ณต์ฌํด๋๊ธฐ
Slack Contact Point ์ค์


Grafana > Alerting > Contact Points > Add contact point์์ Slack์ ์ ํํ๊ณ ์์์ ๋ณต์ฌํด๋ Webhook URL์ ์ ๋ ฅํฉ๋๋ค.

Alert rules > New alert rule > 5. Configure notifications ์ ์ถ๊ฐํ contact point๋ฅผ ์ ํํ๋ฉด ์๋ฆผ์ด ํด๋น ์ฑ๋๋ก ์๋ฆผ์ด ๊ฐ๋๋ค.
์๋ฒ ๋ค์ด ์๋ฆผ (Prometheus Alert)
Prometheus์ up ์งํ๊ฐ 0์ด ๋๋ฉด(์๋ฒ ์๋ต ์์) Slack์ผ๋ก ์๋ฆผ์ ๋ฐ์กํฉ๋๋ค.

Grafana > Alerting > Alert Rules > New alert rule์์ ์๋์ ๊ฐ์ด ์ค์ ํฉ๋๋ค.

up{instance="<prod ์๋ฒ ip>"}
ERROR ๋ก๊ทธ ์๋ฆผ (Loki Alert)
์ ํ๋ฆฌ์ผ์ด์ ์์ ERROR ๋ก๊ทธ๊ฐ ๋ฐ์ํ๋ฉด ์๋ฆผ์ ๋ฐ์กํฉ๋๋ค.

๊ธฐ์กด ์๋ฌ ์๋ฆผ์ ๋ฉ์์ง๋ง ๋ณด๊ณ ์ ํํ ์์ธ์ ํ์ ํ๊ธฐ ์ด๋ ค์ ์๋ฆผ ๋ฉ์์ง ๋ณธ๋ฌธ์ ์ปค์คํ ํ์ฌ ๊ฐ์
์ปค์คํ ์๋ฆผ ๋ฉ์์ง๋ฅผ ์ํ LogQL ์ฟผ๋ฆฌ
count_over_time(
{service_name="goody-api"}
| json
| detected_level="ERROR"
| line_format "{{ .attributes_exception_stacktrace }}"
| regexp "(?m)^\\\\s*at\\\\s+(?P<error_location>org\\\\.re\\\\.goody\\\\.[^\\\\r\\\\n]+)"
| label_format
exception_message="{{ .attributes_exception_message }}",
error_location="{{ .error_location }}",
exception_type="{{ .attributes_exception_type }}",
body="{{ .body }}"
| __error__=""
[5m])
ํ๋ก์ ํธ์ ์์คํ ์๋ฌ๋ ๋๋ถ๋ถ AppException, InfraException์ผ๋ก ๋ฉํ๋๊ธฐ ๋๋ฌธ์ exception.type ํ๋๋ง์ผ๋ก๋ ๊ตฌ์ฒด์ ์ธ ๋ฐ์ ์์ธ์ ํ์ ํ๊ธฐ ์ด๋ ค์ ์ต๋๋ค. ์ด๋ฅผ ๋ณด์ํ๊ธฐ ์ํด exception.stacktrace์์ org.re.goody๋ก ์์ํ๋ ํ๋ก์ ํธ ๋ด๋ถ ํ์ผ ์์น๋ฅผ ์ ๊ท์์ผ๋ก ํ์ฑํ์ฌ error_location ๋ ์ด๋ธ๋ก ์ถ์ถํ์ต๋๋ค.
{
"body":"[Unexpected Error] Exception details",
"traceid":"d2ef8eb328173570f6f016b0dd",
"spanid":"7b65517dc9a",
"severity":"ERROR",
"flags":3,
"attributes":{
"exception.message":"No static resource auth/refresh.",
"exception.stacktrace":"org.springframework.web.servlet.resource.NoResourceFoundException: No static resource auth/refresh.\\n\\tat org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest(ResourceHttpRequestHandler.java:585)\\n\\tat org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:52)\\n\\tat org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherSe...",
"exception.type":"org.springframework.web.servlet.resource.NoResourceFoundException"
},
"instrumentation_scope":{"name":"org.re.goody.common.exception.ErrorLoggingService"
}}
์๋ฆผ ๋ฉ์์ง๋ฅผ ๋ ์ปค์คํ ํ๊ณ ์ถ๋ค๋ฉด, ๋ก๊ทธ์ attributes ํ๋์ ํฌํจ๋ ์์ฑ๋ค(์: exception.message, exception.stacktrace, exception.type ๋ฑ)์ ํ์ธํ๊ณ ํ์ํ ํญ๋ชฉ๋ง label_format์ผ๋ก ์ถ์ถํ๋ฉด ๋ฉ๋๋ค.
์ถ์ถํ ํญ๋ชฉ๋ค์ ์๋ฆผ ๋ฉ์์ง์ ์ ์ฉํ๊ธฐ ์ํด์๋ ์ปค์คํ ์ ํด์ผํฉ๋๋ค.
์๋ฆผ ๋ฉ์์ง ์ ๋ชฉ ๋ฐ ๋ณธ๋ฌธ ์ปค์คํ

Grafana > Alerting > Contact Points ์์ ์ปค์คํ ํ๊ณ ์ํ๋ ์๋ฆผ > edit ์ ์ ํ

Optional Slack settings ๋ฅผ ์ด๋ฉด ์๋ฆผ ๋ฉ์์ง ์ปค์คํ ์ด ๊ฐ๋ฅํฉ๋๋ค.
์ ๋ ๋ณ๋ค๋ฅธ ์ปค์คํ ์์ด Title๊ณผ Text Body๋ง ์ค์ ํ์ต๋๋ค.

Title
๐จ [FIRING] {{ .CommonLabels.alertname }} ( {{ .CommonLabels.exception_message }} )
Text Body
*์๋ฌ ์์ฝ*
{{ .CommonLabels.body }}
*์๋ฌ ์์ธ*
- Message: {{ .CommonLabels.exception_message }}
- Type: {{ .CommonLabels.exception_type }}
*์๋ฌ ๋ฐ์ ์์น*
{{ .CommonLabels.error_location }}
*Trace*
- TraceId: `{{ .CommonLabels.traceid }}`
๐ *Links*
- Logs: <{{ .CommonAnnotations.logs_url }}|๋ฐ๋ก ๋ณด๊ธฐ>
- Trace: <{{ .CommonAnnotations.trace_url }}|๋ฐ๋ก ๋ณด๊ธฐ>
body๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ก๊น ์ ์ถ๋ ฅํ ๋ฉ์์ง ๋ฌธ์์ด์ ๋๋ค. ํ๋ก์ ํธ ๋ก๊น ์ค์ ์ ๋ฐ๋ผ ์ถ๋ ฅ๋ ๊ฐ์ ๋๋ค.

๐ก ์์ผ๋ก ์ถ๊ฐํ ์๋ฆผ๋ค
ํ์ฌ๋ ์๋ฒ ๋ค์ด๊ณผ ERROR ๋ก๊ทธ ๋ ๊ฐ์ง๋ง ์ค์ ๋์ด ์์ต๋๋ค. ์๋ ์๋ฆผ๋ค๋ ์์ฐจ์ ์ผ๋ก ์ถ๊ฐํ ์์ ์ ๋๋ค.
| ์๋ฆผ | ์ฟผ๋ฆฌ ์์ | ๊ถ์ฅ ์๊ณ์น |
| JVM Heap ์ฌ์ฉ๋ฅ ๊ณผ๋ค | jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} | 80% ์ด์ 5๋ถ ์ง์ |
| HTTP 5xx ์๋ฌ ๊ธ์ฆ | rate(http_server_requests_seconds_count{status=~"5.."}[5m]) | ๋ถ๋น 10๊ฑด ์ด์ |
| HTTP ์๋ต์๊ฐ (P99) ์ด๊ณผ | histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m])) | 3์ด ์ด์ |
| GC ๋น๋ ๊ณผ๋ค | rate(jvm_gc_pause_seconds_count[5m]) | ๋ถ๋น 5ํ ์ด์ |