refactor(logging): standardize status code handling and enhance logging in webhook and cron handlers

This commit is contained in:
bsiggel
2026-03-08 22:09:22 +00:00
parent a0cf845877
commit 1c765d1eec
15 changed files with 61 additions and 64 deletions

View File

@@ -38,7 +38,7 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
kuerzel = body.get('kuerzel') kuerzel = body.get('kuerzel')
if not kuerzel: if not kuerzel:
return ApiResponse( return ApiResponse(
status_code=400, status=400,
body={ body={
'error': 'kuerzel required', 'error': 'kuerzel required',
'message': 'Please provide kuerzel in body' 'message': 'Please provide kuerzel in body'
@@ -57,7 +57,7 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
} }
}) })
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'triggered', 'status': 'triggered',
'message': 'Calendar sync triggered for all employees', 'message': 'Calendar sync triggered for all employees',
@@ -71,7 +71,7 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
if not set_employee_lock(redis_client, kuerzel_upper, 'api', ctx): if not set_employee_lock(redis_client, kuerzel_upper, 'api', ctx):
ctx.logger.info(f"Calendar Sync API: Sync already active for {kuerzel_upper}, skipping") ctx.logger.info(f"Calendar Sync API: Sync already active for {kuerzel_upper}, skipping")
return ApiResponse( return ApiResponse(
status_code=409, status=409,
body={ body={
'status': 'conflict', 'status': 'conflict',
'message': f'Calendar sync already active for {kuerzel_upper}', 'message': f'Calendar sync already active for {kuerzel_upper}',
@@ -92,7 +92,7 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
}) })
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'triggered', 'status': 'triggered',
'message': f'Calendar sync triggered for {kuerzel_upper}', 'message': f'Calendar sync triggered for {kuerzel_upper}',
@@ -104,7 +104,7 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse:
except Exception as e: except Exception as e:
ctx.logger.error(f"Error in API trigger: {e}") ctx.logger.error(f"Error in API trigger: {e}")
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={ body={
'error': 'Internal server error', 'error': 'Internal server error',
'details': str(e) 'details': str(e)

View File

@@ -24,10 +24,13 @@ config = {
} }
async def handler(input_data: Dict[str, Any], ctx: FlowContext) -> None: async def handler(input_data: None, ctx: FlowContext) -> None:
"""Cron handler that triggers the calendar sync cascade.""" """Cron handler that triggers the calendar sync cascade."""
try: try:
log_operation('info', "Calendar Sync Cron: Starting to emit sync-all event", context=ctx) ctx.logger.info("=" * 80)
ctx.logger.info("🕐 CALENDAR SYNC CRON: STARTING")
ctx.logger.info("=" * 80)
ctx.logger.info("Emitting sync-all event")
# Enqueue sync-all event # Enqueue sync-all event
await ctx.enqueue({ await ctx.enqueue({
@@ -37,7 +40,11 @@ async def handler(input_data: Dict[str, Any], ctx: FlowContext) -> None:
} }
}) })
log_operation('info', "Calendar Sync Cron: Emitted sync-all event", context=ctx) ctx.logger.info("Calendar sync-all event emitted successfully")
ctx.logger.info("=" * 80)
except Exception as e: except Exception as e:
log_operation('error', f"Fehler beim Cron-Job: {e}", context=ctx) ctx.logger.error("=" * 80)
ctx.logger.error("❌ ERROR: CALENDAR SYNC CRON")
ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80)

View File

