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