DEV Community

Cover image for Cara Memuat Global Custom Error Filter GraphQL di Hot Chocolate .NET Platform
Rahmat Al Hakam
Rahmat Al Hakam

Posted on

Cara Memuat Global Custom Error Filter GraphQL di Hot Chocolate .NET Platform

Permasalahan Error di Hot Chocolate

Saat mengembangkan sistem menggunakan Framework Hot Chocolate, 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.

{
    "errors": [
        {
            "message": "Unexpected Execution Error",
            "locations": [
                {
                    "line": 11,
                    "column": 2
                }
            ],
            "path": [
                "upsertStudent"
            ]
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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:

services.AddGraphQLServer()
        .RegisterDbContext<AppDbContext>()
        ....
        .ModifyRequestOptions(opt => opt.IncludeExceptionDetails = env.IsDevelopment());
Enter fullscreen mode Exit fullscreen mode

Penambahaan opsi IncludeExceptionDetails dengan nilai true (jika env = development), maka akan memunculkan detail error. Berikut contoh hasil responsenya.

{
    "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 .... ...."
            }
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

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.

//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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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.

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

    public BusinessLogicException(string message) : base(String.Format(message)) { }
}
Enter fullscreen mode Exit fullscreen mode

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

services.AddGraphQLServer();
...
services.AddErrorFilter<GraphQLErrorFilter>();
Enter fullscreen mode Exit fullscreen mode

Jika sudah ditambahkan maka custom exception sudah bisa digunakan. Contoh penggunaan custom exception sebagai berikut.

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);
Enter fullscreen mode Exit fullscreen mode

Penggunaan custom exception akan tampil pada response error. Berikut contoh hasil akhir saat error terjadi.

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

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.

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

Setelah itu jangan lupa untuk menambahkan ErrorLoggingDiagnosticsEventListener pada Service Collection.

services.AddGraphQLServer()
        .AddDiagnosticEventListener<ErrorLoggingDiagnosticsEventListener>();
Enter fullscreen mode Exit fullscreen mode

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

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)
Enter fullscreen mode Exit fullscreen mode

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

Referensi

Top comments (0)