@@ -11,24 +11,23 @@ Verarbeitet:
""" """
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from motia import FlowContext from motia import FlowContext, queue
from services.advoware import AdvowareAPI from services.advoware import AdvowareAPI
from services.espocrm import EspoCRMAPI from services.espocrm import EspoCRMAPI
from services.bankverbindungen_mapper import BankverbindungenMapper from services.bankverbindungen_mapper import BankverbindungenMapper
from services.notification_utils import NotificationManager from services.notification_utils import NotificationManager
from services.redis_client import get_redis_client
import json import json
import redis
import os
config = { config = {
"name": "VMH Bankverbindungen Sync Handler", "name": "VMH Bankverbindungen Sync Handler",
"description": "Zentraler Sync-Handler für Bankverbindungen (Webhooks + Cron Events)", "description": "Zentraler Sync-Handler für Bankverbindungen (Webhooks + Cron Events)",
"flows": ["vmh-bankverbindungen"], "flows": ["vmh-bankverbindungen"],
"triggers": [ "triggers": [
{"type": "queue", "topic": "vmh.bankverbindungen.create"}, queue("vmh.bankverbindungen.create"),
{"type": "queue", "topic": "vmh.bankverbindungen.update"}, queue("vmh.bankverbindungen.update"),
{"type": "queue", "topic": "vmh.bankverbindungen.delete"}, queue("vmh.bankverbindungen.delete"),
{"type": "queue", "topic": "vmh.bankverbindungen.sync_check"} queue("vmh.bankverbindungen.sync_check")
], ],
"enqueues": [] "enqueues": []
} }
@@ -47,20 +46,11 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
ctx.logger.info(f"🔄 Bankverbindungen Sync gestartet: {action.upper()} | Entity: {entity_id} | Source: {source}") ctx.logger.info(f"🔄 Bankverbindungen Sync gestartet: {action.upper()} | Entity: {entity_id} | Source: {source}")
# Shared Redis client # Shared Redis client (centralized factory)
redis_host = os.getenv('REDIS_HOST', 'localhost') redis_client = get_redis_client(strict=False)
redis_port = int(os.getenv('REDIS_PORT', '6379'))
redis_db = int(os.getenv('REDIS_DB_ADVOWARE_CACHE', '1'))
redis_client = redis.Redis( # APIs initialisieren (mit Context für besseres Logging)
host=redis_host, espocrm = EspoCRMAPI(ctx)
port=redis_port,
db=redis_db,
decode_responses=True
)
# APIs initialisieren
espocrm = EspoCRMAPI()
advoware = AdvowareAPI(ctx) advoware = AdvowareAPI(ctx)
mapper = BankverbindungenMapper() mapper = BankverbindungenMapper()
notification_mgr = NotificationManager(espocrm_api=espocrm, context=ctx) notification_mgr = NotificationManager(espocrm_api=espocrm, context=ctx)
@@ -130,7 +120,7 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
pass pass
async def handle_create(entity_id, betnr, espo_entity, espocrm, advoware, mapper, ctx, redis_client, lock_key): async def handle_create(entity_id, betnr, espo_entity, espocrm, advoware, mapper, ctx, redis_client, lock_key) -> None:
"""Erstellt neue Bankverbindung in Advoware""" """Erstellt neue Bankverbindung in Advoware"""
try: try:
ctx.logger.info(f"🔨 CREATE Bankverbindung in Advoware für Beteiligter {betnr}...") ctx.logger.info(f"🔨 CREATE Bankverbindung in Advoware für Beteiligter {betnr}...")
@@ -176,7 +166,7 @@ async def handle_create(entity_id, betnr, espo_entity, espocrm, advoware, mapper
redis_client.delete(lock_key) redis_client.delete(lock_key)
async def handle_update(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key): async def handle_update(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key) -> None:
"""Update nicht möglich - Sendet Notification an User""" """Update nicht möglich - Sendet Notification an User"""
try: try:
ctx.logger.warn(f"⚠️ UPDATE: Advoware API unterstützt kein PUT für Bankverbindungen") ctx.logger.warn(f"⚠️ UPDATE: Advoware API unterstützt kein PUT für Bankverbindungen")
@@ -219,7 +209,7 @@ async def handle_update(entity_id, betnr, advoware_id, espo_entity, espocrm, not
redis_client.delete(lock_key) redis_client.delete(lock_key)
async def handle_delete(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key): async def handle_delete(entity_id, betnr, advoware_id, espo_entity, espocrm, notification_mgr, ctx, redis_client, lock_key) -> None:
"""Delete nicht möglich - Sendet Notification an User""" """Delete nicht möglich - Sendet Notification an User"""
try: try:
ctx.logger.warn(f"⚠️ DELETE: Advoware API unterstützt kein DELETE für Bankverbindungen") ctx.logger.warn(f"⚠️ DELETE: Advoware API unterstützt kein DELETE für Bankverbindungen")

View File

@@ -32,7 +32,7 @@ async def handler(input_data: Dict[str, Any], ctx: FlowContext) -> None:
ctx.logger.info("🕐 Beteiligte Sync Cron gestartet") ctx.logger.info("🕐 Beteiligte Sync Cron gestartet")
try: try:
espocrm = EspoCRMAPI() espocrm = EspoCRMAPI(ctx)
# Berechne Threshold für "veraltete" Syncs (24 Stunden) # Berechne Threshold für "veraltete" Syncs (24 Stunden)
threshold = datetime.datetime.now() - datetime.timedelta(hours=24) threshold = datetime.datetime.now() - datetime.timedelta(hours=24)

View File

@@ -11,7 +11,7 @@ Verarbeitet:
""" """
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from motia import FlowContext from motia import FlowContext, queue
from services.advoware import AdvowareAPI from services.advoware import AdvowareAPI
from services.advoware_service import AdvowareService from services.advoware_service import AdvowareService
from services.espocrm import EspoCRMAPI from services.espocrm import EspoCRMAPI
@@ -33,10 +33,10 @@ config = {
"description": "Zentraler Sync-Handler für Beteiligte (Webhooks + Cron Events)", "description": "Zentraler Sync-Handler für Beteiligte (Webhooks + Cron Events)",
"flows": ["vmh-beteiligte"], "flows": ["vmh-beteiligte"],
"triggers": [ "triggers": [
{"type": "queue", "topic": "vmh.beteiligte.create"}, queue("vmh.beteiligte.create"),
{"type": "queue", "topic": "vmh.beteiligte.update"}, queue("vmh.beteiligte.update"),
{"type": "queue", "topic": "vmh.beteiligte.delete"}, queue("vmh.beteiligte.delete"),
{"type": "queue", "topic": "vmh.beteiligte.sync_check"} queue("vmh.beteiligte.sync_check")
], ],
"enqueues": [] "enqueues": []
} }
@@ -174,7 +174,7 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
ctx.logger.error(traceback.format_exc()) ctx.logger.error(traceback.format_exc())
async def handle_create(entity_id, espo_entity, espocrm, advoware, sync_utils, mapper, ctx): async def handle_create(entity_id, espo_entity, espocrm, advoware, sync_utils, mapper, ctx) -> None:
"""Erstellt neuen Beteiligten in Advoware""" """Erstellt neuen Beteiligten in Advoware"""
try: try:
ctx.logger.info(f"🔨 CREATE in Advoware...") ctx.logger.info(f"🔨 CREATE in Advoware...")
@@ -233,7 +233,7 @@ async def handle_create(entity_id, espo_entity, espocrm, advoware, sync_utils, m
await sync_utils.release_sync_lock(entity_id, 'failed', str(e), increment_retry=True) await sync_utils.release_sync_lock(entity_id, 'failed', str(e), increment_retry=True)
async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_utils, mapper, ctx): async def handle_update(entity_id, betnr, espo_entity, espocrm, advoware, sync_utils, mapper, ctx) -> None:
"""Synchronisiert existierenden Beteiligten""" """Synchronisiert existierenden Beteiligten"""
try: try:
ctx.logger.info(f"🔍 Fetch von Advoware betNr={betnr}...") ctx.logger.info(f"🔍 Fetch von Advoware betNr={betnr}...")

View File

@@ -10,7 +10,7 @@ Verarbeitet:
""" """
from typing import Dict, Any from typing import Dict, Any
from motia import FlowContext from motia import FlowContext, queue
from services.espocrm import EspoCRMAPI from services.espocrm import EspoCRMAPI
from services.document_sync_utils import DocumentSync from services.document_sync_utils import DocumentSync
from services.xai_service import XAIService from services.xai_service import XAIService
@@ -23,9 +23,9 @@ config = {
"description": "Zentraler Sync-Handler für Documents mit xAI Collections", "description": "Zentraler Sync-Handler für Documents mit xAI Collections",
"flows": ["vmh-documents"], "flows": ["vmh-documents"],
"triggers": [ "triggers": [
{"type": "queue", "topic": "vmh.document.create"}, queue("vmh.document.create"),
{"type": "queue", "topic": "vmh.document.update"}, queue("vmh.document.update"),
{"type": "queue", "topic": "vmh.document.delete"} queue("vmh.document.delete")
], ],
"enqueues": [] "enqueues": []
} }
@@ -127,7 +127,7 @@ async def handler(event_data: Dict[str, Any], ctx: FlowContext[Any]) -> None:
ctx.logger.error(traceback.format_exc()) ctx.logger.error(traceback.format_exc())
async def handle_create_or_update(entity_id: str, document: Dict[str, Any], sync_utils: DocumentSync, xai_service: XAIService, ctx: FlowContext[Any], entity_type: str = 'CDokumente'): async def handle_create_or_update(entity_id: str, document: Dict[str, Any], sync_utils: DocumentSync, xai_service: XAIService, ctx: FlowContext[Any], entity_type: str = 'CDokumente') -> None:
""" """
Behandelt Create/Update von Documents Behandelt Create/Update von Documents
@@ -316,7 +316,7 @@ async def handle_create_or_update(entity_id: str, document: Dict[str, Any], sync
await sync_utils.release_sync_lock(entity_id, success=False, error_message=str(e)) await sync_utils.release_sync_lock(entity_id, success=False, error_message=str(e))
async def handle_delete(entity_id: str, document: Dict[str, Any], sync_utils: DocumentSync, xai_service: XAIService, ctx: FlowContext[Any], entity_type: str = 'CDokumente'): async def handle_delete(entity_id: str, document: Dict[str, Any], sync_utils: DocumentSync, xai_service: XAIService, ctx: FlowContext[Any], entity_type: str = 'CDokumente') -> None:
""" """
Behandelt Delete von Documents Behandelt Delete von Documents

View File

@@ -57,7 +57,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'received', 'status': 'received',
'action': 'create', 'action': 'create',
@@ -71,6 +71,6 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error(f"Error: {e}") ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}
) )

