enuthu / api-error-logger
Centralized API-call and application-error logging with duplicate detection, sanitization, and administrator notifications.
Requires (Dev)
- phpstan/phpstan: ^1.12 || ^2.2
- phpunit/phpunit: ^9.6 || ^10.5 || ^11.0
- squizlabs/php_codesniffer: ^3.7
Suggests
- phpmailer/phpmailer: Use the optional PHPMailer transport adapter.
- symfony/mailer: Use the optional Symfony Mailer transport adapter.
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-09-19 05:20:41 UTC
README
enuthu/api-error-logger is a reusable PHP package for centralized API-call, API-error, and application-error logging. It stores structured error records, groups duplicate failures by fingerprint, sanitizes sensitive values, and sends configurable administrator notifications without allowing logger failures to crash the host application.
Existing Implementation Review
The package follows the requested conceptual behavior:
- capture normalized API/application error data;
- persist structured records to a database;
- group repeated failures by stable fingerprint;
- increment
occurrence_countand updatelast_occurred_at; - send administrator email only for configured severities;
- throttle repeated notifications;
- sanitize sensitive data before database or email handling;
- isolate logging failures with an optional fallback logger.
Requirements
- PHP
>=8.0 - PDO
- JSON extension
- MySQL 8+, MariaDB, PostgreSQL, or SQLite for tests/local development
Optional mail integrations:
symfony/mailerphpmailer/phpmailer
Installation
During local development:
composer install
After publication to Packagist:
composer require enuthu/api-error-logger
From GitLab before Packagist publication:
{
"repositories": [
{
"type": "vcs",
"url": "https://gitlab.com/your-group/api-error-logger.git"
}
],
"require": {
"enuthu/api-error-logger": "^1.0"
}
}
Configuration
Copy or adapt config/api-error-logger.php:
$config = [
'enabled' => true,
'environment' => getenv('APP_ENV') ?: 'production',
'table' => 'api_error_logs',
'email_notifications' => true,
'email_severities' => ['critical'],
'email_throttle_minutes' => 10,
'admin_emails' => ['admin@example.com'],
'mask_fields' => ['password', 'token', 'authorization', 'cookie'],
'fallback_log' => true,
'fallback_log_path' => __DIR__ . '/storage/logs/api-error-logger.log',
];
Credentials should come from environment variables or the host application's secret manager. Do not commit SMTP passwords, API tokens, or application secrets.
Database Migration
Migration SQL is provided for:
migrations/create_api_error_logs_mysql.sqlmigrations/create_api_error_logs_pgsql.sqlmigrations/create_api_error_logs_sqlite.sql
Programmatic table creation:
use ENuthu\ApiErrorLogger\Config\LoggerConfig;
use ENuthu\ApiErrorLogger\Database\SchemaManager;
$pdo = new PDO($dsn, $username, $password);
$config = new LoggerConfig(['table' => 'api_error_logs']);
(new SchemaManager($pdo, $config))->createTable();
The table uses a BIGINT/BIGSERIAL style ID for production volumes, stores company_code as a string to preserve values such as 011, and stores context as JSON where the database supports it.
Basic Usage
use ENuthu\ApiErrorLogger\ApiErrorLogger;
$logger = ApiErrorLogger::createWithPdo($pdo, $config);
$logger->logError([
'severity' => 'error',
'error_type' => 'Timeout',
'error_message' => 'The request timed out.',
'api_version' => 'v3_v28',
'api_name' => 'getCompanyList',
'user_id' => 278,
'username' => 'BKDeng',
'company_id' => 4,
'company_code' => '011',
'platform' => 'iOS',
'os_version' => '26.5',
'device_type' => 'iPad',
'device_model' => 'iPad14,6',
]);
Convenience methods are also available:
$logger->info('Health check completed.');
$logger->warning(['error_message' => 'Slow API response.', 'api_name' => 'getCompanyList']);
$logger->error(['error_message' => 'The request timed out.']);
$logger->critical(['error_message' => 'Payment API unavailable.']);
API Call Logging
$logger->logApiCall([
'api_version' => 'v3_v28',
'api_name' => 'getCompanyList',
'request_id' => 'req-123',
'http_method' => 'POST',
'request_url' => 'https://api.example.com/company-list',
'http_status_code' => 200,
'response_time_ms' => 145,
'context' => [
'screen' => 'Company Selection',
'network' => 'WiFi',
],
]);
Request bodies are not stored automatically. Pass only safe, intentional metadata through context.
Exception Logging
try {
// application logic
} catch (Throwable $exception) {
$logger->logException($exception, [
'api_name' => 'getCompanyList',
'user_id' => 278,
'context' => [
'screen' => 'Company Selection',
],
]);
}
The logger stores exception class, message, file, line, and trace in sanitized structured context.
Email Notifications
The package separates notification formatting from mail transport. You can provide any implementation of MailerInterface.
use ENuthu\ApiErrorLogger\ApiErrorLogger;
use ENuthu\ApiErrorLogger\Notifications\Transports\SymfonyMailerTransport;
$transport = new SymfonyMailerTransport($symfonyMailer);
$logger = ApiErrorLogger::createWithPdo($pdo, [
'email_notifications' => true,
'email_severities' => ['error', 'critical'],
'email_throttle_minutes' => 10,
'admin_emails' => ['admin@example.com'],
], $transport);
Available adapters:
NativeMailTransportPhpMailerTransportSymfonyMailerTransport
Notifications include useful debugging fields such as error ID, severity, API name, request ID, user, company, device, HTTP status, occurrence count, environment, and exception details when available.
Severity
Built-in severities are:
infowarningerrorcritical
Severity names are normalized to lowercase strings. The notification list is fully configurable:
'email_severities' => ['critical'],
Duplicate Handling And Throttling
Errors are grouped by a SHA-256 fingerprint built from stable values:
- API name
- error type
- normalized error message
- exception class
- source file and line
Volatile values such as timestamps and request IDs are excluded. Repeated fingerprints update one record by incrementing occurrence_count, updating last_occurred_at, and preserving first_occurred_at.
This grouped-record approach scales better for high-volume repeated failures than writing thousands of identical rows. If a host application needs per-occurrence analytics later, a second occurrence table can be added behind ErrorLogRepositoryInterface without changing the public logger API.
Email throttling is controlled by:
'email_throttle_minutes' => 10,
When the same fingerprint repeats inside the throttle window, the database record is updated but no duplicate email is sent.
Sensitive Data
The sanitizer masks common sensitive keys before persistence or notification:
passwordpasswdtokenaccess_tokenrefresh_tokenauthorizationcookiesessionsession_idsecretapi_keypin
Extend the list through mask_fields:
'mask_fields' => ['password', 'authorization', 'customer_pin'],
Failure Isolation
All logger operations catch internal failures. A database outage, invalid notification transport, or fallback logging failure will not crash the host application.
For file fallback logging:
'fallback_log' => true,
'fallback_log_path' => __DIR__ . '/storage/logs/api-error-logger.log',
To use a PSR-3 logger as fallback:
use ENuthu\ApiErrorLogger\Support\PsrFallbackLogger;
$fallback = new PsrFallbackLogger($psrLogger);
Yii2 Integration
Register the logger as an application component without making Yii2 a package dependency:
use ENuthu\ApiErrorLogger\ApiErrorLogger;
use ENuthu\ApiErrorLogger\Config\LoggerConfig;
use ENuthu\ApiErrorLogger\Database\PdoErrorLogRepository;
use ENuthu\ApiErrorLogger\Notifications\EmailNotifier;
'components' => [
'apiErrorLogger' => function () {
$config = new LoggerConfig(require Yii::getAlias('@app/config/api-error-logger.php'));
$pdo = Yii::$app->db->pdo;
$repository = new PdoErrorLogRepository($pdo, $config);
$mailer = new class (Yii::$app->mailer, $config) implements \ENuthu\ApiErrorLogger\Contracts\MailerInterface {
private $yiiMailer;
private $config;
public function __construct($yiiMailer, $config)
{
$this->yiiMailer = $yiiMailer;
$this->config = $config;
}
public function send(\ENuthu\ApiErrorLogger\Models\MailMessage $message): void
{
$this->yiiMailer->compose()
->setFrom([$message->fromAddress() => $message->fromName()])
->setTo($message->to())
->setSubject($message->subject())
->setTextBody($message->textBody())
->send();
}
};
return new ApiErrorLogger(
$config,
$repository,
new EmailNotifier($config, $mailer)
);
},
],
Controller usage:
Yii::$app->apiErrorLogger->logException($exception, [
'api_name' => Yii::$app->controller->action->id,
'user_id' => Yii::$app->user->id,
]);
Global error handler integration:
try {
Yii::$app->apiErrorLogger->logException($exception, [
'request_url' => Yii::$app->request->absoluteUrl,
'http_method' => Yii::$app->request->method,
]);
} catch (Throwable $ignored) {
// The package already isolates failures; this is only extra protection.
}
Testing And Quality
composer validate --strict
composer test
composer analyse
composer check-style
composer check
Tests use SQLite in memory and fake mail transports. They do not send real email.
Versioning
Initial development version:
0.1.0
First production release:
1.0.0
Release checklist:
- tests passing;
- static analysis passing;
- Composer validation passing;
- README completed;
- CHANGELOG updated;
- Git tag created;
- GitLab release created;
- Packagist synchronization confirmed.
GitLab Release
git remote add origin git@gitlab.com:your-group/api-error-logger.git
git push -u origin main
git tag 1.0.0
git push origin 1.0.0
Create a GitLab release from tag 1.0.0 and include the CHANGELOG entry.
Packagist Publication
- Push the package to GitLab.
- Ensure
composer.jsonuses the final package nameenuthu/api-error-logger. - Create and push tag
1.0.0. - Log in to Packagist and submit the GitLab repository URL.
- Configure GitLab/Packagist synchronization or webhook.
- Confirm Packagist lists version
1.0.0. - Install in another project:
composer require enuthu/api-error-logger:^1.0
Troubleshooting
- No emails are sent: confirm
email_notifications,email_severities,admin_emails, and the host mail transport. - Duplicate errors do not create new rows: this is expected; grouped fingerprints increment
occurrence_count. - Company code loses leading zeros: pass it as a string, for example
'011'. - Logger returns
null: logging may be disabled or the repository failed; check the fallback log. - Migration fails: confirm the configured database driver is one of
mysql,pgsql, orsqlite.