-
Notifications
You must be signed in to change notification settings - Fork 8
Feature/catch and publish exception #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Jeff-Xu23
wants to merge
5
commits into
dev
Choose a base branch
from
feature/catch-and-publish-exception
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 3 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| # Exception Capturing and Publishing | ||
|
|
||
| This document describes how to use the exception capturing and publishing features of the Aevatar framework. This functionality allows exception information to be written to Orleans Stream for centralized processing, monitoring, and analysis. | ||
|
|
||
| ## Feature Overview | ||
|
|
||
| The exception capturing and publishing functionality sends exception information to a dedicated exception handling channel via Orleans Stream. Key features include: | ||
|
|
||
| - Publishing detailed exception information to a dedicated Orleans Stream | ||
| - Supporting the recording of exception context information | ||
| - Automatically capturing calling method and class name | ||
| - Providing easy-to-use helper methods to simplify the exception handling process | ||
| - Using a separate Kafka Topic, isolated from business Topics | ||
|
|
||
| ## Configuration | ||
|
|
||
| Add the following configuration to the `appsettings.json` file: | ||
|
|
||
| ```json | ||
| { | ||
| "Aevatar": { | ||
| "ExceptionStreamNamespace": "AevatarException" | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Where `ExceptionStreamNamespace` specifies the Stream namespace for exception events, with a default value of "AevatarException". | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Direct Exception Publishing | ||
|
|
||
| ```csharp | ||
| public class MyGAgent : GAgentBase<MyState, MyStateLogEvent> | ||
| { | ||
| public async Task DoSomethingAsync() | ||
| { | ||
| try | ||
| { | ||
| // Business logic | ||
| await ProcessDataAsync(); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Publish exception | ||
| var contextData = new { UserId = "user123", Action = "ProcessData" }; | ||
| await this.PublishExceptionAsync(ex, contextData); | ||
|
|
||
| // Can choose to rethrow or handle the exception | ||
| throw; | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ### Using Helper Methods to Automatically Capture and Publish Exceptions | ||
|
|
||
| ```csharp | ||
| public class MyGAgent : GAgentBase<MyState, MyStateLogEvent> | ||
| { | ||
| public async Task DoSomethingAsync() | ||
| { | ||
| var contextData = new { UserId = "user123", Action = "ProcessData" }; | ||
|
|
||
| // Automatically capture and publish exceptions, rethrown by default | ||
| await this.CatchAndPublishExceptionAsync(async () => | ||
| { | ||
| await ProcessDataAsync(); | ||
| }, contextData); | ||
| } | ||
|
|
||
| public async Task<Result> GetDataAsync() | ||
| { | ||
| var contextData = new { UserId = "user123", Action = "GetData" }; | ||
|
|
||
| // For cases with return values, without rethrowing the exception | ||
| var (result, exceptionId) = await this.CatchAndPublishExceptionAsync( | ||
| async () => await FetchDataAsync(), | ||
| new Result { Success = false }, // Default value | ||
| contextData, | ||
| rethrowException: false); | ||
|
|
||
| if (exceptionId != Guid.Empty) | ||
| { | ||
| // Exception occurred, using default value | ||
| Logger.LogWarning("Exception occurred, using default value. ExceptionId: {ExceptionId}", exceptionId); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Exception Event Format | ||
|
|
||
| The published exception event contains the following information: | ||
|
|
||
| ```csharp | ||
| public class ExceptionEvent : EventBase | ||
| { | ||
| public GrainId GrainId { get; set; } // Grain ID where the exception occurred | ||
| public string ExceptionMessage { get; set; } // Exception message | ||
| public string ExceptionType { get; set; } // Exception type | ||
| public string StackTrace { get; set; } // Stack trace | ||
| public string ContextData { get; set; } // Context data (JSON format) | ||
| public DateTime Timestamp { get; set; } // Exception timestamp (UTC) | ||
| public string? MethodName { get; set; } // Method name where the exception occurred | ||
| public string? ClassName { get; set; } // Class name where the exception occurred | ||
| } | ||
| ``` | ||
|
|
||
| ## Exception Handling Process | ||
|
|
||
| 1. Exception is captured in the GAgent | ||
| 2. Exception information is encapsulated as an ExceptionEvent | ||
| 3. ExceptionEvent is published to a dedicated Orleans Stream | ||
| 4. Via Kafka, exception events are routed to consumers for processing | ||
| 5. Exception handling services can aggregate, analyze, and alert on exceptions | ||
|
|
||
| ## Best Practices | ||
|
|
||
| - Add exception capturing and publishing for important or complex operations | ||
| - Include sufficient information in the context data to facilitate troubleshooting | ||
| - When handling sensitive data, be careful not to include personal privacy information in the context | ||
| - For high-frequency operations, consider setting an exception sampling rate to avoid too many exception events affecting performance | ||
| - Implement exception consumer services for real-time monitoring and analysis of exceptions |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,209 @@ | ||
| using System; | ||
| using System.Threading.Tasks; | ||
| using Aevatar.Core; | ||
| using Aevatar.Core.Abstractions; | ||
| using Aevatar.Core.Extensions; | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace Aevatar.Samples.ExceptionHandling; | ||
|
|
||
| /// <summary> | ||
| /// Sample State Class | ||
| /// </summary> | ||
| [GenerateSerializer] | ||
| public class SampleState : StateBase | ||
| { | ||
| [Id(0)] public int Counter { get; set; } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sample State Log Event Class | ||
| /// </summary> | ||
| [GenerateSerializer] | ||
| public class SampleStateLogEvent : StateLogEventBase<SampleStateLogEvent> | ||
| { | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Sample GAgent demonstrating how to use exception capturing and publishing features | ||
| /// </summary> | ||
| [GAgent] | ||
| public class ExceptionHandlingSampleGAgent : GAgentBase<SampleState, SampleStateLogEvent> | ||
| { | ||
| private readonly ILogger<ExceptionHandlingSampleGAgent> _logger; | ||
|
|
||
| public ExceptionHandlingSampleGAgent(ILogger<ExceptionHandlingSampleGAgent> logger) : base(logger) | ||
| { | ||
| _logger = logger; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Demonstrates how to use exception publishing feature directly | ||
| /// </summary> | ||
| public async Task DemoDirectExceptionPublishingAsync() | ||
| { | ||
| _logger.LogInformation("Starting direct exception publishing demo"); | ||
|
|
||
| try | ||
| { | ||
| // Simulate an exception | ||
| throw new InvalidOperationException("This is a test exception"); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| // Create context data | ||
| var contextData = new | ||
| { | ||
| Operation = "DemoDirectExceptionPublishing", | ||
| Timestamp = DateTime.UtcNow, | ||
| GrainId = this.GetGrainId().ToString() | ||
| }; | ||
|
|
||
| // Publish exception directly | ||
| var exceptionId = await this.PublishExceptionAsync(ex, contextData); | ||
|
|
||
| _logger.LogInformation("Published exception with ID: {ExceptionId}", exceptionId); | ||
|
|
||
| // In real applications, you might choose to rethrow or handle the exception | ||
| // throw; | ||
| } | ||
|
|
||
| _logger.LogInformation("Completed direct exception publishing demo"); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Demonstrates how to use helper method to catch and publish exceptions (without return value) | ||
| /// </summary> | ||
| public async Task DemoExceptionHandlingWithoutResultAsync() | ||
| { | ||
| _logger.LogInformation("Starting exception handling demo without result"); | ||
|
|
||
| var contextData = new | ||
| { | ||
| Operation = "DemoExceptionHandlingWithoutResult", | ||
| Timestamp = DateTime.UtcNow, | ||
| GrainId = this.GetGrainId().ToString() | ||
| }; | ||
|
|
||
| // Use helper method to catch and publish exceptions, without rethrowing the exception | ||
| var exceptionId = await this.CatchAndPublishExceptionAsync( | ||
| async () => | ||
| { | ||
| // Simulate an exception | ||
| await Task.Delay(100); | ||
| throw new ArgumentException("Invalid argument in operation"); | ||
| }, | ||
| contextData, | ||
| rethrowException: false); | ||
|
|
||
| if (exceptionId != Guid.Empty) | ||
| { | ||
| _logger.LogInformation("Exception occurred and published with ID: {ExceptionId}", exceptionId); | ||
| } | ||
|
|
||
| _logger.LogInformation("Completed exception handling demo without result"); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Demonstrates how to use helper method to catch and publish exceptions (with return value) | ||
| /// </summary> | ||
| public async Task<(bool Success, string Message)> DemoExceptionHandlingWithResultAsync() | ||
| { | ||
| _logger.LogInformation("Starting exception handling demo with result"); | ||
|
|
||
| var contextData = new | ||
| { | ||
| Operation = "DemoExceptionHandlingWithResult", | ||
| Timestamp = DateTime.UtcNow, | ||
| Parameters = new { Id = "sample-id", RequestType = "GET" } | ||
| }; | ||
|
|
||
| // Use helper method to catch and publish exceptions, with return value, without rethrowing the exception | ||
| var (result, exceptionId) = await this.CatchAndPublishExceptionAsync( | ||
| async () => | ||
| { | ||
| // Simulate a successful operation | ||
| await Task.Delay(100); | ||
|
|
||
| // May throw an exception based on conditions | ||
| if (DateTime.UtcNow.Millisecond % 2 == 0) | ||
| { | ||
| throw new TimeoutException("Operation timed out"); | ||
| } | ||
|
|
||
| return (Success: true, Message: "Operation completed successfully"); | ||
| }, | ||
| (Success: false, Message: "Operation failed due to an exception"), // Default value | ||
| contextData, | ||
| rethrowException: false); | ||
|
|
||
| if (exceptionId != Guid.Empty) | ||
| { | ||
| _logger.LogInformation("Exception occurred and published with ID: {ExceptionId}", exceptionId); | ||
| _logger.LogInformation("Using default result: {Result}", result); | ||
| } | ||
| else | ||
| { | ||
| _logger.LogInformation("Operation completed successfully: {Result}", result); | ||
| } | ||
|
|
||
| _logger.LogInformation("Completed exception handling demo with result"); | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Demonstrates how to use exception capturing and publishing in actual business logic | ||
| /// </summary> | ||
| public async Task<int> PerformBusinessOperationAsync(int value) | ||
| { | ||
| _logger.LogInformation("Performing business operation with value: {Value}", value); | ||
|
|
||
| // Catch and publish exceptions with business context data | ||
| var contextData = new | ||
| { | ||
| OperationName = "PerformBusinessOperation", | ||
| InputValue = value | ||
| }; | ||
|
|
||
| var (result, exceptionId) = await this.CatchAndPublishExceptionAsync( | ||
| async () => | ||
| { | ||
| // Simulate business logic | ||
| await Task.Delay(100); | ||
|
|
||
| if (value < 0) | ||
| { | ||
| throw new ArgumentOutOfRangeException(nameof(value), "Value cannot be negative"); | ||
| } | ||
|
|
||
| // Update state | ||
| RaiseEvent(new SampleStateLogEvent()); | ||
| State.Counter += value; | ||
|
|
||
| return State.Counter; | ||
| }, | ||
| -1, // Default value, indicating operation failure | ||
| contextData, | ||
| rethrowException: false); | ||
|
|
||
| if (exceptionId != Guid.Empty) | ||
| { | ||
| _logger.LogWarning("Business operation failed with exception ID: {ExceptionId}", exceptionId); | ||
| } | ||
| else | ||
| { | ||
| _logger.LogInformation("Business operation completed successfully, new counter value: {Counter}", result); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// GAgent description | ||
| /// </summary> | ||
| public override Task<string> GetDescriptionAsync() | ||
| { | ||
| return Task.FromResult("Exception Handling Sample GAgent"); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| namespace Aevatar.Core.Abstractions; | ||
|
|
||
| [GenerateSerializer] | ||
| public class ExceptionEvent : EventBase | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could you add GrainType, EventType and the original Event object
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| { | ||
| [Id(0)] public required GrainId GrainId { get; set; } | ||
| [Id(1)] public required string ExceptionMessage { get; set; } | ||
| [Id(2)] public required string ExceptionType { get; set; } | ||
| [Id(3)] public required string StackTrace { get; set; } | ||
|
Jeff-Xu23 marked this conversation as resolved.
|
||
| [Id(4)] public required string ContextData { get; set; } | ||
| [Id(5)] public required DateTime Timestamp { get; set; } = DateTime.UtcNow; | ||
| [Id(6)] public string? MethodName { get; set; } | ||
| [Id(7)] public string? ClassName { get; set; } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.