View File

@@ -57,7 +57,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'received', 'status': 'received',
'action': 'delete', 'action': 'delete',
@@ -71,6 +71,6 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error(f"Error: {e}") ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}
) )

View File

@@ -57,7 +57,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'received', 'status': 'received',
'action': 'update', 'action': 'update',
@@ -71,6 +71,6 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error(f"Error: {e}") ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}
) )

View File

@@ -60,7 +60,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'received', 'status': 'received',
'action': 'create', 'action': 'create',
@@ -74,7 +74,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error(f"Error: {e}") ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={ body={
'error': 'Internal server error', 'error': 'Internal server error',
'details': str(e) 'details': str(e)

View File

@@ -57,7 +57,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'received', 'status': 'received',
'action': 'delete', 'action': 'delete',
@@ -71,6 +71,6 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error(f"Error: {e}") ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={'error': 'Internal server error', 'details': str(e)} body={'error': 'Internal server error', 'details': str(e)}
) )

View File

@@ -60,7 +60,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'status': 'received', 'status': 'received',
'action': 'update', 'action': 'update',
@@ -74,7 +74,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error(f"Error: {e}") ctx.logger.error(f"Error: {e}")
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={ body={
'error': 'Internal server error', 'error': 'Internal server error',
'details': str(e) 'details': str(e)

View File

@@ -63,7 +63,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'success': True, 'success': True,
'message': f'{len(entity_ids)} document(s) enqueued for sync', 'message': f'{len(entity_ids)} document(s) enqueued for sync',
@@ -79,7 +79,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={ body={
'success': False, 'success': False,
'error': str(e) 'error': str(e)

View File

@@ -63,7 +63,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'success': True, 'success': True,
'message': f'{len(entity_ids)} document(s) enqueued for deletion', 'message': f'{len(entity_ids)} document(s) enqueued for deletion',
@@ -79,7 +79,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={ body={
'success': False, 'success': False,
'error': str(e) 'error': str(e)

View File

@@ -63,7 +63,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
f"{len(entity_ids)} events emitted") f"{len(entity_ids)} events emitted")
return ApiResponse( return ApiResponse(
status_code=200, status=200,
body={ body={
'success': True, 'success': True,
'message': f'{len(entity_ids)} document(s) enqueued for sync', 'message': f'{len(entity_ids)} document(s) enqueued for sync',
@@ -79,7 +79,7 @@ async def handler(request: ApiRequest, ctx: FlowContext[Any]) -> ApiResponse:
ctx.logger.error("=" * 80) ctx.logger.error("=" * 80)
return ApiResponse( return ApiResponse(
status_code=500, status=500,
body={ body={
'success': False, 'success': False,
'error': str(e) 'error': str(e)