Local debugging guide — Python
This guide covers everything specific to debugging an Extend Event Handler app written in Python. For general debugging concepts — environment setup, VS Code debug workflow, log reading, and common issues — see:
- Extend local debugging guide — common concepts for all Extend app types
- Python language setup guide — Python prerequisites, debugpy, jq, and Python-specific troubleshooting
File paths, task names, and module names in this guide refer to the AccelByte Extend Event Handler Python template. If your project uses different names, the concepts still apply — adjust paths to match your layout.
Prerequisites
See the Python language setup guide for installation requirements. In addition, grpcurl is required for simulating incoming events during local development — see Triggering events for testing in the main guide for installation and usage.
Project structure
| File / Package | What it does |
|---|---|
src/app/__main__.py | Entry point — initialises the AccelByte SDK, logs in with client credentials, registers gRPC services, and starts the server. |
src/app/services/login_handler.py | Your business logic for userLoggedIn events — receives the event and calls grant_entitlement. |
src/app/services/third_party_login_handler.py | Your business logic for userThirdPartyLoggedIn events — same pattern as login_handler.py. |
src/app/services/entitlement.py | Shared helper that calls the AGS Fulfillment API to grant an item to a user. |
src/app/utils.py | Reads environment variables from .env via environs. |
src/accelbyte_grpc_plugin/__init__.py | App class — wires together gRPC server, interceptors, Prometheus, Zipkin, and health checking. |
src/accelbyte_grpc_plugin/interceptors/ | DebugLoggingServerInterceptor and MetricsServerInterceptor. |
src/account_pb2.py / src/account_pb2_grpc.py | Auto-generated gRPC stubs from proto/account.proto. Do not edit directly. |
proto/account.proto | Proto definition for AGS IAM account events. |
Port numbers (defaults in src/app/__main__.py):
| Port | Purpose |
|---|---|
6565 | gRPC server — receives events from Kafka Connect and simulated grpcurl calls |
8080 | Prometheus metrics endpoint (/metrics) |
Running the service locally
From the terminal
# Export all variables from your .env file
export $(grep -v '^#' .env | xargs)
PYTHONPATH=src venv/bin/python -m app
From VS Code
Use Terminal → Run Task → "Run: App".
This task is defined in .vscode/tasks.json and sets PYTHONPATH=src for you.
Confirming the service is up
You should see log output like:
INFO:app:login client...
INFO:app:login client successful
INFO:app:starting app server..
INFO:app:gRPC health checking enabled
INFO:app:gRPC reflection enabled
INFO:app:prometheus enabled
INFO:app:zipkin enabled
INFO:app:app server started
Confirm gRPC reflection (and therefore the server) is working:
grpcurl -plaintext localhost:6565 list
Expected output — a list of registered services:
accelbyte.iam.account.v1.UserAuthenticationUserLoggedInService
accelbyte.iam.account.v1.UserAuthenticationUserThirdPartyLoggedInService
grpc.health.v1.Health
grpc.reflection.v1alpha.ServerReflection
Attaching the debugger
VS Code (recommended)
The repository ships with a ready-to-use launch configuration in .vscode/launch.json:
{
"name": "Debug: App",
"type": "debugpy",
"request": "launch",
"module": "app",
"cwd": "${workspaceFolder}",
"envFile": "${workspaceFolder}/.env",
"env": {
"PYTHONPATH": "src"
},
"console": "integratedTerminal",
"justMyCode": true
}
Steps:
Follow the attaching the debugger steps in the generic guide, then select "Debug: App" from the dropdown.
VS Code uses debugpy under the hood. You do not need to configure it separately.
Tip —
justMyCode: By defaultjustMyCode: trueskips stepping into third-party library code. If you need to step into the AccelByte SDK or gRPC internals, change it tofalsein.vscode/launch.json.
Other IDEs — debugpy headless mode
Start debugpy in headless mode so your IDE can connect to it:
export $(grep -v '^#' .env | xargs)
PYTHONPATH=src venv/bin/python -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m app
The process blocks until a debugger connects. Then configure your IDE to attach to
localhost:5678 via DAP (Debug Adapter Protocol).
Where to put breakpoints
Most of the interesting logic happens in the service handler files. Start there.
| What you want to investigate | File and location |
|---|---|
| Any login event arriving | src/app/services/login_handler.py — top of OnMessage |
| Third-party login event arriving | src/app/services/third_party_login_handler.py — top of OnMessage |
| Entitlement grant logic | src/app/services/entitlement.py — inside grant_entitlement |
| Service not starting at all | src/app/__main__.py — right after auth_service.login_client(sdk=sdk) |
| Startup credential check | src/app/__main__.py — the if error: block after login_client |
Inspecting the incoming event payload
When the debugger pauses inside OnMessage, the request parameter contains the full event.
Expand it in the Variables panel to see all fields the event carries — user_id,
namespace, platform_id (for third-party events), and others defined in
proto/account.proto.
Conditional breakpoint syntax
Right-click a breakpoint → Edit Breakpoint → enter a Python expression, for example:
request.user_id == "test-user-001"
For more conditional breakpoint syntax examples, see the Python language guide.
Triggering events for testing
For event simulation options and grpcurl commands, see
Triggering events for testing in the main guide.
Describe a service method
When you need to know the exact field names for a proto method, use grpcurl describe:
grpcurl -plaintext localhost:6565 \
describe accelbyte.iam.account.v1.UserAuthenticationUserLoggedInService.OnMessage
This prints the full proto definition of the method — useful when you need to know exactly
what fields to include in the -d payload.
Reading logs
The service uses Python's standard logging module writing to stdout. A single log line
looks like:
INFO:app:OnMessage request: {"user_id": "test-user-001", "namespace": "mygame"}
Use grep to filter log output during local runs:
# Show only ERROR lines
PYTHONPATH=src venv/bin/python -m app 2>&1 | grep "ERROR"
# Show lines related to a specific user
PYTHONPATH=src venv/bin/python -m app 2>&1 | grep "test-user-001"
# Show all OnMessage calls
PYTHONPATH=src venv/bin/python -m app 2>&1 | grep "OnMessage"
Enabling debug-level logging
The default log level is INFO. To see debug-level output (including gRPC interceptor
logs), set PLUGIN_GRPC_SERVER_LOGGING_ENABLED=true in your .env:
PLUGIN_GRPC_SERVER_LOGGING_ENABLED=true
This activates DebugLoggingServerInterceptor, which logs the gRPC method name for every
incoming call.
gRPC call pattern
Each successful event produces a request log and a response log from the handler:
INFO:app:OnMessage request: {"user_id": "...", "namespace": "..."}
INFO:app:OnMessage response: {}
If only the request line appears and no response line follows, the handler encountered an
error before returning. Check for ERROR lines above or below.
Python-specific troubleshooting
Proto changes have no effect
Symptom: You edited a file in proto/ but nothing changed at runtime.
Cause: The generated stubs in src/account_pb2.py, src/account_pb2_grpc.py, and
src/account_pb2.pyi have not been regenerated.
Fix: Run the "Proto: Generate" VS Code task, or:
./proto.sh
Then restart the service.
ITEM_ID_TO_GRANT not set — service exits immediately
Symptom:
ERROR:app:ITEM_ID_TO_GRANT environment variable is required
Cause: ITEM_ID_TO_GRANT is missing from the .env file.
Fix: Add ITEM_ID_TO_GRANT=<your-item-id> to .env and restart. The item ID must
exist in a published store in the namespace specified by AB_NAMESPACE.
Service exits — client credential login fails
Symptom:
ERROR:app:login client failed
or an exception traceback from auth_service.login_client.
Cause: AB_CLIENT_ID or AB_CLIENT_SECRET is wrong, or AB_BASE_URL points to the
wrong environment.
Fix:
-
Double-check the credentials in your
.env. -
Confirm
AB_BASE_URLis reachable:curl "$AB_BASE_URL/iam/v3/public/config" -
Make sure your OAuth client has the
CLIENT_CREDENTIALSgrant type enabled in the Admin Portal.
Checking for port conflicts
If you see "address already in use":
ss -tlnp | grep -E '6565|8080'
Kill the stale process and start again.
grpcurl returns "Failed to dial"
Symptom:
Failed to dial target host "localhost:6565": ...
Cause: The gRPC server either has not started yet, crashed on startup, or is listening on a different port.
Fix:
- Check the service logs for startup errors (
ERRORlines). - Confirm port 6565 is open:
ss -tlnp | grep 6565. - Rerun
grpcurl -plaintext localhost:6565 list— if it succeeds, the server is up.
Handler returns Internal but no ERROR log appears
Symptom: grpcurl reports Code: Internal but you do not see a corresponding ERROR
line in the logs.
Cause: The exception is caught inside the AccelByte SDK or gRPC layer before reaching the structured logger.
Fix:
- Set a breakpoint in
src/app/services/entitlement.pyinsidegrant_entitlementto inspect theerrorvalue returned byplatform_service.fulfill_item. - Enable debug logging: set
PLUGIN_GRPC_SERVER_LOGGING_ENABLED=truein.envand restart.
Debugger never pauses at a breakpoint
Possible causes and fixes:
| Cause | Fix |
|---|---|
Service started with Run: App task instead of the debugger | Use the VS Code "Debug: App" launch config (F5) |
| Another instance is already running and handling the event | Check for port conflicts: ss -tlnp | grep 6565 and stop the stale process |
justMyCode: true skips your file | Check the file is under src/ and not treated as a library; try setting justMyCode: false |
grpcurl is targeting the wrong port | Confirm port 6565 with grpcurl -plaintext localhost:6565 list |
AI assistance
The app template ships with a Claude agent skill at
.claude/skills/debugging-guide/SKILL.md.
Copy the full skill file into your own repository and activate it in your AI assistant.
For the full skill content and prompting tips, see the AI assistance section in the main guide.