Skip to content
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
147 commits
Select commit Hold shift + click to select a range
83f774a
Traefik ingress 알림 label 수정
Neibce Jun 18, 2026
d5997d0
Grafana 대시보드 운영 지표 정리
Neibce Jun 18, 2026
a61eb8c
플랫폼과 데이터스토어 관측 지표 보강
Neibce Jun 18, 2026
38eb41e
관측성 알림 대응 runbook 추가
Neibce Jun 18, 2026
203b580
Sentry 기본 개인정보 전송 비활성화
Neibce Jun 18, 2026
179644f
트레이싱 도입 전제 조건 문서화
Neibce Jun 18, 2026
0c062cd
Loki와 Alloy 로그 수집 설정 추가
Neibce Jun 18, 2026
dfea20c
로그 스택 관측 알림과 대시보드 보강
Neibce Jun 18, 2026
8e8be19
관측성 매니페스트 검증 스크립트 추가
Neibce Jun 18, 2026
6fa61b5
데이터스토어 세부 관측 지표 보강
Neibce Jun 18, 2026
999be2d
Redis 메모리 알림 오탐 방지
Neibce Jun 18, 2026
9a9780d
Sentry 이벤트 민감정보 필터링 보강
Neibce Jun 18, 2026
1d8ecc6
Sentry 릴리즈 태그 배포 연동
Neibce Jun 18, 2026
e8e3a55
프론트 요청 ID 전파 추가
Neibce Jun 19, 2026
01bf747
알림 이메일 fallback 예시 추가
Neibce Jun 19, 2026
ee2e225
관측성 로그와 요청 ID 테스트 추가
Neibce Jun 19, 2026
41f539e
트레이싱 비활성 경로 안전화
Neibce Jun 19, 2026
a0adaab
관측성 매니페스트 검증 강화
Neibce Jun 19, 2026
71191bf
Judge 서버 관측 지표 회귀 테스트 추가
Neibce Jun 19, 2026
86702e9
관측성 실클러스터 점검 스크립트 추가
Neibce Jun 19, 2026
5eaeb90
관측성 selector 검증 보강
Neibce Jun 19, 2026
06b943d
로그 저장소 운영 전제 검증 보강
Neibce Jun 19, 2026
b4edefa
관측성 검증 CI 추가
Neibce Jun 19, 2026
44969cb
깃허브 액션 제거와 메트릭 테스트 추가
Neibce Jun 19, 2026
d802e39
온프레미스 트레이싱 스택 추가
Neibce Jun 19, 2026
6b66ee5
로그와 트레이스 상관관계 추가
Neibce Jun 19, 2026
3d39fb8
Celery 작업 관측 지표 추가
Neibce Jun 19, 2026
eb7f405
Celery 작업 request ID 전파 추가
Neibce Jun 19, 2026
aac001e
프론트 nginx 요청 로그 상관관계 추가
Neibce Jun 19, 2026
0f14c15
Ingress 로그 수집 관측성 추가
Neibce Jun 19, 2026
798a0d9
Longhorn 스토리지 관측성 추가
Neibce Jun 19, 2026
a350d3e
공개 엔드포인트 관측성 추가
Neibce Jun 19, 2026
07b3887
vLLM AI 추론 관측성 추가
Neibce Jun 19, 2026
7548395
hub-auth 관측성 추가
Neibce Jun 19, 2026
f236011
GPU 관측성 추가
Neibce Jun 19, 2026
8a6b4bb
프론트엔드 오류 관측성 추가
Neibce Jun 19, 2026
5778e17
모니터링 스택 관측성 추가
Neibce Jun 19, 2026
a1c1e36
쿠버네티스 이벤트 관측성 추가
Neibce Jun 19, 2026
b56560c
관측 파이프라인 실패 알림 보강
Neibce Jun 19, 2026
e4eff0c
데이터스토어 병목 관측성 추가
Neibce Jun 19, 2026
bb8a8d2
채점 중단 관측성 추가
Neibce Jun 19, 2026
cbdb1aa
배포 롤아웃 관측성 추가
Neibce Jun 19, 2026
620d7c9
로그 파이프라인 장애 관측성 추가
Neibce Jun 19, 2026
fb3c03c
그라파나 공개 경로 관측성 추가
Neibce Jun 19, 2026
f75b941
채점 경로 추적 구간 보강
Neibce Jun 19, 2026
c489eea
운영 요약 기록 규칙 추가
Neibce Jun 19, 2026
374f083
핵심 P0 알림 누락 조건 보강
Neibce Jun 19, 2026
e32953f
서비스 엔드포인트 관측성 추가
Neibce Jun 19, 2026
96b1590
AI 힌트 백엔드 관측성 추가
Neibce Jun 19, 2026
7c25879
AI 힌트 API 결과 관측성 추가
Neibce Jun 19, 2026
aa9520a
제출 생성 경로 관측성 추가
Neibce Jun 19, 2026
bc39238
채점 작업 결과 관측성 추가
Neibce Jun 19, 2026
6f56294
정기 작업 신선도 관측성 추가
Neibce Jun 19, 2026
f1792a6
모니터링 검증 범위 보강
Neibce Jun 19, 2026
c9e8164
모니터링 스모크 체크 범위 보강
Neibce Jun 19, 2026
0d8f27f
앱 관측성 배선 검증 추가
Neibce Jun 19, 2026
dbcd5c5
서비스 엔드포인트 스모크 체크 보강
Neibce Jun 19, 2026
e4a1999
트래픽 인그레스 메트릭 수집 추가
Neibce Jun 19, 2026
775507f
관측성 런북 알림명 정리
Neibce Jun 19, 2026
1c825f2
셀러리 관측성 메트릭 수집 안정화
Neibce Jun 19, 2026
cc7a39e
프로메테우스 룰 검증 방식 수정
Neibce Jun 19, 2026
7ced7ec
인그레스 5xx 알림 라벨 호환성 보강
Neibce Jun 19, 2026
388618b
개요 대시보드 인그레스 지표 추가
Neibce Jun 19, 2026
1671407
로그 스택 스모크 체크 필수화
Neibce Jun 19, 2026
7f6c01d
그라파나 데이터소스 검증 강화
Neibce Jun 19, 2026
32120d3
모니터링 헬름 차트 버전 고정
Neibce Jun 19, 2026
aaf8d5e
알림 라우팅 네임스페이스 매처 해제
Neibce Jun 19, 2026
83e866c
알림 매처 스모크 체크 보강
Neibce Jun 19, 2026
9916167
그라파나 데이터소스 스모크 체크 보강
Neibce Jun 19, 2026
a707e0d
Alloy 파드 로그 경로 마운트 추가
Neibce Jun 19, 2026
9fe8490
프론트엔드 nginx 로그 검증 보강
Neibce Jun 19, 2026
0cf92d4
모니터링 selector 검증 보강
Neibce Jun 19, 2026
e9410ca
Loki 차트 배포 모드 정정
Neibce Jun 19, 2026
95adf0b
Alloy 로그 수집 범위 보강
Neibce Jun 19, 2026
d33b003
로그 스택 스모크 체크 보강
Neibce Jun 19, 2026
fcef109
로그 대시보드 네임스페이스 범위 정리
Neibce Jun 19, 2026
6e08419
로그 대시보드 빈 패널 방지
Neibce Jun 19, 2026
d839034
요청 ID 헤더 안전성 보강
Neibce Jun 19, 2026
3bf7860
프론트엔드 로그 민감 URL 제거
Neibce Jun 19, 2026
12bcad0
관측성 운영 문서 최신화
Neibce Jun 19, 2026
816dcf0
알림 SealedSecret 운영 절차 보강
Neibce Jun 19, 2026
3edf6d8
트레이스 대시보드 검증 보강
Neibce Jun 19, 2026
802e974
트레이스 쿠버네티스 메타데이터 보강
Neibce Jun 19, 2026
27eef1c
관측성 검증 스크립트 제거와 컬렉터 분산 보강
Neibce Jun 19, 2026
64539fe
공개 엔드포인트 프로브 분산 보강
Neibce Jun 19, 2026
bbc40ee
모니터링 스택 상태 저장 보강
Neibce Jun 19, 2026
b832c3f
Longhorn 매니저 부분 장애 관측 보강
Neibce Jun 19, 2026
0e03428
누적 메트릭 카운터 타입 정합성 보강
Neibce Jun 19, 2026
0611776
공개 엔드포인트 프로브 누락 알림 보강
Neibce Jun 19, 2026
8ea437f
데이터스토어 HA 알림 등급 정리
Neibce Jun 19, 2026
57e6ebd
알림 경로와 Loki 게이트웨이 HA 보강
Neibce Jun 19, 2026
24454c7
관측 스택 unavailable 알림 기준 정리
Neibce Jun 19, 2026
049d0fc
모니터링 설정 reload 실패 감지 보강
Neibce Jun 19, 2026
d6aa459
dev P1 외부 알림 라우팅 분리
Neibce Jun 19, 2026
06a540a
핵심 작업 엔드포인트 결측 알림 보강
Neibce Jun 19, 2026
2ca6071
GitHub Actions 변경 제외
Neibce Jun 19, 2026
274b7b8
Prometheus 고가용성 관측 기준 보강
Neibce Jun 19, 2026
860be56
Grafana 메트릭 관측 경로 보강
Neibce Jun 19, 2026
4cf0495
모니터링 서비스 target 장애 감지 보강
Neibce Jun 19, 2026
9f57e6e
모니터링 target 알림 범위 정교화
Neibce Jun 19, 2026
09d3c5f
관측 알림 runbook 검색성 보강
Neibce Jun 19, 2026
8784d2a
Trace 대시보드 조회 패널 보강
Neibce Jun 19, 2026
10c6d99
로그 유입 대시보드 세분화
Neibce Jun 19, 2026
07af749
알림 메시지 진단 링크 보강
Neibce Jun 19, 2026
4b3a7f1
Frontend readiness probe 보강
Neibce Jun 19, 2026
6f09295
사용자 경로 PDB 보강
Neibce Jun 19, 2026
a06bb70
PDB 위험 상태 관측 보강
Neibce Jun 19, 2026
6b140fa
Grafana 관측 대상 정규화
Neibce Jun 19, 2026
614cb88
로그 노드별 관측 보강
Neibce Jun 19, 2026
b5f3067
채점 소요 시간 관측 기준 보정
Neibce Jun 19, 2026
06191a2
스케줄 작업 미관측 알림 보강
Neibce Jun 19, 2026
2313d14
채점 완료 시간 관측 인덱스 추가
Neibce Jun 19, 2026
478903f
제출 대기 관측 인덱스 추가
Neibce Jun 19, 2026
61ff394
Alloy 노드 로그 수집 범위 보강
Neibce Jun 19, 2026
6ca85d7
Tempo PVC 롤아웃 전략 보강
Neibce Jun 19, 2026
21b6a78
제출 테이블 관측 조회 제거
Neibce Jun 19, 2026
1526adb
불필요한 제출 관측 인덱스 제거
Neibce Jun 19, 2026
f3930f5
백엔드 메트릭의 DB 직접 조회 제거
Neibce Jun 19, 2026
ae5c36e
Loki 게이트웨이 메트릭 대상 정정
Neibce Jun 19, 2026
21685cd
알림 라우팅 기본 수신자 무해화
Neibce Jun 19, 2026
72a2c01
judge 서버 관측 DB 조회 제거
Neibce Jun 19, 2026
3815830
Redis 관측 수집 성공 판정 보정
Neibce Jun 19, 2026
9a15919
Overview 대시보드 Prometheus 명시
Neibce Jun 19, 2026
853d9bb
judge 서버 관측 스냅샷 정리
Neibce Jun 19, 2026
6e37405
Alloy 로그 수집 설정 문법 정리
Neibce Jun 19, 2026
a90d8e4
Alertmanager 웹훅 시크릿 감시 추가
Neibce Jun 19, 2026
5cb9a6d
서비스 endpoint 관측 메트릭 정정
Neibce Jun 19, 2026
f79e9be
모니터링 Helm 설치 절차 보강
Neibce Jun 19, 2026
fd57d99
OTel Collector 메트릭 엔드포인트 명시
Neibce Jun 19, 2026
e7db2e9
OpenTelemetry 초기화 중복 방지
Neibce Jun 19, 2026
2daad2e
노드 압력 알림 라벨 보강
Neibce Jun 19, 2026
bed6630
공개 엔드포인트 알림 범위 제한
Neibce Jun 19, 2026
e4c5257
관측성 문서와 미사용 메트릭 정리
Neibce Jun 19, 2026
7065c80
Alertmanager 이메일 예시 제거
Neibce Jun 19, 2026
bf022c2
Sentry 릴리즈 하드코딩 제거
Neibce Jun 19, 2026
80ae303
불필요한 관측성 예외처리 제거
Neibce Jun 19, 2026
753a9dd
과한 관측성 알림 제거
Neibce Jun 19, 2026
582d02c
judge 서버 우회 메트릭 제거
Neibce Jun 19, 2026
e1ee6f5
Redis 누적 Celery 상세 메트릭 제거
Neibce Jun 19, 2026
bc8f42e
Sentry 릴리즈 값을 배포 SHA로 갱신
Neibce Jun 19, 2026
66bf6ad
Sentry 프론트엔드 빌드 설정 연결
Neibce Jun 19, 2026
4c102ec
Sentry 릴리즈 설정 단일화
Neibce Jun 19, 2026
def0ed6
Sentry 릴리즈를 APP_VERSION으로 통일
Neibce Jun 19, 2026
e3f6556
APP_VERSION 주입 방식 정리
Neibce Jun 19, 2026
83e525e
내부 frontend error 수집 제거
Neibce Jun 19, 2026
b3a80c2
과한 Redis 관측 메트릭 제거
Neibce Jun 19, 2026
8733744
불필요한 관측성 코드 정리
Neibce Jun 19, 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
14 changes: 13 additions & 1 deletion .github/workflows/ci2develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@ jobs:
with:
cmd: yq -i '(.images[] | select(.name == "backend").newTag) = "${{ github.sha }}-dev"' kubernetes/overlays/dev/kustomization.yaml

