LogoSkills

Serverpod Logging Rules

ApiInterceptor usage and security guide for Serverpod API request/response logging.

ApiInterceptor usage and security guide for Serverpod API request/response logging.

Architecture Overview#

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                      Repository                          โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚
โ”‚  โ”‚              ApiInterceptor                      โ”‚    โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚         Serverpod Client                โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ”‚  (kobic_client โ†’ kobic_server)          โ”‚    โ”‚    โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ApiInterceptor Usage#

Location#

package/core/lib/src/data/api_interceptor.dart

Default Settings#

const ApiInterceptor({
  this.enableLogging = true,   // Enable API call logging
  this.enableRetry = true,     // Enable retry on failure
  this.maxRetries = 3,         // Maximum retry count
  this.retryDelay = 1s,        // Delay between retries
})

Usage in Repository#

class BookRepository implements IBookRepository {
  final ApiInterceptor _interceptor;

  @override
  Future<Either<Failure, Book>> getBook(int id) async {
    try {
      final result = await _interceptor.executeApiCall(
        apiCall: () => _client.book.getBook(id),
        apiName: 'getBook',
        parameters: {'id': id},
      );
      return Right(result);
    } on ApiException catch (error) {
      return Left(error.toFailure());
    }
  }
}

Log Output Format#

API Call Start#

๐Ÿš€ API Call: getBook
โฐ Start: 10:30:45.123
๐Ÿ“ Parameters:
  โ€ข id: 123

API Success#

โœ… API Success: getBook (245ms)
๐Ÿ“Š Result: Book
  โ€ข Value: Book(id: 123, title:  ' Title ' )

API Failure#

โŒ API Failure: getBook (1205ms)
๐Ÿ’ฅ Error: ServerpodClientException
๐Ÿ“„ Message: Not Found

Retry#

โš ๏ธ Retry 2/3 (after 1000ms)

Retry Policy#

Retryable Errors#

Status CodeDescriptionRetry
5xxServer errorโœ…
408Request Timeoutโœ…
socket errorNetwork connection failureโœ…
timeoutRequest timeoutโœ…
handshake errorSSL handshake failureโœ…
4xx (other)Client errorโŒ

Custom Retry Settings#

// Custom retry configuration
final interceptor = ApiInterceptor(
  enableRetry: true,
  maxRetries: 5,
  retryDelay: const Duration(seconds: 2),
);

Error Conversion#

ApiInterceptor converts ServerpodClientException to project custom exceptions.

Status CodeConverted ExceptionMessage
401UnauthorizedExceptionAuthentication required
403ForbiddenExceptionAccess denied
404NotFoundExceptionResource not found
422ValidationExceptionInvalid input data
500ServerExceptionServer error occurred
socketNetworkExceptionNetwork connection error
timeoutTimeoutExceptionRequest timeout

Security Rules#

Do Not Log Sensitive Information#

// โŒ Forbidden: Include sensitive info in parameters
await _interceptor.executeApiCall(
  apiCall: () => _client.auth.login(userId, password),
  apiName: 'login',
  parameters: {
    'userId': userId,
    'password': password,  // ๐Ÿšจ Forbidden!
  },
);

// โœ… Recommended: Exclude sensitive info
await _interceptor.executeApiCall(
  apiCall: () => _client.auth.login(userId, password),
  apiName: 'login',
  parameters: {'userId': userId},  // password excluded
);

Sensitive Information List#

CategoryExamplesLogging
Credentialspassword, pin, userPwโŒ Forbidden
TokensaccessToken, refreshToken, idTokenโŒ Forbidden
Identity infouserId, loginId (except for debugging)โš ๏ธ Caution
Personal infophoneNumber, email, addressโŒ Forbidden
Auth codessmsCode, verificationCodeโŒ Forbidden

kDebugMode Pattern#

Disable Logging in Production Builds#

import 'package:flutter/foundation.dart' show kDebugMode;

// Set in ApiInterceptorConfig
class ApiInterceptorConfig {
  static const defaultEnableLogging = kDebugMode;
  // ...
}

// Or explicitly set during creation
final interceptor = ApiInterceptor(
  enableLogging: kDebugMode,
);

kDebugMode Advantages#

  • Compile-time constant, completely removed from production code
  • No runtime overhead
  • Enhanced security (prevents log exposure in production)

Server-side Logging (Serverpod)#

Using session.log()#

// In backend/kobic_server
Future<Book> getBook(Session session, int id) async {
  session.log('๐Ÿ“š Book lookup request: id=$id');

  final book = await Book.db.findById(session, id);

  if (book == null) {
    session.log('โš ๏ธ Book not found: id=$id', level: LogLevel.warning);
    throw NotFoundException('Book not found');
  }

  session.log('โœ… Book lookup success: ${book.title}');
  return book;
}

LogLevel Types#

LevelUsage
LogLevel.debugDevelopment debugging
LogLevel.infoGeneral information (default)
LogLevel.warningWarnings
LogLevel.errorErrors
LogLevel.fatalFatal errors

Checklist#

When Implementing Repository#

  • Use ApiInterceptor.executeApiCall()
  • Explicitly specify apiName
  • Exclude sensitive info from parameters
  • Verify error conversion handling

During PR Review#

  • New API calls are wrapped with ApiInterceptor
  • No sensitive info in parameters
  • kDebugMode conditional logging maintained