Cara Memuat Global Custom Error Filter GraphQL di Hot Chocolate .NET Platform
Tags: graphql, tutorial, dotnet, indonesia
Published: 2022-12-12 15:53:07

## Permasalahan Error di Hot Chocolate

Saat mengembangkan sistem menggunakan Framework [Hot Chocolate](https://chillicream.com/docs/hotchocolate), kita pasti akan menemukan masalah berupa response error setelah melakukan request query ke sistem. Error yang ditampilkan tidak informatif sehingga sering membuat bingung letak permasalahan utamanya. Berikut contoh default error yang akan ditampilkan.

```json
{
	"errors": [
		{
			"message": "Unexpected Execution Error",
			"locations": [
				{
					"line": 11,
					"column": 2
				}
			],
			"path": [
				"upsertStudent"
			]
		}
	]
}
```

Response error hanya menampilkan pesan "unexpected execution error". Response ini tidak informatif karena kita tidak mengetahui penyebab utama atau root cause error tersebut. Kita hanya punya informasi nama query yang membuat error yaitu `upsertStudent`, tapi tidak tahu line code mana yang menyebabkan error tersebut terjadi. Oleh karena itu, kita perlu menghandle dengan cara membuat custom error filter agar error yang ditampilkan informatif dan dapat dimengerti oleh frontend web atau mobile.

## Pendekatan Pemecahan Permasalahan
Ada beberapa cara yang bisa digunakan untuk mengatasi masalah di atas. Beberapa solusi yang bisa digunakan:

### **1. Menampilkan Detail Error di Response**

Solusi ini sebenarnya **tidak** cocok digunakan untuk production environment karena menampilkan pesan error secara gamblang. Gunakan solusi ini hanya untuk development saja. Berikut codenya:

```c#
services.AddGraphQLServer()
        .RegisterDbContext<AppDbContext>()
        ....
        .ModifyRequestOptions(opt => opt.IncludeExceptionDetails = env.IsDevelopment());
```
Penambahaan opsi `IncludeExceptionDetails` dengan nilai true (jika env = development), maka akan memunculkan detail error. Berikut contoh hasil responsenya.

```json
{
	"errors": [
		{
			"message": "Unexpected Execution Error",
			"locations": [
				{
					"line": 20,
					"column": 2
				}
			],
			"path": [
				"removeStudent"
			],
			"extensions": {
				"message": "Sequence contains no elements.",
				"stackTrace": "
at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)\r\n 
.... 
at ManajemenTugasAkhirGeologi.GraphQL.Students.Services.Implementations.StudentService.RemoveStudent(Guid studentId, CancellationToken cancellationToken) 
in ... 
\\GraphQL\\Students\\Services\\Implementations\\StudentService.cs:line 55 .... ...."
			}
		}
	]
}
```
Hasil response tersebut menampilkan error yang sebenarnya yaitu "sequence contains no element" pada file `StudentService.cs` line 55. Dari detail stack trace tersebut dapat disimpulkan bahwa data yang dicari tidak ditemukan dalam database. 

Solusi ini tidak cocok untuk production karena pesan errornya masih sangat general, oleh karena itu kita bisa melakukan custom error filter agar pesan error yang ditampilkan sesuai dengan yang kita harapkan.

### **2. Global Custom Error Filter**

Solusi ini dapat digunakan untuk mengcustom pesan error sesuai dengan keinginan kita. Hal pertama yang dilakukan adalah membuat class yang mengimplementasikan `IErrorFilter` dari HotChocolate library.

```c#
//implement IErrorFilter
public class GraphQLErrorFilter : IErrorFilter
{
    public IError OnError(IError error)
    {
        // BusinessLogicException adalah custom error
        if (error.Exception is BusinessLogicException)
        {
            return error.WithCode("BusinessLogic").WithMessage(error.Exception!.Message);
        }
        return error;
    }
}
```
Potongan code di atas menjelaskan class `GraphQLErrorFilter` yang mengimplementasikan `IErrorFilter` dari Hot Chocolate. Method `OnError` akan mengecek apakah error yang dihandle adalah `BusinessLogicException`, jika iya, maka akan menampilkan error sesuai dengan pesan yang ditulis pada saat throw exception. Selain itu, maka error akan dikembalikan tanpa ada perubahan. Kodingan custom exception sebagai berikut. 

```c#
//custom class exception
public class BusinessLogicException : Exception
{
    public BusinessLogicException() { }

    public BusinessLogicException(string message) : base(String.Format(message)) { }
}
```
Code di atas menjelaskan tentang pembuatan class exception baru yang bernama `BusinessLogicException` yang merupakan turunan dari class `Exception`. Terdapat overloading constructor bernama `BusinessLogicException` yang bisa menerima 0 atau 1 parameter string message.

Hal selanjutnya yaitu mendaftarkan custom error filter pada `Service Collection`

```c#
services.AddGraphQLServer();
...
services.AddErrorFilter<GraphQLErrorFilter>();
```
Jika sudah ditambahkan maka custom exception sudah bisa digunakan. Contoh penggunaan custom exception sebagai berikut.
```c#
var student = await _dbContext.Students.SingleOrDefaultAsync(s => s.Id == studentId, cancellationToken);
if (student == null)
    //memanggil custom exception
    throw new BusinessLogicException($"id {studentId} tidak ditemukan");
_dbContext.Remove(student);
await _dbContext.SaveChangesAsync(cancellationToken);
```
Penggunaan custom exception akan tampil pada response error. Berikut contoh hasil akhir saat error terjadi.

```json
{
	"errors": [
		{
			"message": "id 08dacddf-40f6-43cb-84d6-7c51795af961 tidak ditemukan",
			"locations": [
				{
					"line": 20,
					"column": 2
				}
			],
			"path": [
				"removeStudent"
			],
			"extensions": {
				"code": "BusinessLogic"
			}
		}
	]
}
```

### **3. Tampilkan Error pada Log di Console**

Solusi ketiga yang dapat dilakukan adalah menampilkan logging eror pada console setiap kali error terjadi. Framework Hot chocolate telah menyediakan class `ExecutionDiagnosticEventListener`. Terdapat banyak method yang dapat dioverride dari class tersebut, seperti `ResolverError()`, `TaskError()`, dan `RequestError()`. Berikut contoh code untuk menampilkan error di log console.

```c#
public class ErrorLoggingDiagnosticsEventListener : ExecutionDiagnosticEventListener
{
    private readonly ILogger<ErrorLoggingDiagnosticsEventListener> _log;

    public ErrorLoggingDiagnosticsEventListener(ILogger<ErrorLoggingDiagnosticsEventListener> log)
    {
        _log = log;
    }

    public override void ResolverError(IMiddlewareContext context, IError error)
    {
        _log.LogError(error.Exception, error.Message);
    }

    public override void TaskError(IExecutionTask task, IError error)
    {
        _log.LogError(error.Exception, error.Message);
    }

    public override void RequestError(IRequestContext context, Exception exception)
    {
        _log.LogError(exception, "RequestError");
    }
}
```
Setelah itu jangan lupa untuk menambahkan `ErrorLoggingDiagnosticsEventListener` pada `Service Collection`.

```c#
services.AddGraphQLServer()
        .AddDiagnosticEventListener<ErrorLoggingDiagnosticsEventListener>();
```

Setelah itu, console akan menampilkan error dan stack tracenya. Berikut contoh realnya.

```log
fail: ManajemenTugasAkhirGeologi.ErrorFilters.ErrorLoggingDiagnosticsEventListener[0]
      Data mahasiswa tidak ditemukan
      ManajemenTugasAkhirGeologi.ErrorFilters.BusinessLogicException: Data mahasiswa tidak ditemukan
         at ManajemenTugasAkhirGeologi.GraphQL.Students.Services.Implementations.StudentService.RemoveStudent(Guid studentId, CancellationToken cancellationToken) in D:\manajementugasakhir\ManajemenTugasAkhirGeologi\GraphQL\Students\Services\Implementations\StudentService.cs:line 57
         at ManajemenTugasAkhirGeologi.GraphQL.Students.Mutations.StudentMutation.RemoveStudent(Guid studentId, IStudentService studentService, CancellationToken cancellationToken) in D:\manajementugasakhir\ManajemenTugasAkhirGeologi\GraphQL\Students\Mutations\StudentMutation.cs:line 23
         at HotChocolate.Resolvers.Expressions.ExpressionHelper.AwaitTaskHelper[T](Task`1 task)
         at HotChocolate.Types.Helpers.FieldMiddlewareCompiler.<>c__DisplayClass9_0.<<CreateResolverMiddleware>b__0>d.MoveNext()
      --- End of stack trace from previous location ---
         at HotChocolate.AspNetCore.Authorization.AuthorizeMiddleware.InvokeAsync(IDirectiveContext context)
         at HotChocolate.Utilities.MiddlewareCompiler`1.ExpressionHelper.AwaitTaskHelper(Task task)
         at HotChocolate.Execution.Processing.Tasks.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)
         at HotChocolate.Execution.Processing.Tasks.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)
```

### **4. Selesai 🔥**
Nah itulah beberapa solusi yang bisa dicoba Dari ketiga solusi tersebut, saya lebih prefer untuk memakai cara yang kedua dan ketiga karena lebih cocok untuk dilakukan pada level production. Detail error **seharusnya** tidak boleh ditampilkan pada pihak luar karena berpotensi pada security risk atau celah keamanan.

## Sekian dan Terimakasih
Jika artikel ini bermanfaat, bisa like, comment, dan share ya... 👍
Masukan, kritik, dan pertanyaan dapat disampaikan pada kolom komentar. Mantab, salam koding, dan terimakasih. 

![Thank You](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qztkom1nw53w30a8wcg1.gif)



## Referensi

- [doc hot chocolate error filter](https://chillicream.com/docs/hotchocolate/v12/api-reference/error-filter)
- [doc hot chocolate diagnostic events](https://chillicream.com/docs/hotchocolate/v12/server/instrumentation#execution-events)
- [stack overflow](https://stackoverflow.com/questions/65764361/how-can-i-get-more-error-details-or-logging-when-an-exception-is-thrown-in-a-ho)
- [Cover Image](faesel.com)