- name: Update Backend Sentry Release
if: needs.ci-backend-dev.result == 'success'
uses: mikefarah/yq@master
with:
cmd: |
yq -i '(.spec.template.spec.containers[] | select(.name == "backend").env[] | select(.name == "SENTRY_RELEASE").value) = "${{ github.sha }}-dev"' kubernetes/overlays/dev/backend-deployment-patch.yaml
yq -i '(.spec.template.spec.containers[] | select(.name == "celery-worker").env[] | select(.name == "SENTRY_RELEASE").value) = "${{ github.sha }}-dev"' kubernetes/overlays/dev/celery-worker-deployment-patch.yaml
yq -i '(.spec.template.spec.containers[] | select(.name == "celery-beat").env[] | select(.name == "SENTRY_RELEASE").value) = "${{ github.sha }}-dev"' kubernetes/overlays/dev/celery-beat-deployment-patch.yaml

- name: Update Frontend Image Tag
if: needs.ci-frontend-dev.result == 'success'
uses: mikefarah/yq@master
Expand All @@ -152,7 +161,10 @@ jobs:
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add kubernetes/overlays/dev/kustomization.yaml
git add kubernetes/overlays/dev/kustomization.yaml \
kubernetes/overlays/dev/backend-deployment-patch.yaml \
kubernetes/overlays/dev/celery-worker-deployment-patch.yaml \
kubernetes/overlays/dev/celery-beat-deployment-patch.yaml

