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"
]
}
]
}
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());
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 .... ...."
}
}
]
}
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;
}
}
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)) { }
}
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>();
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);
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"
}
}
]
}
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");
}
}
Setelah itu jangan lupa untuk menambahkan ErrorLoggingDiagnosticsEventListener
pada Service Collection
.
services.AddGraphQLServer()
.AddDiagnosticEventListener<ErrorLoggingDiagnosticsEventListener>();
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)
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.
Top comments (0)