Local debugging guide — C#
This guide covers everything specific to debugging an Extend Event Handler app written in C#. 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
- C# language setup guide — .NET prerequisites, attach mode, jq, and C#-specific troubleshooting
File paths, task names, and class names in this guide refer to the AccelByte Extend Event Handler C# template. If your project uses different names, the concepts still apply — adjust paths to match your layout.
Prerequisites
See the C# 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 / Class | What it does |
|---|---|
Program.cs | Entry point — wires together the gRPC server, Prometheus metrics, OpenTelemetry tracing, IAM login, and health checks. |
Services/UserLoggedInService.cs | Your business logic for userLoggedIn events — receives the event and calls Entitlement.GrantEntitlement. |
Services/UserThirdPartyLoggedInService.cs | Your business logic for userThirdPartyLoggedIn events — same pattern as UserLoggedInService.cs. |
Services/Entitlement.cs | Shared helper that calls the AGS Fulfillment API to grant an item to a user. |
Classes/DefaultAccelByteServiceProvider.cs | Reads configuration, builds the AccelByteSDK instance, and performs the initial client-credentials login. |
Classes/AppSettingConfigRepository.cs | Reads environment variables (AB_BASE_URL, AB_CLIENT_ID, AB_CLIENT_SECRET, AB_NAMESPACE, ITEM_ID_TO_GRANT) into typed properties. |
Classes/DebugLoggerServerInterceptor.cs | gRPC interceptor — logs the method name and headers for every incoming call. |
Classes/ExceptionHandlingInterceptor.cs | gRPC interceptor — catches unhandled exceptions and returns appropriate gRPC status codes. |
Protos/iam/account/v1/account.proto | Proto definition for AGS IAM account events. |
appsettings.json | Static configuration — Kestrel endpoint bindings (ports 6565 and 8080), log levels. |
Port numbers (configured in appsettings.json):
| Port | Purpose |
|---|---|
6565 | gRPC server — receives events from Kafka Connect and simulated grpcurl calls |
8080 | HTTP server — Prometheus metrics endpoint (/metrics) |
Running the service locally
From the terminal
# Export all variables from your .env file
export $(grep -v '^#' .env | xargs)
dotnet run --project src/AccelByte.PluginArch.EventHandler.Demo.Server/AccelByte.PluginArch.EventHandler.Demo.Server.csproj
From VS Code
Use Terminal → Run Task → "Run: App".
This task is defined in .vscode/tasks.json and reads the .env file for you automatically.
Confirming the service is up
You should see log output like:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://0.0.0.0:8080
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://0.0.0.0:6565
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
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": "coreclr",
"request": "launch",
"preLaunchTask": "Build: App",
"program": "${workspaceFolder}/src/AccelByte.PluginArch.EventHandler.Demo.Server/bin/Debug/net8.0/AccelByte.PluginArch.EventHandler.Demo.Server.dll",
"args": [],
"cwd": "${workspaceFolder}/src/AccelByte.PluginArch.EventHandler.Demo.Server",
"envFile": "${workspaceFolder}/.env",
"console": "integratedTerminal",
"justMyCode": true
}
Steps:
Follow the attaching the debugger steps in the generic guide, then select "Debug: App" from the dropdown.
VS Code builds the project (via the Build: App pre-launch task) and attaches the
.NET debugger (coreclr). You do not need to configure the debugger 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 — dotnet with remote attach
Start the application and attach a compatible .NET debugger (for example, JetBrains Rider or Visual Studio):
export $(grep -v '^#' .env | xargs)
dotnet run --project src/AccelByte.PluginArch.EventHandler.Demo.Server/AccelByte.PluginArch.EventHandler.Demo.Server.csproj
Then use your IDE's Attach to Process feature and select the AccelByte.PluginArch.EventHandler.Demo.Server process.
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 | Services/UserLoggedInService.cs — top of OnMessage |
| Third-party login event arriving | Services/UserThirdPartyLoggedInService.cs — top of OnMessage |
| Entitlement grant logic | Services/Entitlement.cs — inside GrantEntitlement |
| Service not starting at all | Program.cs — just before Environment.Exit(1) calls |
| Startup credential check | Classes/DefaultAccelByteServiceProvider.cs — constructor, after Sdk.LoginClient(true) |
| gRPC call tracing | Classes/DebugLoggerServerInterceptor.cs — UnaryServerHandler |
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 — UserId,
Namespace, PlatformId (for third-party events), and others defined in
Protos/iam/account/v1/account.proto.
Conditional breakpoint syntax
Right-click a breakpoint → Edit Breakpoint → enter a C# expression, for example:
request.UserId == "test-user-001"
For more conditional breakpoint syntax examples, see the C# 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 ASP.NET Core's Microsoft.Extensions.Logging, which writes to stdout. A
single log line looks like:
info: AccelByte.PluginArch.EventHandler.Demo.Server.Services.UserLoggedInService[0]
Received UserLoggedIn event: { "userId": "test-user-001", "namespace": "mygame" }
Log levels
The default log level is configured in appsettings.json:
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
To see more verbose output during local development, override the level by setting the
DOTNET_ENVIRONMENT=Development environment variable (already set in the "Run: App"
VS Code task) or by adding overrides to appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}
gRPC call pattern
The DebugLoggerServerInterceptor logs every incoming gRPC call:
info: AccelByte.PluginArch.EventHandler.Demo.Server.DebugLoggerServerInterceptor[0]
REQUEST /accelbyte.iam.account.v1.UserAuthenticationUserLoggedInService/OnMessage
info: AccelByte.PluginArch.EventHandler.Demo.Server.DebugLoggerServerInterceptor[0]
RESPONSE /accelbyte.iam.account.v1.UserAuthenticationUserLoggedInService/OnMessage
If only the REQUEST line appears and no RESPONSE line follows, the handler encountered
an error before returning. Look for fail: or error: lines above or below.
Filtering logs
Use grep to focus on the output you care about in the terminal:
# Show only error-level lines
dotnet run ... 2>&1 | grep -E '^fail:|^crit:'
# Show only OnMessage calls
dotnet run ... 2>&1 | grep "OnMessage"
# Show lines related to a specific user
dotnet run ... 2>&1 | grep "test-user-001"
C#-specific troubleshooting
ITEM_ID_TO_GRANT not set — service exits immediately
Symptom:
fail: AccelByte.PluginArch.EventHandler.Demo.Server.Program[0]
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:
fail: AccelByte.PluginArch.EventHandler.Demo.Server.DefaultAccelByteServiceProvider[0]
Error unable to login using clientId and clientSecret
or an exception thrown from the DefaultAccelByteServiceProvider constructor.
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.
Build fails before the debugger starts
Symptom: Pressing F5 shows a build error in the terminal and the debugger never attaches.
Cause: The Build: App pre-launch task failed.
Fix:
- Open the Terminal → Run Task → "Build: App" task directly to see the full compiler output.
- Fix any compilation errors, then press F5 again.
You can also build from the terminal:
dotnet build src/plugin-arch-event-handler-grpc-server.sln
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 (
fail:orcrit:lines). - 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 before reaching the application
logger, or justMyCode: true is hiding the stack frame.
Fix:
- Set a breakpoint in
Services/Entitlement.csinsideGrantEntitlementto inspect any exception thrown byFulfillItemOp.Execute. - Set
justMyCode: falsein.vscode/launch.jsonto allow stepping into SDK internals. - Increase log verbosity in
appsettings.json(set"Default": "Debug").
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) |
| Project was built in Release mode | The launch config targets bin/Debug/net8.0/; make sure you're not building with -c Release |
| 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 the file | Check the file is part of the main project, not a referenced library; try setting justMyCode: false |
grpcurl is targeting the wrong port | Confirm port 6565 with grpcurl -plaintext localhost:6565 list |
Proto changes have no effect
Symptom: You edited a file in Protos/ but the behavior of the service did not change.
Cause: The generated C# stubs have not been regenerated.
Fix: Run the proto generation step for the project (check Makefile or any proto.sh
script in the repository root), then rebuild and restart the service:
dotnet build src/plugin-arch-event-handler-grpc-server.sln
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.