mirror of
https://github.com/ivuorinen/monolog-gdpr-filter.git
synced 2026-02-16 04:51:02 +00:00
feat: add advanced architecture, documentation, and coverage improvements (#65)
* fix(style): resolve PHPCS line-length warnings in source files * fix(style): resolve PHPCS line-length warnings in test files * feat(audit): add structured audit logging with ErrorContext and AuditContext - ErrorContext: standardized error information with sensitive data sanitization - AuditContext: structured context for audit entries with operation types - StructuredAuditLogger: enhanced audit logger wrapper with timing support * feat(recovery): add recovery mechanism for failed masking operations - FailureMode enum: FAIL_OPEN, FAIL_CLOSED, FAIL_SAFE modes - RecoveryStrategy interface and RecoveryResult value object - RetryStrategy: exponential backoff with configurable attempts - FallbackMaskStrategy: type-aware fallback values * feat(strategies): add CallbackMaskingStrategy for custom masking logic - Wraps custom callbacks as MaskingStrategy implementations - Factory methods: constant(), hash(), partial() for common use cases - Supports exact match and prefix match for field paths * docs: add framework integration guides and examples - symfony-integration.md: Symfony service configuration and Monolog setup - psr3-decorator.md: PSR-3 logger decorator pattern implementation - framework-examples.md: CakePHP, CodeIgniter 4, Laminas, Yii2, PSR-15 - docker-development.md: Docker development environment guide * chore(docker): add Docker development environment - Dockerfile: PHP 8.2-cli-alpine with Xdebug for coverage - docker-compose.yml: development services with volume mounts * feat(demo): add interactive GDPR pattern tester playground - PatternTester.php: pattern testing utility with strategy support - index.php: web API endpoint with JSON response handling - playground.html: interactive web interface for testing patterns * docs(todo): update with completed medium priority items - Mark all PHPCS warnings as fixed (81 → 0) - Document new Audit and Recovery features - Update test count to 1,068 tests with 2,953 assertions - Move remaining items to low priority * feat: add advanced architecture, documentation, and coverage improvements - Add architecture improvements: - ArrayAccessorInterface and DotArrayAccessor for decoupled array access - MaskingOrchestrator for single-responsibility masking coordination - GdprProcessorBuilder for fluent configuration - MaskingPluginInterface and AbstractMaskingPlugin for plugin architecture - PluginAwareProcessor for plugin hook execution - AuditLoggerFactory for instance-based audit logger creation - Add advanced features: - SerializedDataProcessor for handling print_r/var_export/serialize output - KAnonymizer with GeneralizationStrategy for GDPR k-anonymity - RetentionPolicy for configurable data retention periods - StreamingProcessor for memory-efficient large log processing - Add comprehensive documentation: - docs/performance-tuning.md - benchmarking, optimization, caching - docs/troubleshooting.md - common issues and solutions - docs/logging-integrations.md - ELK, Graylog, Datadog, etc. - docs/plugin-development.md - complete plugin development guide - Improve test coverage (84.41% → 85.07%): - ConditionalRuleFactoryInstanceTest (100% coverage) - GdprProcessorBuilderEdgeCasesTest (100% coverage) - StrategyEdgeCasesTest for ReDoS detection and type parsing - 78 new tests, 119 new assertions - Update TODO.md with current statistics: - 141 PHP files, 1,346 tests, 85.07% line coverage * chore: tests, update actions, sonarcloud issues * chore: rector * fix: more sonarcloud fixes * chore: more fixes * refactor: copilot review fix * chore: rector
This commit is contained in:
264
docs/symfony-integration.md
Normal file
264
docs/symfony-integration.md
Normal file
@@ -0,0 +1,264 @@
|
||||
# Symfony Integration Guide
|
||||
|
||||
This guide explains how to integrate the Monolog GDPR Filter with Symfony applications.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
composer require ivuorinen/monolog-gdpr-filter
|
||||
```
|
||||
|
||||
## Basic Service Configuration
|
||||
|
||||
Add the GDPR processor as a service in `config/services.yaml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
App\Logging\GdprProcessor:
|
||||
class: Ivuorinen\MonologGdprFilter\GdprProcessor
|
||||
arguments:
|
||||
$patterns: '%gdpr.patterns%'
|
||||
$fieldPaths: '%gdpr.field_paths%'
|
||||
$customCallbacks: []
|
||||
$auditLogger: null
|
||||
$maxDepth: 100
|
||||
$dataTypeMasks: []
|
||||
$conditionalRules: []
|
||||
```
|
||||
|
||||
## Parameters Configuration
|
||||
|
||||
Define GDPR patterns in `config/services.yaml` or a dedicated parameters file:
|
||||
|
||||
```yaml
|
||||
parameters:
|
||||
gdpr.patterns:
|
||||
'/\b\d{3}-\d{2}-\d{4}\b/': '***-**-****' # US SSN
|
||||
'/\b[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}\b/': '****' # IBAN
|
||||
'/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/': '[email]' # Email
|
||||
'/\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/': '****-****-****-****' # Credit Card
|
||||
|
||||
gdpr.field_paths:
|
||||
'user.password': '***REMOVED***'
|
||||
'user.ssn': '***-**-****'
|
||||
'payment.card_number': '****-****-****-****'
|
||||
```
|
||||
|
||||
## Monolog Handler Configuration
|
||||
|
||||
Configure Monolog to use the GDPR processor in `config/packages/monolog.yaml`:
|
||||
|
||||
```yaml
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
channels: ["!event"]
|
||||
formatter: monolog.formatter.json
|
||||
processor: ['@App\Logging\GdprProcessor']
|
||||
|
||||
# For production with file rotation
|
||||
production:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: info
|
||||
max_files: 14
|
||||
processor: ['@App\Logging\GdprProcessor']
|
||||
```
|
||||
|
||||
## Environment-Specific Configuration
|
||||
|
||||
Create environment-specific configurations:
|
||||
|
||||
### config/packages/dev/monolog.yaml
|
||||
|
||||
```yaml
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: stream
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: debug
|
||||
# In dev, you might want less aggressive masking
|
||||
```
|
||||
|
||||
### config/packages/prod/monolog.yaml
|
||||
|
||||
```yaml
|
||||
monolog:
|
||||
handlers:
|
||||
main:
|
||||
type: fingers_crossed
|
||||
action_level: error
|
||||
handler: nested
|
||||
excluded_http_codes: [404, 405]
|
||||
buffer_size: 50
|
||||
|
||||
nested:
|
||||
type: rotating_file
|
||||
path: "%kernel.logs_dir%/%kernel.environment%.log"
|
||||
level: info
|
||||
max_files: 14
|
||||
processor: ['@App\Logging\GdprProcessor']
|
||||
```
|
||||
|
||||
## Advanced Configuration with Audit Logging
|
||||
|
||||
Enable audit logging for compliance tracking:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
App\Logging\AuditLogger:
|
||||
class: Ivuorinen\MonologGdprFilter\RateLimitedAuditLogger
|
||||
arguments:
|
||||
$auditLogger: '@App\Logging\AuditCallback'
|
||||
$maxRequestsPerMinute: 100
|
||||
$windowSeconds: 60
|
||||
|
||||
App\Logging\AuditCallback:
|
||||
class: Closure
|
||||
factory: ['App\Logging\AuditCallbackFactory', 'create']
|
||||
arguments:
|
||||
$logger: '@monolog.logger.audit'
|
||||
|
||||
App\Logging\GdprProcessor:
|
||||
class: Ivuorinen\MonologGdprFilter\GdprProcessor
|
||||
arguments:
|
||||
$patterns: '%gdpr.patterns%'
|
||||
$fieldPaths: '%gdpr.field_paths%'
|
||||
$auditLogger: '@App\Logging\AuditLogger'
|
||||
```
|
||||
|
||||
Create the factory class:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// src/Logging/AuditCallbackFactory.php
|
||||
|
||||
namespace App\Logging;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class AuditCallbackFactory
|
||||
{
|
||||
public static function create(LoggerInterface $logger): callable
|
||||
{
|
||||
return function (string $path, mixed $original, mixed $masked) use ($logger): void {
|
||||
$logger->info('GDPR masking applied', [
|
||||
'path' => $path,
|
||||
'original_type' => gettype($original),
|
||||
'masked_preview' => substr((string) $masked, 0, 20) . '...',
|
||||
]);
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Conditional Masking by Environment
|
||||
|
||||
Apply different masking rules based on log level or channel:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
App\Logging\ConditionalRuleFactory:
|
||||
class: App\Logging\ConditionalRuleFactory
|
||||
|
||||
App\Logging\GdprProcessor:
|
||||
class: Ivuorinen\MonologGdprFilter\GdprProcessor
|
||||
arguments:
|
||||
$conditionalRules:
|
||||
error_only: '@=service("App\\Logging\\ConditionalRuleFactory").createErrorOnlyRule()'
|
||||
```
|
||||
|
||||
```php
|
||||
<?php
|
||||
// src/Logging/ConditionalRuleFactory.php
|
||||
|
||||
namespace App\Logging;
|
||||
|
||||
use Monolog\Level;
|
||||
use Monolog\LogRecord;
|
||||
|
||||
class ConditionalRuleFactory
|
||||
{
|
||||
public function createErrorOnlyRule(): callable
|
||||
{
|
||||
return fn(LogRecord $record): bool =>
|
||||
$record->level->value >= Level::Error->value;
|
||||
}
|
||||
|
||||
public function createChannelRule(array $channels): callable
|
||||
{
|
||||
return fn(LogRecord $record): bool =>
|
||||
in_array($record->channel, $channels, true);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing in Symfony
|
||||
|
||||
Create a test to verify GDPR filtering works:
|
||||
|
||||
```php
|
||||
<?php
|
||||
// tests/Logging/GdprProcessorTest.php
|
||||
|
||||
namespace App\Tests\Logging;
|
||||
|
||||
use Ivuorinen\MonologGdprFilter\GdprProcessor;
|
||||
use Monolog\Level;
|
||||
use Monolog\LogRecord;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use DateTimeImmutable;
|
||||
|
||||
class GdprProcessorTest extends TestCase
|
||||
{
|
||||
public function testEmailMasking(): void
|
||||
{
|
||||
$processor = new GdprProcessor([
|
||||
'/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/' => '[email]',
|
||||
]);
|
||||
|
||||
$record = new LogRecord(
|
||||
datetime: new DateTimeImmutable(),
|
||||
channel: 'test',
|
||||
level: Level::Info,
|
||||
message: 'User logged in: user@example.com',
|
||||
context: []
|
||||
);
|
||||
|
||||
$result = $processor($record);
|
||||
|
||||
$this->assertStringContainsString('[email]', $result->message);
|
||||
$this->assertStringNotContainsString('user@example.com', $result->message);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Patterns Not Matching
|
||||
|
||||
1. Verify regex patterns are valid: `preg_match('/your-pattern/', 'test-string')`
|
||||
2. Check pattern escaping in YAML (may need quotes)
|
||||
3. Enable debug mode to see which patterns are applied
|
||||
|
||||
### Performance Issues
|
||||
|
||||
1. Use the rate-limited audit logger
|
||||
2. Consider caching pattern validation results
|
||||
3. Profile with Symfony profiler
|
||||
|
||||
### Memory Issues
|
||||
|
||||
1. Set appropriate `maxDepth` to prevent deep recursion
|
||||
2. Monitor rate limiter statistics
|
||||
3. Use cleanup intervals for long-running processes
|
||||
|
||||
## See Also
|
||||
|
||||
- [PSR-3 Decorator Guide](psr3-decorator.md)
|
||||
- [Framework Examples](framework-examples.md)
|
||||
- [Docker Development](docker-development.md)
|
||||
Reference in New Issue
Block a user