mirror of
https://github.com/ivuorinen/monolog-gdpr-filter.git
synced 2026-02-12 05:49:41 +00:00
feat: performance, integrations, advanced features (#2)
* feat: performance, integrations, advanced features * chore: fix linting problems * chore: suppressions and linting * chore(lint): pre-commit linting, fixes * feat: comprehensive input validation, security hardening, and regression testing - Add extensive input validation throughout codebase with proper error handling - Implement comprehensive security hardening with ReDoS protection and bounds checking - Add 3 new regression test suites covering critical bugs, security, and validation scenarios - Enhance rate limiting with memory management and configurable cleanup intervals - Update configuration security settings and improve Laravel integration - Fix TODO.md timestamps to reflect actual development timeline - Strengthen static analysis configuration and improve code quality standards * feat: configure static analysis tools and enhance development workflow - Complete configuration of Psalm, PHPStan, and Rector for harmonious static analysis. - Fix invalid configurations and tool conflicts that prevented proper code quality analysis. - Add comprehensive safe analysis script with interactive workflow, backup/restore capabilities, and dry-run modes. Update documentation with linting policy requiring issue resolution over suppression. - Clean completed items from TODO to focus on actionable improvements. - All static analysis tools now work together seamlessly to provide code quality insights without breaking existing functionality. * fix(test): update Invalid regex pattern expectation * chore: phpstan, psalm fixes * chore: phpstan, psalm fixes, more tests * chore: tooling tweaks, cleanup * chore: tweaks to get the tests pass * fix(lint): rector config tweaks and successful run * feat: refactoring, more tests, fixes, cleanup * chore: deduplication, use constants * chore: psalm fixes * chore: ignore phpstan deliberate errors in tests * chore: improve codebase, deduplicate code * fix: lint * chore: deduplication, codebase simplification, sonarqube fixes * fix: resolve SonarQube reliability rating issues Fix useless object instantiation warnings in test files by assigning instantiated objects to variables. This resolves the SonarQube reliability rating issue (was C, now targeting A). Changes: - tests/Strategies/MaskingStrategiesTest.php: Fix 3 instances - tests/Strategies/FieldPathMaskingStrategyTest.php: Fix 1 instance The tests use expectException() to verify that constructors throw exceptions for invalid input. SonarQube flagged standalone `new` statements as useless. Fixed by assigning to variables with explicit unset() and fail() calls. All tests pass (623/623) and static analysis tools pass. * fix: resolve more SonarQube detected issues * fix: resolve psalm detected issues * fix: resolve more SonarQube detected issues * fix: resolve psalm detected issues * fix: duplications * fix: resolve SonarQube reliability rating issues * fix: resolve psalm and phpstan detected issues
This commit is contained in:
258
examples/conditional-masking.php
Normal file
258
examples/conditional-masking.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
/**
|
||||
* Conditional Masking Examples
|
||||
*
|
||||
* This file demonstrates various ways to use conditional masking
|
||||
* to apply GDPR processing only when certain conditions are met.
|
||||
*/
|
||||
|
||||
use Ivuorinen\MonologGdprFilter\ConditionalRuleFactory;
|
||||
use Ivuorinen\MonologGdprFilter\GdprProcessor;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Level;
|
||||
use Monolog\Logger;
|
||||
use Monolog\LogRecord;
|
||||
|
||||
// Example 1: Level-based conditional masking
|
||||
// Only mask sensitive data in ERROR and CRITICAL logs
|
||||
echo "=== Example 1: Level-based Conditional Masking ===\n";
|
||||
|
||||
$levelBasedProcessor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
[],
|
||||
[
|
||||
'error_levels_only' => ConditionalRuleFactory::createLevelBasedRule(['Error', 'Critical'])
|
||||
]
|
||||
);
|
||||
|
||||
$logger = new Logger('example');
|
||||
$logger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$logger->pushProcessor($levelBasedProcessor);
|
||||
|
||||
$logger->info('User john@example.com logged in successfully'); // Email NOT masked
|
||||
$logger->error('Failed login attempt for admin@company.com'); // Email WILL be masked
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Example 2: Channel-based conditional masking
|
||||
// Only mask data in security and audit channels
|
||||
echo "=== Example 2: Channel-based Conditional Masking ===\n";
|
||||
|
||||
$channelBasedProcessor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
[],
|
||||
[
|
||||
'security_channels' => ConditionalRuleFactory::createChannelBasedRule(['security', 'audit'])
|
||||
]
|
||||
);
|
||||
|
||||
$securityLogger = new Logger('security');
|
||||
$securityLogger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$securityLogger->pushProcessor($channelBasedProcessor);
|
||||
|
||||
$appLogger = new Logger('application');
|
||||
$appLogger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$appLogger->pushProcessor($channelBasedProcessor);
|
||||
|
||||
$securityLogger->info('Security event: user@example.com accessed admin panel'); // WILL be masked
|
||||
$appLogger->info('Application event: user@example.com placed order'); // NOT masked
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Example 3: Context-based conditional masking
|
||||
// Only mask when specific fields are present in context
|
||||
echo "=== Example 3: Context-based Conditional Masking ===\n";
|
||||
|
||||
$contextBasedProcessor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
[],
|
||||
[
|
||||
'gdpr_consent_required' => ConditionalRuleFactory::createContextFieldRule('user.gdpr_consent')
|
||||
]
|
||||
);
|
||||
|
||||
$contextLogger = new Logger('context');
|
||||
$contextLogger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$contextLogger->pushProcessor($contextBasedProcessor);
|
||||
|
||||
// This will be masked because gdpr_consent field is present
|
||||
$contextLogger->info('User action performed', [
|
||||
'email' => 'user@example.com',
|
||||
'user' => ['id' => 123, 'gdpr_consent' => true]
|
||||
]);
|
||||
|
||||
// This will NOT be masked because gdpr_consent field is missing
|
||||
$contextLogger->info('System action performed', [
|
||||
'email' => 'system@example.com',
|
||||
'user' => ['id' => 1]
|
||||
]);
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Example 4: Environment-based conditional masking
|
||||
// Only mask in production environment
|
||||
echo "=== Example 4: Environment-based Conditional Masking ===\n";
|
||||
|
||||
$envBasedProcessor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
[],
|
||||
[
|
||||
'production_only' => ConditionalRuleFactory::createContextValueRule('env', 'production')
|
||||
]
|
||||
);
|
||||
|
||||
$envLogger = new Logger('env');
|
||||
$envLogger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$envLogger->pushProcessor($envBasedProcessor);
|
||||
|
||||
// This will be masked because env=production
|
||||
$envLogger->info('Production log entry', [
|
||||
'email' => 'prod@example.com',
|
||||
'env' => 'production'
|
||||
]);
|
||||
|
||||
// This will NOT be masked because env=development
|
||||
$envLogger->info('Development log entry', [
|
||||
'email' => 'dev@example.com',
|
||||
'env' => 'development'
|
||||
]);
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Example 5: Multiple conditional rules (AND logic)
|
||||
// Only mask when ALL conditions are met
|
||||
echo "=== Example 5: Multiple Conditional Rules ===\n";
|
||||
|
||||
$multiRuleProcessor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
[],
|
||||
[
|
||||
'error_level' => ConditionalRuleFactory::createLevelBasedRule(['Error', 'Critical']),
|
||||
'production_env' => ConditionalRuleFactory::createContextValueRule('env', 'production'),
|
||||
'security_channel' => ConditionalRuleFactory::createChannelBasedRule(['security'])
|
||||
]
|
||||
);
|
||||
|
||||
$multiLogger = new Logger('security');
|
||||
$multiLogger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$multiLogger->pushProcessor($multiRuleProcessor);
|
||||
|
||||
// This WILL be masked - all conditions met: Error level + production env + security channel
|
||||
$multiLogger->error('Security error in production', [
|
||||
'email' => 'admin@example.com',
|
||||
'env' => 'production'
|
||||
]);
|
||||
|
||||
// This will NOT be masked - wrong level (Info instead of Error)
|
||||
$multiLogger->info('Security info in production', [
|
||||
'email' => 'admin@example.com',
|
||||
'env' => 'production'
|
||||
]);
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Example 6: Custom conditional rule
|
||||
// Create a custom rule based on complex logic
|
||||
echo "=== Example 6: Custom Conditional Rule ===\n";
|
||||
|
||||
$customRule = function (LogRecord $record): bool {
|
||||
// Only mask for high-privilege users (user_id > 1000) during business hours
|
||||
$context = $record->context;
|
||||
$isHighPrivilegeUser = isset($context['user_id']) && $context['user_id'] > 1000;
|
||||
$isBusinessHours = (int)date('H') >= 9 && (int)date('H') <= 17;
|
||||
|
||||
return $isHighPrivilegeUser && $isBusinessHours;
|
||||
};
|
||||
|
||||
$customProcessor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
[],
|
||||
[
|
||||
'high_privilege_business_hours' => $customRule
|
||||
]
|
||||
);
|
||||
|
||||
$customLogger = new Logger('custom');
|
||||
$customLogger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$customLogger->pushProcessor($customProcessor);
|
||||
|
||||
// This will be masked if user_id > 1000 AND it's business hours
|
||||
$customLogger->info('High privilege user action', [
|
||||
'email' => 'admin@example.com',
|
||||
'user_id' => 1001,
|
||||
'action' => 'delete_user'
|
||||
]);
|
||||
|
||||
// This will NOT be masked (user_id <= 1000)
|
||||
$customLogger->info('Regular user action', [
|
||||
'email' => 'user@example.com',
|
||||
'user_id' => 500,
|
||||
'action' => 'view_profile'
|
||||
]);
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Example 7: Combining conditional masking with data type masking
|
||||
echo "=== Example 7: Conditional + Data Type Masking ===\n";
|
||||
|
||||
$combinedProcessor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
[],
|
||||
[],
|
||||
null,
|
||||
100,
|
||||
[
|
||||
'integer' => '***INT***',
|
||||
'string' => '***STRING***'
|
||||
],
|
||||
[
|
||||
'error_level' => ConditionalRuleFactory::createLevelBasedRule(['Error'])
|
||||
]
|
||||
);
|
||||
|
||||
$combinedLogger = new Logger('combined');
|
||||
$combinedLogger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$combinedLogger->pushProcessor($combinedProcessor);
|
||||
|
||||
// ERROR level: both regex patterns AND data type masking will be applied
|
||||
$combinedLogger->error('Error occurred', [
|
||||
'email' => 'error@example.com', // Will be masked by regex
|
||||
'user_id' => 12345, // Will be masked by data type rule
|
||||
'message' => 'Something went wrong' // Will be masked by data type rule
|
||||
]);
|
||||
|
||||
// INFO level: no masking will be applied due to conditional rule
|
||||
$combinedLogger->info('Info message', [
|
||||
'email' => 'info@example.com', // Will NOT be masked
|
||||
'user_id' => 67890, // Will NOT be masked
|
||||
'message' => 'Everything is fine' // Will NOT be masked
|
||||
]);
|
||||
|
||||
echo "\nConditional masking examples completed.\n";
|
||||
417
examples/laravel-integration.md
Normal file
417
examples/laravel-integration.md
Normal file
@@ -0,0 +1,417 @@
|
||||
# Laravel Integration Examples
|
||||
|
||||
This document provides comprehensive examples for integrating the Monolog GDPR Filter with Laravel applications.
|
||||
|
||||
## Installation and Setup
|
||||
|
||||
### 1. Install the Package
|
||||
|
||||
```bash
|
||||
composer require ivuorinen/monolog-gdpr-filter
|
||||
```
|
||||
|
||||
### 2. Register the Service Provider
|
||||
|
||||
Add the service provider to your `config/app.php`:
|
||||
|
||||
```php
|
||||
'providers' => [
|
||||
// Other providers...
|
||||
Ivuorinen\MonologGdprFilter\Laravel\GdprServiceProvider::class,
|
||||
],
|
||||
```
|
||||
|
||||
### 3. Add the Facade (Optional)
|
||||
|
||||
```php
|
||||
'aliases' => [
|
||||
// Other aliases...
|
||||
'Gdpr' => Ivuorinen\MonologGdprFilter\Laravel\Facades\Gdpr::class,
|
||||
],
|
||||
```
|
||||
|
||||
### 4. Publish the Configuration
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag=gdpr-config
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Basic Configuration (`config/gdpr.php`)
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
return [
|
||||
'auto_register' => true,
|
||||
'channels' => ['single', 'daily', 'stack'],
|
||||
|
||||
'field_paths' => [
|
||||
'user.email' => '', // Mask with regex
|
||||
'user.ssn' => GdprProcessor::removeField(),
|
||||
'payment.card_number' => GdprProcessor::replaceWith('[CARD]'),
|
||||
'request.password' => GdprProcessor::removeField(),
|
||||
],
|
||||
|
||||
'custom_callbacks' => [
|
||||
'user.ip' => fn($value) => hash('sha256', $value), // Hash IPs
|
||||
'user.name' => fn($value) => strtoupper($value), // Transform names
|
||||
],
|
||||
|
||||
'max_depth' => 100,
|
||||
|
||||
'audit_logging' => [
|
||||
'enabled' => env('GDPR_AUDIT_ENABLED', false),
|
||||
'channel' => 'gdpr-audit',
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Ivuorinen\MonologGdprFilter\GdprProcessor;
|
||||
|
||||
return [
|
||||
'patterns' => [
|
||||
// Custom patterns for your application
|
||||
'/\binternal-id-\d+\b/' => '***INTERNAL***',
|
||||
'/\bcustomer-\d{6}\b/' => '***CUSTOMER***',
|
||||
],
|
||||
|
||||
'field_paths' => [
|
||||
// User data
|
||||
'user.email' => '',
|
||||
'user.phone' => GdprProcessor::replaceWith('[PHONE]'),
|
||||
'user.address' => GdprProcessor::removeField(),
|
||||
|
||||
// Payment data
|
||||
'payment.card_number' => GdprProcessor::replaceWith('[CARD]'),
|
||||
'payment.cvv' => GdprProcessor::removeField(),
|
||||
'payment.account_number' => GdprProcessor::replaceWith('[ACCOUNT]'),
|
||||
|
||||
// Request data
|
||||
'request.password' => GdprProcessor::removeField(),
|
||||
'request.token' => GdprProcessor::replaceWith('[TOKEN]'),
|
||||
'headers.authorization' => GdprProcessor::replaceWith('[AUTH]'),
|
||||
],
|
||||
|
||||
'custom_callbacks' => [
|
||||
// Hash sensitive identifiers
|
||||
'user.ip' => fn($ip) => 'ip_' . substr(hash('sha256', $ip), 0, 8),
|
||||
'session.id' => fn($id) => 'sess_' . substr(hash('sha256', $id), 0, 12),
|
||||
|
||||
// Mask parts of identifiers
|
||||
'user.username' => function($username) {
|
||||
if (strlen($username) <= 3) return '***';
|
||||
return substr($username, 0, 2) . str_repeat('*', strlen($username) - 2);
|
||||
},
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### 1. Using the Facade
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Ivuorinen\MonologGdprFilter\Laravel\Facades\Gdpr;
|
||||
|
||||
// Mask a message directly
|
||||
$maskedMessage = Gdpr::regExpMessage('Contact john.doe@example.com for details');
|
||||
// Result: "Contact ***EMAIL*** for details"
|
||||
|
||||
// Get default patterns
|
||||
$patterns = Gdpr::getDefaultPatterns();
|
||||
|
||||
// Test pattern validation
|
||||
try {
|
||||
Gdpr::validatePatterns(['/\btest\b/' => '***TEST***']);
|
||||
echo "Pattern is valid!";
|
||||
} catch (InvalidArgumentException $e) {
|
||||
echo "Pattern error: " . $e->getMessage();
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Manual Integration with Specific Channels
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// In a service provider or middleware
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Ivuorinen\MonologGdprFilter\GdprProcessor;
|
||||
|
||||
$processor = app('gdpr.processor');
|
||||
|
||||
// Add to specific channel
|
||||
Log::channel('api')->pushProcessor($processor);
|
||||
Log::channel('audit')->pushProcessor($processor);
|
||||
```
|
||||
|
||||
### 3. Custom Logging with GDPR Protection
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UserService
|
||||
{
|
||||
public function createUser(array $userData)
|
||||
{
|
||||
// This will automatically be GDPR filtered
|
||||
Log::info('Creating user', [
|
||||
'user_data' => $userData, // Contains email, phone, etc.
|
||||
'request_ip' => request()->ip(),
|
||||
'timestamp' => now(),
|
||||
]);
|
||||
|
||||
// User creation logic...
|
||||
}
|
||||
|
||||
public function loginAttempt(string $email, bool $success)
|
||||
{
|
||||
Log::info('Login attempt', [
|
||||
'email' => $email, // Will be masked
|
||||
'success' => $success,
|
||||
'ip' => request()->ip(), // Will be hashed if configured
|
||||
'user_agent' => request()->userAgent(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Artisan Commands
|
||||
|
||||
### Test Regex Patterns
|
||||
|
||||
```bash
|
||||
# Test a pattern against sample data
|
||||
php artisan gdpr:test-pattern '/\b\d{3}-\d{2}-\d{4}\b/' '***SSN***' '123-45-6789'
|
||||
|
||||
# With validation
|
||||
php artisan gdpr:test-pattern '/\b\d{16}\b/' '***CARD***' '4111111111111111' --validate
|
||||
```
|
||||
|
||||
### Debug Configuration
|
||||
|
||||
```bash
|
||||
# Show current configuration
|
||||
php artisan gdpr:debug --show-config
|
||||
|
||||
# Show all patterns
|
||||
php artisan gdpr:debug --show-patterns
|
||||
|
||||
# Test with sample data
|
||||
php artisan gdpr:debug \
|
||||
--test-data='{
|
||||
"message":"Email: test@example.com", "context":{"user":{"email":"user@example.com"}}
|
||||
}'
|
||||
```
|
||||
|
||||
## Middleware Integration
|
||||
|
||||
### HTTP Request/Response Logging
|
||||
|
||||
Register the middleware in `app/Http/Kernel.php`:
|
||||
|
||||
```php
|
||||
protected $middleware = [
|
||||
// Other middleware...
|
||||
\Ivuorinen\MonologGdprFilter\Laravel\Middleware\GdprLogMiddleware::class,
|
||||
];
|
||||
```
|
||||
|
||||
Or apply to specific routes:
|
||||
|
||||
```php
|
||||
Route::middleware(['gdpr.log'])->group(function () {
|
||||
Route::post('/api/users', [UserController::class, 'store']);
|
||||
Route::put('/api/users/{id}', [UserController::class, 'update']);
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Middleware Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Ivuorinen\MonologGdprFilter\Laravel\Facades\Gdpr;
|
||||
|
||||
class ApiRequestLogger
|
||||
{
|
||||
public function handle(Request $request, Closure $next)
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
// Log request
|
||||
Log::info('API Request', [
|
||||
'method' => $request->method(),
|
||||
'url' => $request->fullUrl(),
|
||||
'headers' => $request->headers->all(),
|
||||
'body' => $request->all(),
|
||||
]);
|
||||
|
||||
$response = $next($request);
|
||||
|
||||
// Log response
|
||||
Log::info('API Response', [
|
||||
'status' => $response->getStatusCode(),
|
||||
'duration' => round((microtime(true) - $startTime) * 1000, 2),
|
||||
'memory' => memory_get_peak_usage(true),
|
||||
]);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Unit Testing with GDPR
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Ivuorinen\MonologGdprFilter\Laravel\Facades\Gdpr;
|
||||
|
||||
class GdprTest extends TestCase
|
||||
{
|
||||
public function test_email_masking()
|
||||
{
|
||||
$result = Gdpr::regExpMessage('Contact john@example.com');
|
||||
$this->assertStringContains('***EMAIL***', $result);
|
||||
}
|
||||
|
||||
public function test_custom_pattern()
|
||||
{
|
||||
$processor = new GdprProcessor([
|
||||
'/\bcustomer-\d+\b/' => '***CUSTOMER***'
|
||||
]);
|
||||
|
||||
$result = $processor->regExpMessage('Order for customer-12345');
|
||||
$this->assertEquals('Order for ***CUSTOMER***', $result);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Integration Testing
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Tests\TestCase;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class GdprLoggingTest extends TestCase
|
||||
{
|
||||
public function test_user_creation_logging()
|
||||
{
|
||||
Log::shouldReceive('info')
|
||||
->once()
|
||||
->with('Creating user', \Mockery::on(function ($context) {
|
||||
// Verify that email is masked
|
||||
return str_contains($context['user_data']['email'], '***EMAIL***');
|
||||
}));
|
||||
|
||||
$response = $this->postJson('/api/users', [
|
||||
'name' => 'John Doe',
|
||||
'email' => 'john@example.com',
|
||||
'phone' => '+1234567890',
|
||||
]);
|
||||
|
||||
$response->assertStatus(201);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Optimize for Large Applications
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// config/gdpr.php
|
||||
return [
|
||||
'performance' => [
|
||||
'chunk_size' => 500, // Smaller chunks for memory-constrained environments
|
||||
'garbage_collection_threshold' => 5000, // More frequent GC
|
||||
],
|
||||
|
||||
// Use more specific patterns to reduce processing time
|
||||
'patterns' => [
|
||||
'/\b[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\b/' => '***EMAIL***',
|
||||
'/\b\d{3}-\d{2}-\d{4}\b/' => '***SSN***',
|
||||
// Avoid overly broad patterns
|
||||
],
|
||||
|
||||
// Prefer field paths over regex for known locations
|
||||
'field_paths' => [
|
||||
'user.email' => '',
|
||||
'request.email' => '',
|
||||
'customer.email_address' => '',
|
||||
],
|
||||
];
|
||||
```
|
||||
|
||||
### Channel-Specific Configuration
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Apply GDPR only to specific channels
|
||||
'channels' => [
|
||||
'single', // Local development
|
||||
'daily', // Production file logs
|
||||
'database', // Database logging
|
||||
// Skip 'stderr' for performance-critical error logging
|
||||
],
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **GDPR not working**: Check if auto_register is true and channels are correctly configured
|
||||
2. **Performance issues**: Reduce pattern count, use field_paths instead of regex
|
||||
3. **Over-masking**: Make patterns more specific, check pattern order
|
||||
4. **Memory issues**: Reduce chunk_size and garbage_collection_threshold
|
||||
|
||||
### Debug Steps
|
||||
|
||||
```bash
|
||||
# Check configuration
|
||||
php artisan gdpr:debug --show-config
|
||||
|
||||
# Test patterns
|
||||
php artisan gdpr:test-pattern '/your-pattern/' '***MASKED***' 'test-string'
|
||||
|
||||
# View current patterns
|
||||
php artisan gdpr:debug --show-patterns
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Use field paths over regex** when you know the exact location of sensitive data
|
||||
2. **Test patterns thoroughly** before deploying to production
|
||||
3. **Monitor performance** with large datasets
|
||||
4. **Use audit logging** for compliance requirements
|
||||
5. **Regularly review patterns** to ensure they're not over-masking
|
||||
6. **Consider data retention** policies for logged data
|
||||
172
examples/rate-limiting.php
Normal file
172
examples/rate-limiting.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Rate Limiting for Audit Logging Examples
|
||||
*
|
||||
* This file demonstrates how to use rate limiting to prevent
|
||||
* audit log flooding while maintaining system performance.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
use Ivuorinen\MonologGdprFilter\GdprProcessor;
|
||||
use Ivuorinen\MonologGdprFilter\RateLimitedAuditLogger;
|
||||
use Monolog\Logger;
|
||||
use Monolog\LogRecord;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Level;
|
||||
|
||||
// Example 1: Basic Rate-Limited Audit Logging
|
||||
echo "=== Example 1: Basic Rate-Limited Audit Logging ===\n";
|
||||
|
||||
$auditLogs = [];
|
||||
$baseAuditLogger = function (string $path, mixed $original, mixed $masked) use (&$auditLogs): void {
|
||||
$auditLogs[] = [
|
||||
'timestamp' => date('Y-m-d H:i:s'),
|
||||
'path' => $path,
|
||||
'original' => $original,
|
||||
'masked' => $masked
|
||||
];
|
||||
echo sprintf('AUDIT: %s - %s -> %s%s', $path, $original, $masked, PHP_EOL);
|
||||
};
|
||||
|
||||
// Wrap with rate limiting (100 per minute by default)
|
||||
$rateLimitedLogger = new RateLimitedAuditLogger($baseAuditLogger, 5, 60); // 5 per minute for demo
|
||||
|
||||
$processor = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
['user.email' => 'masked@example.com'],
|
||||
[],
|
||||
$rateLimitedLogger
|
||||
);
|
||||
|
||||
$logger = new Logger('rate-limited');
|
||||
$logger->pushHandler(new StreamHandler('php://stdout', Level::Debug));
|
||||
$logger->pushProcessor($processor);
|
||||
|
||||
// Simulate high-volume logging that would exceed rate limits
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$logger->info('User activity', [
|
||||
'user' => ['email' => sprintf('user%d@example.com', $i)],
|
||||
'action' => 'login'
|
||||
]);
|
||||
}
|
||||
|
||||
echo "\nTotal audit logs: " . count($auditLogs) . "\n";
|
||||
echo "Expected: 5 regular logs + rate limit warnings\n\n";
|
||||
|
||||
// Example 2: Using Predefined Rate Limiting Profiles
|
||||
echo "=== Example 2: Rate Limiting Profiles ===\n";
|
||||
|
||||
$auditLogs2 = [];
|
||||
$baseLogger2 = GdprProcessor::createArrayAuditLogger($auditLogs2, false);
|
||||
|
||||
// Available profiles: 'strict', 'default', 'relaxed', 'testing'
|
||||
$strictLogger = RateLimitedAuditLogger::create($baseLogger2, 'strict'); // 50/min
|
||||
$relaxedLogger = RateLimitedAuditLogger::create($baseLogger2, 'relaxed'); // 200/min
|
||||
$testingLogger = RateLimitedAuditLogger::create($baseLogger2, 'testing'); // 1000/min
|
||||
|
||||
echo "Strict profile: " . ($strictLogger->isOperationAllowed('general_operations')
|
||||
? 'Available' : 'Rate limited') . "\n";
|
||||
echo "Relaxed profile: " . ($relaxedLogger->isOperationAllowed('general_operations')
|
||||
? 'Available' : 'Rate limited') . "\n";
|
||||
echo "Testing profile: " . ($testingLogger->isOperationAllowed('general_operations')
|
||||
? 'Available' : 'Rate limited') . "\n\n";
|
||||
|
||||
// Example 3: Using GdprProcessor Helper Methods
|
||||
echo "=== Example 3: GdprProcessor Helper Methods ===\n";
|
||||
|
||||
$auditLogs3 = [];
|
||||
// Create rate-limited logger using GdprProcessor helper
|
||||
$rateLimitedAuditLogger = GdprProcessor::createRateLimitedAuditLogger(
|
||||
GdprProcessor::createArrayAuditLogger($auditLogs3, false),
|
||||
'default'
|
||||
);
|
||||
|
||||
$processor3 = new GdprProcessor(
|
||||
['/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/' => '***EMAIL***'],
|
||||
['sensitive_data' => '***REDACTED***'],
|
||||
[],
|
||||
$rateLimitedAuditLogger
|
||||
);
|
||||
|
||||
// Process some logs
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$logRecord = new LogRecord(
|
||||
new DateTimeImmutable(),
|
||||
'app',
|
||||
Level::Info,
|
||||
sprintf('Processing user%d@example.com', $i),
|
||||
['sensitive_data' => 'secret_value_' . $i]
|
||||
);
|
||||
|
||||
$result = $processor3($logRecord);
|
||||
echo "Processed: " . $result->message . "\n";
|
||||
}
|
||||
|
||||
echo "Audit logs generated: " . count($auditLogs3) . "\n\n";
|
||||
|
||||
// Example 4: Rate Limit Statistics and Monitoring
|
||||
echo "=== Example 4: Rate Limit Statistics ===\n";
|
||||
|
||||
$rateLimitedLogger4 = new RateLimitedAuditLogger($baseAuditLogger, 10, 60);
|
||||
|
||||
// Generate some activity
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$rateLimitedLogger4('test_operation', 'value_' . $i, 'masked_' . $i);
|
||||
}
|
||||
|
||||
// Check statistics
|
||||
$stats = $rateLimitedLogger4->getRateLimitStats();
|
||||
echo "Rate Limit Statistics:\n";
|
||||
foreach ($stats as $operationType => $stat) {
|
||||
if ($stat['current_requests'] > 0) {
|
||||
echo " {$operationType}:\n";
|
||||
echo sprintf(' Current requests: %d%s', $stat['current_requests'], PHP_EOL);
|
||||
echo sprintf(' Remaining requests: %d%s', $stat['remaining_requests'], PHP_EOL);
|
||||
echo " Time until reset: {$stat['time_until_reset']} seconds\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
|
||||
// Example 5: Different Operation Types
|
||||
echo "=== Example 5: Operation Type Classification ===\n";
|
||||
|
||||
$rateLimitedLogger5 = new RateLimitedAuditLogger($baseAuditLogger, 2, 60); // Very restrictive
|
||||
|
||||
echo "Testing different operation types (2 per minute limit):\n";
|
||||
|
||||
// These will be classified into different operation types
|
||||
$rateLimitedLogger5('json_masked', '{"key": "value"}', '{"key": "***MASKED***"}');
|
||||
$rateLimitedLogger5('conditional_skip', 'skip_reason', 'Level not matched');
|
||||
$rateLimitedLogger5('regex_error', '/invalid[/', 'Pattern compilation failed');
|
||||
$rateLimitedLogger5('preg_replace_error', 'input', 'PCRE error occurred');
|
||||
|
||||
// Try to exceed limits for each type
|
||||
echo "\nTesting rate limiting per operation type:\n";
|
||||
$rateLimitedLogger5('json_encode_error', 'data', 'JSON encoding failed'); // json_operations
|
||||
$rateLimitedLogger5('json_decode_error', 'data', 'JSON decoding failed'); // json_operations (should be limited)
|
||||
$rateLimitedLogger5('conditional_error', 'rule', 'Rule evaluation failed'); // conditional_operations
|
||||
$rateLimitedLogger5('regex_validation', 'pattern', 'Pattern is invalid'); // regex_operations
|
||||
|
||||
echo "\nOperation type stats:\n";
|
||||
$stats5 = $rateLimitedLogger5->getRateLimitStats();
|
||||
foreach ($stats5 as $type => $stat) {
|
||||
if ($stat['current_requests'] > 0) {
|
||||
$current = $stat['current_requests'];
|
||||
$all = $stat['current_requests'] + $stat['remaining_requests'];
|
||||
echo " {$type}: {$current}/{$all} used\n";
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n=== Rate Limiting Examples Completed ===\n";
|
||||
echo "\nKey Benefits:\n";
|
||||
echo "• Prevents audit log flooding during high-volume operations\n";
|
||||
echo "• Maintains system performance by limiting resource usage\n";
|
||||
echo "• Provides configurable rate limits for different environments\n";
|
||||
echo "• Separate rate limits for different operation types\n";
|
||||
echo "• Built-in statistics and monitoring capabilities\n";
|
||||
echo "• Graceful degradation with rate limit warnings\n";
|
||||
Reference in New Issue
Block a user