# 변경사항이 없으면 종료
if git diff-index --quiet HEAD; then
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/ci2production-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,15 @@ jobs:
with:
cmd: yq -i '(.images[] | select(.name == "backend").newTag) = "${{ github.sha }}-prod"' kubernetes/overlays/prod/kustomization.yaml

- name: Update Backend Sentry Release
if: needs.ci-backend.result == 'success'
uses: mikefarah/yq@master
with:
cmd: |
yq -i '(.spec.template.spec.containers[] | select(.name == "backend").env[] | select(.name == "SENTRY_RELEASE").value) = "${{ github.sha }}-prod"' kubernetes/overlays/prod/backend-deployment-patch.yaml
yq -i '(.spec.template.spec.containers[] | select(.name == "celery-worker").env[] | select(.name == "SENTRY_RELEASE").value) = "${{ github.sha }}-prod"' kubernetes/overlays/prod/celery-worker-deployment-patch.yaml
yq -i '(.spec.template.spec.containers[] | select(.name == "celery-beat").env[] | select(.name == "SENTRY_RELEASE").value) = "${{ github.sha }}-prod"' kubernetes/overlays/prod/celery-beat-deployment-patch.yaml

- name: Update Frontend Image Tag
if: needs.ci-frontend.result == 'success'
uses: mikefarah/yq@master
Expand All @@ -181,7 +190,10 @@ jobs:
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add kubernetes/overlays/prod/kustomization.yaml
git add kubernetes/overlays/prod/kustomization.yaml \
kubernetes/overlays/prod/backend-deployment-patch.yaml \
kubernetes/overlays/prod/celery-worker-deployment-patch.yaml \
kubernetes/overlays/prod/celery-beat-deployment-patch.yaml

