- Add admin dashboard with sync controls and data browser - Implement RMM, Auvik, and Addigy organization mappings - Add chunked ticket sync with progress tracking - Implement entity sync service with rate limiting - Add analytics engine and performance optimizer - Create data browser for all PSA entities - Add navigation components and UI improvements - Implement background processing and sync services - Add comprehensive documentation and migration scripts - Update configuration items with multi-system support - Enhance contact management and purchase history - Add issue type assignment and LLM analyzer - Improve error handling and logging utilities
7.6 KiB
Error Handling and Logging Guide
Overview
This document describes the error handling and logging implementation for the PostgreSQL Autotask Sync system.
Error Types
All sync errors extend from the base SyncError class defined in lib/types/errors.ts:
Error Hierarchy
SyncError (base)
├── NetworkError (retryable)
├── AuthError (not retryable)
├── RateLimitError (retryable)
├── ApiError (retryable for 5xx)
├── DatabaseError (configurable)
│ └── ConstraintError (not retryable)
├── ValidationError (not retryable)
├── MappingError (not retryable)
├── ConfigError (not retryable)
└── TimeoutError (retryable)
Error Properties
Each error includes:
message: Human-readable error descriptioncode: Machine-readable error code (e.g., 'NETWORK_ERROR')context: Additional context data (entity, operation, etc.)isRetryable: Boolean indicating if operation can be retriedstack: Stack trace for debugging
Error Categorization
The categorizeError() function automatically categorizes generic errors:
import { categorizeError, isRetryableError } from '@/lib/types/errors';
try {
// ... operation
} catch (error) {
const categorized = categorizeError(error);
console.log(`Error type: ${categorized.code}`);
console.log(`Retryable: ${categorized.isRetryable}`);
}
Logging
Logger Utility
The Logger class in lib/utils/logger.ts provides structured logging:
import { createLogger } from '@/lib/utils/logger';
const logger = createLogger({ syncId: '123', entity: 'companies' });
logger.info('Starting sync');
logger.warn('Rate limit approaching', { remaining: 10 });
logger.error('Sync failed', error, { recordCount: 100 });
Log Levels
DEBUG: Detailed diagnostic informationINFO: General informational messagesWARN: Warning messages for non-critical issuesERROR: Error messages for failures
Log Format
[2025-11-01T12:00:00.000Z] [INFO] [syncId=abc123, entity=companies] Starting sync
Error Handling in Sync Operations
Sync Service (lib/services/sync-service.ts)
The sync service implements comprehensive error handling:
- Entity-level error handling: Each entity sync is wrapped in try-catch
- Error categorization: Errors are categorized for better diagnostics
- Sync history updates: Failed syncs are recorded in sync_history
- Detailed logging: Errors include context (entity, sync ID, type)
- Graceful degradation: One entity failure doesn't stop the entire sync
Example error log output:
✗ Failed to sync Companies:
Error: Autotask API error: Connection timeout
Stack: <stack trace>
Entity: companies
Sync Type: full
Sync ID: sync_20251101_120000_abc123
[NETWORK_ERROR] Autotask API error: Connection timeout
Entity Sync Service (lib/services/entity-sync.ts)
The entity sync service provides granular error handling:
- Operation-level try-catch: Each step (fetch, map, upsert) is protected
- Contextual logging: All logs prefixed with
[entity] - Error wrapping: Generic errors wrapped with context
- Soft delete tolerance: Soft delete failures don't fail entire sync
Example log output:
[companies] Starting sync (full)
[companies] Fetching records from Autotask API...
[companies] Fetched 150 records from Autotask
[companies] Mapping 150 records to database schema...
[companies] Successfully mapped 150 records
[companies] Upserting records to PostgreSQL...
[companies] Upserted 150 records to PostgreSQL
[companies] Checking for records to soft delete...
[companies] Soft deleted 5 missing records
[companies] Sync completed in 2543ms
Error Recovery Strategies
Retryable Errors
For retryable errors (network, rate limit, 5xx API errors):
- Error is logged with
isRetryable: true - Sync history records the error
- User/system can retry the operation
- Rate limiter handles 429 responses automatically
Non-Retryable Errors
For non-retryable errors (auth, validation, constraints):
- Error is logged with detailed context
- Sync history records the failure
- User must fix the underlying issue before retrying
Partial Sync Failures
When some entities succeed and others fail:
- Successful entities are committed to database
- Failed entities are logged with errors
- Sync result includes both successes and failures
- User can retry only failed entities
Sync History
All sync operations are recorded in the sync_history table:
SELECT
entity_type,
sync_type,
status,
records_added,
records_updated,
records_deleted,
error_message,
started_at,
completed_at
FROM sync_history
WHERE status = 'failed'
ORDER BY started_at DESC;
Error messages in sync_history include:
- Error category (e.g.,
[NETWORK_ERROR]) - Original error message
- Full context for debugging
Best Practices
1. Always Use Try-Catch
try {
await syncOperation();
} catch (error) {
const categorized = categorizeError(error);
logger.error('Operation failed', categorized);
throw categorized; // Re-throw categorized error
}
2. Provide Context
try {
await fetchData();
} catch (error) {
throw new ApiError(
'Failed to fetch companies',
500,
{ entity: 'companies', operation: 'fetch', recordCount: 100 }
);
}
3. Log at Appropriate Levels
- Use
infofor normal operations - Use
warnfor recoverable issues - Use
errorfor failures - Use
debugfor detailed diagnostics
4. Include Timing Information
const startTime = Date.now();
try {
await operation();
const duration = Date.now() - startTime;
logger.info(`Operation completed in ${duration}ms`);
} catch (error) {
const duration = Date.now() - startTime;
logger.error(`Operation failed after ${duration}ms`, error);
}
5. Update Sync History
Always update sync_history for tracking:
const historyId = await createSyncHistory(entity, syncType);
try {
const stats = await syncEntity(entity);
await updateSyncHistory(historyId, 'completed', stats);
} catch (error) {
await updateSyncHistory(historyId, 'failed', 0, 0, 0, error.message);
throw error;
}
Monitoring and Debugging
View Recent Errors
const syncService = createSyncService(autotaskClient);
const history = await syncService.getSyncHistory(50);
const failures = history.filter(h => h.status === 'failed');
Check Error Patterns
SELECT
error_message,
COUNT(*) as occurrence_count,
MAX(started_at) as last_occurrence
FROM sync_history
WHERE status = 'failed'
AND started_at > NOW() - INTERVAL '7 days'
GROUP BY error_message
ORDER BY occurrence_count DESC;
Identify Problematic Entities
SELECT
entity_type,
COUNT(*) as failure_count,
COUNT(*) FILTER (WHERE error_message LIKE '%NETWORK_ERROR%') as network_errors,
COUNT(*) FILTER (WHERE error_message LIKE '%API_ERROR%') as api_errors
FROM sync_history
WHERE status = 'failed'
AND started_at > NOW() - INTERVAL '7 days'
GROUP BY entity_type
ORDER BY failure_count DESC;
Future Enhancements
Potential improvements for error handling:
- Retry Logic: Automatic retry with exponential backoff for retryable errors
- Circuit Breaker: Prevent repeated failures by temporarily disabling failing operations
- Error Notifications: Send alerts for critical errors (email, Slack, etc.)
- Error Metrics: Track error rates and patterns over time
- Detailed Stack Traces: Store full stack traces in separate table for debugging
- Error Recovery Workflows: Automated recovery procedures for common errors