tutorial

53 Logging dan Error Handling di GraphQL Server

53 Logging dan Error Handling di GraphQL Server

Salah satu tantangan dalam membangun API modern adalah memastikan observabilitas dan robustnya penanganan error. Ketika kita membangun server GraphQL, baik dengan Node.js, Python, maupun bahasa lain, logging dan error handling yang baik bukan sekadar pelengkap, tapi bagian fundamental yang tidak boleh diabaikan. Dalam artikel ini, kita akan eksplorasi secara mendalam tentang logging dan error handling pada GraphQL Server, lengkap dengan contoh kode, simulasi, dan diagram alur.


Mengapa Logging dan Error Handling itu Penting?

Logging memberikan visibility tentang apa yang terjadi di sistem, membantu membantu debugging, pemantauan performa, hingga deteksi serangan. Error handling yang baik memastikan aplikasi tetap tangguh dan user mendapat pesan error yang relevan tanpa mengorbankan keamanan sistem.


Konsep Dasar Logging & Error Handling di GraphQL

Logging

Secara sederhana, logging adalah proses mencatat peristiwa selama eksekusi program. Pada server GraphQL, kita bisa membagi logging menjadi beberapa level, misalnya:

Level LogDeskripsi
ErrorError yang menyebabkan request gagal
WarningHal-hal yang perlu diperhatikan, tapi tidak fatal
InfoInformasi umum jalannya sistem
DebugDetail teknis, biasa dipakai saat develop

Error Handling

GraphQL memiliki mekanisme untuk error propagation yang berbeda dengan REST. Semua response GraphQL mengembalikan HTTP 200, meski error terjadi; detail error diletakkan di field errors pada response JSON.

Contoh response error GraphQL:

{
  "data": null,
  "errors": [
    {
      "message": "User not found",
      "locations": [ { "line": 2, "column": 3 } ],
      "path": [ "user" ]
    }
  ]
}

Implementasi Logging di GraphQL Server (Node.js Example)

Mari kita mulai dengan setup sederhana menggunakan Apollo Server dan library logging populer seperti winston.

Instalasi Package

npm install apollo-server winston

Konfigurasi Winston

// logger.js
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.json(),
  transports: [
    new winston.transports.Console(),
    // new winston.transports.File({ filename: 'error.log', level: 'error' }),
  ],
});

module.exports = logger;

Logging pada Resolvers

Kita ingin log setiap kali terjadi error ataupun event penting, misal query user tertentu:

// resolvers.js
const logger = require('./logger');

const resolvers = {
  Query: {
    user: async (_, { id }, ctx) => {
      logger.info(`Fetching user with id=${id}`);
      const user = await ctx.db.getUserById(id);
      if (!user) {
        logger.error(`User not found: id=${id}`);
        throw new Error('User not found');
      }
      return user;
    }
  }
};

module.exports = resolvers;

Log Request dan Response di Middleware

Apollo Server menyediakan lifecycle hooks agar kita bisa logging lebih terstruktur:

// server.js
const { ApolloServer } = require('apollo-server');
const logger = require('./logger');
const typeDefs = require('./typeDefs');
const resolvers = require('./resolvers');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => ({ db: require('./db'), req }),
  plugins: [
    {
      requestDidStart(requestContext) {
        logger.info(`Received request: ${requestContext.request.query}`);
        return {
          willSendResponse({ response }) {
            if (response.errors) {
              logger.error(JSON.stringify(response.errors));
            }
            logger.info('Sending response...');
          },
        };
      },
    },
  ]
});

server.listen().then(({ url }) => {
  logger.info(`🚀 Server ready at ${url}`);
});

Error Handling di GraphQL

Menggunakan Custom Error Classes

GraphQL/Apollo menyediakan class ApolloError untuk error yang disesuaikan, sehingga kita bisa mengatur kode status & field tambahan:

const { ApolloError, UserInputError, AuthenticationError } = require('apollo-server');

const resolvers = {
  Mutation: {
    updateProfile: async (_, args, ctx) => {
      if (!ctx.user) {
        throw new AuthenticationError('You must be logged in');
      }
      if (!args.email) {
        throw new UserInputError('email is required');
      }
      // ...
      try {
        // Simulate db error
        throw new Error('DB connection failed!');
      } catch (err) {
        throw new ApolloError('Internal server error', 'INTERNAL_ERROR');
      }
    }
  }
}

Hasilnya, field extensions.code pada response error akan berisi kode yang sesuai.

Simulasi Output Error

Misal kita kirim mutation updateProfile tanpa login, outputnya:

{
  "data": null,
  "errors": [{
    "message": "You must be logged in",
    "extensions": {
      "code": "UNAUTHENTICATED"
    }
  }]
}

Jika error pada database:

{
  "data": null,
  "errors": [{
    "message": "Internal server error",
    "extensions": {
      "code": "INTERNAL_ERROR"
    }
  }]
}

Pengelolaan Error Secara Terpusat

Alih-alih menangani error di tiap resolver, kita bisa centralized error handling pada layer server dengan formatError:

const server = new ApolloServer({
  // ...
  formatError: (err) => {
    logger.error(`[GraphQL Error] ${err.message}`);
    // Custom masking: hanya tampilkan pesan aman ke client
    if (err.extensions.code === 'INTERNAL_SERVER_ERROR') {
      return new Error('An unexpected error occurred'); // Hide internal details
    }
    return err;
  }
});

Flow Logging & Error Handling (Diagram Mermaid)

Mari lihat alur utama proses ini:

flowchart TD
  A[Request Masuk] --> B{Ada Error?}
  B -- Ya --> C{Error Known?}
  C -- Ya --> D[Log Error w/ Context dan Return Custom Error]
  C -- Tidak --> E[Log Error dan Masking Error, Return Generic Message]
  B -- Tidak --> F[Log Sukses, Return Data]

Pada diagram tersebut, kita membedakan error yang sudah kita ketahui jenisnya (misal: auth, input validation) dengan error tak terduga (runtime error, db crash dsb).


Praktik Terbaik (Best Practice)

Dari pengalaman deploy GraphQL ke production, berikut beberapa saran yang sebaiknya kamu ikuti:

  1. Pisahkan penanganan error trusted vs untrusted
    Trusted: domain error, input, business logic.
    Untrusted: jaringan, database, bug runtime — mask error ke client, log detail pada server.

  2. Log context yang cukup saat error, jangan log password/api key!

  3. Gunakan kode error/enum yang jelas pada extensions.code

  4. Tambahkan correlation id pada setiap request untuk tracing

  5. Notifikasi ke ops/dev kalau error kritikal muncul (misal integrasi ke Slack, Sentry, atau email)

  6. Jangan expose stack trace atau error internal ke front-end


Penutup

Logging dan error handling di server GraphQL bukan sekadar menambah console.log lalu melempar error. Implementasi yang baik membuat sistem mudah dipantau, aman, dan gampang di-debug. Ingat, sebuah sistem pasti akan error — yang membedakannya, seberapa siap aplikasi kita menghadapinya?

Terus asah kemampuan observabilitas Anda, karena dengan ekosistem yang grow fast seperti GraphQL, robust error handling & logging is no longer optional. Happy hacking!

comments powered by Disqus