# 변경사항이 없으면 종료
if git diff-index --quiet HEAD; then
Expand Down
28 changes: 27 additions & 1 deletion backend/account/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from django.contrib import auth
from django.db import DatabaseError
from django.http.response import JsonResponse
from django.test import RequestFactory, TestCase
from django.test import RequestFactory, SimpleTestCase, TestCase
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed
from django.utils import decorators
from django.utils.timezone import now
from utils.api.tests import APIClient, APITestCase
Expand All @@ -18,9 +18,35 @@

from .models import AdminType, ProblemPermission, User
from .decorators import scheduler_only
from .middleware import RequestIDMiddleware
from .tasks import calculate_user_score_basis, calculate_user_score_fluctuation


class RequestIDMiddlewareTest(SimpleTestCase):

def setUp(self):
self.factory = RequestFactory()

def test_generates_request_id_when_header_is_missing(self):
middleware = RequestIDMiddleware(lambda request: JsonResponse({"ok": True}))
request = self.factory.get("/")

response = middleware(request)

self.assertEqual(len(request.request_id), 32)
self.assertEqual(response["X-Request-ID"], request.request_id)

def test_accepts_existing_request_id_and_truncates_it(self):
request_id = "a" * 150
middleware = RequestIDMiddleware(lambda request: JsonResponse({"ok": True}))
request = self.factory.get("/", HTTP_X_REQUEST_ID=request_id)

