メインコンテンツまでスキップ

Local debugging guide — Python

Last updated on April 14, 2026

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:

Based on the Extend App template

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 / PackageWhat it does
src/app/__main__.pyEntry point — initialises the AccelByte SDK, logs in with client credentials, registers gRPC services, and starts the server.
src/app/services/login_handler.pyYour business logic for userLoggedIn events — receives the event and calls grant_entitlement.
src/app/services/third_party_login_handler.pyYour business logic for userThirdPartyLoggedIn events — same pattern as login_handler.py.
src/app/services/entitlement.pyShared helper that calls the AGS Fulfillment API to grant an item to a user.
src/app/utils.pyReads environment variables from .env via environs.
src/accelbyte_grpc_plugin/__init__.pyApp 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.pyAuto-generated gRPC stubs from proto/account.proto. Do not edit directly.
proto/account.protoProto definition for AGS IAM account events.

Port numbers (defaults in src/app/__main__.py):

PortPurpose
6565gRPC server — receives events from Kafka Connect and simulated grpcurl calls
8080Prometheus 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

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 default justMyCode: true skips stepping into third-party library code. If you need to step into the AccelByte SDK or gRPC internals, change it to false in .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 investigateFile and location
Any login event arrivingsrc/app/services/login_handler.py — top of OnMessage
Third-party login event arrivingsrc/app/services/third_party_login_handler.py — top of OnMessage
Entitlement grant logicsrc/app/services/entitlement.py — inside grant_entitlement
Service not starting at allsrc/app/__main__.py — right after auth_service.login_client(sdk=sdk)
Startup credential checksrc/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:

  1. Double-check the credentials in your .env.

  2. Confirm AB_BASE_URL is reachable:

    curl "$AB_BASE_URL/iam/v3/public/config"
  3. Make sure your OAuth client has the CLIENT_CREDENTIALS grant 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:

  1. Check the service logs for startup errors (ERROR lines).
  2. Confirm port 6565 is open: ss -tlnp | grep 6565.
  3. 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:

  1. Set a breakpoint in src/app/services/entitlement.py inside grant_entitlement to inspect the error value returned by platform_service.fulfill_item.
  2. Enable debug logging: set PLUGIN_GRPC_SERVER_LOGGING_ENABLED=true in .env and restart.

Debugger never pauses at a breakpoint

Possible causes and fixes:

CauseFix
Service started with Run: App task instead of the debuggerUse the VS Code "Debug: App" launch config (F5)
Another instance is already running and handling the eventCheck for port conflicts: ss -tlnp | grep 6565 and stop the stale process
justMyCode: true skips your fileCheck the file is under src/ and not treated as a library; try setting justMyCode: false
grpcurl is targeting the wrong portConfirm 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.


References