""" 접속 로그 기록을 위한 유틸리티 함수들 """ from django.contrib.auth.models import User from .models import AccessLog, Person def get_client_ip(request): """ 클라이언트의 실제 IP 주소를 추출 프록시, 로드밸런서, CDN 등을 고려하여 실제 IP를 찾음 """ # 프록시나 로드밸런서를 통한 실제 클라이언트 IP 확인 x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: # 첫 번째 IP가 실제 클라이언트 IP (쉼표로 구분된 경우) ip = x_forwarded_for.split(',')[0].strip() if ip and ip != '127.0.0.1': return ip # Cloudflare 등 CDN에서 사용하는 헤더 cf_connecting_ip = request.META.get('HTTP_CF_CONNECTING_IP') if cf_connecting_ip and cf_connecting_ip != '127.0.0.1': return cf_connecting_ip # Nginx 등 리버스 프록시에서 사용 x_real_ip = request.META.get('HTTP_X_REAL_IP') if x_real_ip and x_real_ip != '127.0.0.1': return x_real_ip # 기본 REMOTE_ADDR remote_addr = request.META.get('REMOTE_ADDR', '알 수 없음') # 로컬 환경 표시 if remote_addr == '127.0.0.1': return f"{remote_addr} (로컬 개발환경)" elif remote_addr.startswith('192.168.') or remote_addr.startswith('10.') or remote_addr.startswith('172.'): return f"{remote_addr} (내부 네트워크)" return remote_addr def get_user_agent(request): """사용자 에이전트 가져오기""" return request.META.get('HTTP_USER_AGENT', '') def get_device_info(user_agent): """User-Agent에서 기기 정보 추출""" if not user_agent: return "알 수 없음" # 모바일 기기 체크 if any(mobile in user_agent for mobile in ['iPhone', 'iPad', 'Android', 'Mobile']): if 'iPhone' in user_agent: return "iPhone" elif 'iPad' in user_agent: return "iPad" elif 'Android' in user_agent: if 'Mobile' in user_agent: return "Android 폰" else: return "Android 태블릿" else: return "모바일" # 데스크톱 OS 체크 if 'Windows NT' in user_agent: if 'Windows NT 10.0' in user_agent: return "Windows 10/11" elif 'Windows NT 6.3' in user_agent: return "Windows 8.1" elif 'Windows NT 6.1' in user_agent: return "Windows 7" else: return "Windows" elif 'Macintosh' in user_agent or 'Mac OS X' in user_agent: return "Mac" elif 'Linux' in user_agent and 'Android' not in user_agent: return "Linux" return "알 수 없음" def get_browser_info(user_agent): """User-Agent에서 브라우저 정보 추출""" if not user_agent: return "알 수 없음" # 브라우저 체크 (순서 중요 - Chrome이 Safari 문자열도 포함하므로) if 'Edg/' in user_agent: return "Microsoft Edge" elif 'Chrome/' in user_agent and 'Safari/' in user_agent: return "Chrome" elif 'Firefox/' in user_agent: return "Firefox" elif 'Safari/' in user_agent and 'Chrome' not in user_agent: return "Safari" elif 'Opera' in user_agent or 'OPR/' in user_agent: return "Opera" return "알 수 없음" def log_user_activity(request, action, description=None, user=None, metadata=None): """ 사용자 활동 로그 기록 Args: request: Django request 객체 action: 활동 유형 (AccessLog.ACTION_CHOICES 중 하나) description: 상세 설명 (선택사항) user: 사용자 객체 (선택사항, 없으면 request.user 사용) metadata: 추가 정보 딕셔너리 (선택사항) """ try: # 사용자 정보 가져오기 if user is None: user = request.user if request.user.is_authenticated else None # Person 객체 가져오기 person = None if user: try: person = Person.objects.get(user=user) except Person.DoesNotExist: pass # 메타데이터 기본값 설정 if metadata is None: metadata = {} # 요청 정보 추가 user_agent = get_user_agent(request) metadata.update({ 'path': request.path, 'method': request.method, 'referer': request.META.get('HTTP_REFERER', ''), 'device_info': get_device_info(user_agent), 'browser_info': get_browser_info(user_agent), }) # 로그 생성 AccessLog.objects.create( user=user, person=person, action=action, description=description, ip_address=get_client_ip(request), user_agent=get_user_agent(request), session_key=request.session.session_key, metadata=metadata ) print(f"[ACCESS_LOG] {action}: {user.username if user else 'Anonymous'} - {description}") except Exception as e: print(f"[ACCESS_LOG_ERROR] 로그 기록 실패: {e}") def log_login(request, user): """로그인 로그 기록""" log_user_activity( request=request, action='LOGIN', description=f'사용자 로그인: {user.username}', user=user ) def log_logout(request, user): """로그아웃 로그 기록""" log_user_activity( request=request, action='LOGOUT', description=f'사용자 로그아웃: {user.username}', user=user ) def log_signup(request, user): """회원가입 로그 기록""" log_user_activity( request=request, action='SIGNUP', description=f'새 회원가입: {user.username}', user=user ) def log_profile_update(request, user, updated_fields=None, field_changes=None): """프로필 수정 로그 기록""" description = f'프로필 수정: {user.username}' if updated_fields: description += f' (수정된 필드: {", ".join(updated_fields)})' metadata = {} if updated_fields: metadata['updated_fields'] = updated_fields # 필드별 수정 전/후 값 기록 if field_changes: metadata['field_changes'] = field_changes # 상세 설명에 변경사항 추가 change_details = [] for field_name, changes in field_changes.items(): old_value = changes.get('old', '') new_value = changes.get('new', '') # 값이 너무 길면 자르기 if len(str(old_value)) > 50: old_value = str(old_value)[:50] + '...' if len(str(new_value)) > 50: new_value = str(new_value)[:50] + '...' change_details.append(f"{field_name}: '{old_value}' → '{new_value}'") if change_details: description += f' | 변경사항: {" | ".join(change_details)}' log_user_activity( request=request, action='PROFILE_UPDATE', description=description, user=user, metadata=metadata ) def log_password_change(request, user): """비밀번호 변경 로그 기록""" log_user_activity( request=request, action='PASSWORD_CHANGE', description=f'비밀번호 변경: {user.username}', user=user ) def log_phone_verification(request, phone_number, user=None): """전화번호 인증 로그 기록""" log_user_activity( request=request, action='PHONE_VERIFICATION', description=f'전화번호 인증: {phone_number}', user=user, metadata={'phone_number': phone_number} ) def log_search(request, query, result_count=None): """검색 로그 기록""" description = f'검색 쿼리: {query}' if result_count is not None: description += f' (결과: {result_count}개)' metadata = {'query': query} if result_count is not None: metadata['result_count'] = result_count log_user_activity( request=request, action='SEARCH', description=description, metadata=metadata ) def log_main_access(request): """메인페이지 접속 로그 기록""" log_user_activity( request=request, action='MAIN_ACCESS', description='메인페이지 접속' ) def log_error(request, error_message, error_type=None): """에러 로그 기록""" metadata = {'error_message': error_message} if error_type: metadata['error_type'] = error_type log_user_activity( request=request, action='ERROR', description=f'에러 발생: {error_message}', metadata=metadata ) def log_withdrawal_request(request, user, withdrawal_request_id): """회원탈퇴 요청 로그 기록""" log_user_activity( request=request, action='OTHER', description=f'회원탈퇴 요청 제출: {user.username}', user=user, metadata={ 'withdrawal_request_id': withdrawal_request_id, 'action_type': 'withdrawal_request' } ) def log_withdrawal_approval(request, approved_by, withdrawn_user, withdrawn_person, withdrawal_request_id): """회원탈퇴 승인 로그 기록""" log_user_activity( request=request, action='OTHER', description=f'회원탈퇴 승인 처리: {withdrawn_user} ({withdrawn_person})', user=approved_by, metadata={ 'withdrawal_request_id': withdrawal_request_id, 'withdrawn_user': withdrawn_user, 'withdrawn_person': withdrawn_person, 'action_type': 'withdrawal_approval' } )