response = middleware(request)

self.assertEqual(request.request_id, "a" * 128)
self.assertEqual(response["X-Request-ID"], request.request_id)


class PermissionDecoratorTest(APITestCase):
"""
데코레이터 테스트
Expand Down
4 changes: 2 additions & 2 deletions backend/judge/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from django.db import transaction, IntegrityError
from django.db.models import F
from django.http import HttpResponseNotFound
from opentelemetry import trace

from account.models import User, UserScore, UserSolved
from conf.models import JudgeServer
Expand All @@ -21,9 +20,10 @@
from submission.models import JudgeStatus, Submission
from utils.cache import cache
from utils.constants import CacheKey, ProblemScore, ProblemField, Tier
from utils.observability_tracing import get_tracer

logger = logging.getLogger(__name__)
tracer = trace.get_tracer(__name__)
tracer = get_tracer(__name__)


# 继续处理在队列中的问题
Expand Down
4 changes: 2 additions & 2 deletions backend/judge/tasks.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import celery
from opentelemetry import trace

from account.models import User
from utils.shortcuts import CELERY_TASK_ARGS
from utils.observability_tracing import get_tracer
from submission.models import Submission
from judge.dispatcher import JudgeDispatcher

tracer = trace.get_tracer(__name__)
tracer = get_tracer(__name__)


@celery.shared_task(**CELERY_TASK_ARGS())
Expand Down
2 changes: 2 additions & 0 deletions backend/oj/celery.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import celery

from utils.celery_observability import configure_celery_observability
from utils.observability_tracing import configure_opentelemetry

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'oj.settings')
Expand All @@ -9,3 +10,4 @@
app = celery.Celery('scheduler')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()
configure_celery_observability()
65 changes: 60 additions & 5 deletions backend/oj/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,71 @@
else:
from .dev_settings import *

SENTRY_SENSITIVE_EXACT_FIELDS = {
"authorization",
"cookie",
"password",
"token",
"secret",
"code",
"src",
}
SENTRY_SENSITIVE_SUBSTRINGS = {
"authorization",
"cookie",
"password",
"token",
"secret",
"source_code",
"sourcecode",
"spj_code",
"src",
}


def _is_sentry_sensitive_key(key):
normalized = str(key).lower()
return (
normalized in SENTRY_SENSITIVE_EXACT_FIELDS
or any(field in normalized for field in SENTRY_SENSITIVE_SUBSTRINGS)
)


def _redact_sentry_value(value):
if isinstance(value, dict):
return {
key: "[REDACTED]" if _is_sentry_sensitive_key(key) else _redact_sentry_value(item)
for key, item in value.items()
}
if isinstance(value, list):
return [_redact_sentry_value(item) for item in value]
return value


def _before_send_sentry(event, hint):
event = _redact_sentry_value(event)
request = event.get("request") or {}
request_id = (request.get("headers") or {}).get("X-Request-ID")
if request_id:
event.setdefault("tags", {})["request_id"] = request_id
return event


SENTRY_DSN = get_env(
"SENTRY_DSN",
"",
)
if SENTRY_DSN:
sentry_sdk.init(
dsn=SENTRY_DSN,
send_default_pii=True,
environment=get_env("SENTRY_ENVIRONMENT", get_env("OJ_ENV", "dev")),
)
SENTRY_OPTIONS = {
"dsn": SENTRY_DSN,
"send_default_pii": False,
"environment": get_env("SENTRY_ENVIRONMENT", get_env("OJ_ENV", "dev")),
"before_send": _before_send_sentry,
}
SENTRY_RELEASE = get_env("SENTRY_RELEASE", "")
if SENTRY_RELEASE:
SENTRY_OPTIONS["release"] = SENTRY_RELEASE
sentry_sdk.init(**SENTRY_OPTIONS)

with open(os.path.join(DATA_DIR, "config", "secret.key"), "r") as f:
SECRET_KEY = f.read()
Expand Down
97 changes: 97 additions & 0 deletions backend/utils/celery_observability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import logging
import time

from celery import signals

from utils.cache import cache

logger = logging.getLogger("codeplace.celery_task")

CELERY_TASK_TOTAL_KEY = "observability:celery:task_total"
CELERY_TASK_RUNTIME_BUCKET_KEY = "observability:celery:task_runtime_bucket"
CELERY_TASK_RUNTIME_COUNT_KEY = "observability:celery:task_runtime_count"
CELERY_TASK_RUNTIME_SUM_KEY = "observability:celery:task_runtime_sum"
CELERY_TASK_RUNTIME_LAST_KEY = "observability:celery:task_runtime_last"
CELERY_TASK_RUNTIME_BUCKETS = (1, 3, 5, 10, 30, 60, 120, 300, 600, float("inf"))

_task_start_times = {}
_configured = False


def configure_celery_observability():
global _configured
if _configured:
return
signals.task_prerun.connect(_on_task_prerun, weak=False)
signals.task_postrun.connect(_on_task_postrun, weak=False)
signals.task_retry.connect(_on_task_retry, weak=False)
_configured = True
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed


def _task_name(task):
return getattr(task, "name", None) or getattr(task, "__name__", None) or "unknown"


def _field(*parts):
return "|".join(str(part) for part in parts)


def _hincrbyfloat(key, field, amount):
client = cache.client.get_client(write=True)
return client.hincrbyfloat(key, field, amount)


def _hsetfloat(key, field, value):
client = cache.client.get_client(write=True)
return client.hset(key, field, value)


def _record_task(task_name, status, duration_seconds=None):
status = (status or "unknown").lower()
try:
cache.hincrby(CELERY_TASK_TOTAL_KEY, _field(task_name, status), 1)
if duration_seconds is not None:
_hincrbyfloat(CELERY_TASK_RUNTIME_SUM_KEY, task_name, duration_seconds)
cache.hincrby(CELERY_TASK_RUNTIME_COUNT_KEY, task_name, 1)
_hsetfloat(CELERY_TASK_RUNTIME_LAST_KEY, _field(task_name, status), duration_seconds)
for bucket in CELERY_TASK_RUNTIME_BUCKETS:
if duration_seconds <= bucket:
bucket_label = "+Inf" if bucket == float("inf") else str(bucket)
cache.hincrby(CELERY_TASK_RUNTIME_BUCKET_KEY, _field(task_name, bucket_label), 1)
except Exception:
logger.exception("failed to record celery task metric", extra={
"task_name": task_name,
"task_status": status,
})


def _on_task_prerun(task_id=None, task=None, **kwargs):
if task_id:
_task_start_times[task_id] = time.monotonic()


def _on_task_postrun(task_id=None, task=None, state=None, **kwargs):
task_name = _task_name(task)
started_at = _task_start_times.pop(task_id, None)
duration_seconds = None
if started_at is not None:
duration_seconds = max(time.monotonic() - started_at, 0)
_record_task(task_name, state, duration_seconds)
logger.info("celery task completed", extra={
"task_id": task_id,
"task_name": task_name,
"task_status": (state or "unknown").lower(),
"duration_ms": round(duration_seconds * 1000, 2) if duration_seconds is not None else None,
})


def _on_task_retry(request=None, reason=None, **kwargs):
task_name = getattr(request, "task", None) or "unknown"
task_id = getattr(request, "id", None)
_record_task(task_name, "retry")
logger.warning("celery task retry", extra={
"task_id": task_id,
"task_name": task_name,
"task_status": "retry",
"retry_reason": str(reason) if reason else None,
})
5 changes: 5 additions & 0 deletions backend/utils/json_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from datetime import datetime, timezone

from utils.observability_context import get_request_id
from utils.observability_tracing import get_current_trace_context


SENSITIVE_EXACT_FIELDS = {
Expand Down Expand Up @@ -37,6 +38,7 @@ def format(self, record):
"message": record.getMessage(),
"request_id": getattr(record, "request_id", None) or get_request_id(),
}
payload.update(get_current_trace_context())
for key in (
"method",
"path",
Expand All @@ -45,7 +47,10 @@ def format(self, record):
"user_id",
"remote_addr",
"task_id",
"task_name",
"task_status",
"submission_id",
"retry_reason",
):
value = getattr(record, key, None)
if value is not None:
Expand Down
Loading