├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src └── Processor │ ├── AbstractProcessor.php │ ├── RedactEmailProcessor.php │ └── RedactIpProcessor.php └── tests ├── Processor ├── RedactEmailProcessorTest.php └── RedactIpProcessorTest.php └── bootstrap.php /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: ~ 5 | pull_request: ~ 6 | 7 | jobs: 8 | build-test: 9 | runs-on: ubuntu-latest 10 | 11 | name: "PHP ${{ matrix.php }}, Monolog ${{ matrix.monolog }}" 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | php: [ "7.4", "8.0", "8.1" ] 17 | monolog: [ "^1.17", "^2.0" ] 18 | 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v2 22 | 23 | - name: Set up PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: "${{ matrix.php }}" 27 | tools: composer 28 | 29 | - name: Restrict Monolog 30 | run: "composer require --no-install monolog/monolog=${{ matrix.monolog }}" 31 | 32 | - name: Install dependencies 33 | run: composer install --prefer-dist 34 | 35 | - name: Run PHPUnit 36 | run: vendor/bin/phpunit --colors=always 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .phpunit.result.cache 3 | vendor/ 4 | atlassian-ide-plugin.xml 5 | composer.lock 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Egeniq 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # monolog-gdpr [![Build Status](https://github.com/egeniq/monolog-gdpr/actions/workflows/ci.yaml/badge.svg?branch=develop)](https://github.com/egeniq/monolog-gdpr/actions/workflows/ci.yaml) 2 | Some Monolog processors that will help in relation to the security requirements under GDPR. 3 | These processors will replace data with their SHA-1 equivalent, allowing you still to search 4 | logs 5 | 6 | WARNING: These processors will json serialise your `$context`. This may cause some undesired side-effects. 7 | 8 | ## Installation 9 | Install the latest version with 10 | 11 | ``` 12 | $ composer require egeniq/monolog-gdpr 13 | ``` 14 | 15 | ## Salted hashes 16 | This library supports salted hashes using `processor->setSalt()`. To compute your hashed 17 | value you could use the following bash command: 18 | 19 | ```bash 20 | $ echo -n 'foo@bar.com' | openssl sha1 21 | ``` 22 | 23 | ## RedactEmailProcessor 24 | Replaces all e-mail addresses by their SHA-1 hash. 25 | 26 | Usage: 27 | 28 | ```PHP 29 | pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); 37 | 38 | $processor = new RedactEmailProcessor(); 39 | // optionally you may configure a salt: 40 | $processor->setSalt('h@tsefl@ts!'); 41 | $log->pushProcessor($processor); 42 | 43 | $log->log(Logger::DEBUG, 'This is a test for foo@bar.com', ['foo' => ['bar' => 'foo@bar.com']]); 44 | ``` 45 | 46 | ## RedactIpProcessor 47 | Replaces all ipv4 addresses by their SHA-1 hash. 48 | 49 | Usage: 50 | 51 | ```PHP 52 | pushHandler(new StreamHandler('path/to/your.log', Logger::WARNING)); 60 | 61 | $processor = new RedactIpProcessor(); 62 | 63 | // optionally you may configure a salt: 64 | $processor->setSalt('h@tsefl@ts!'); 65 | $log->pushProcessor($processor); 66 | 67 | $log->log(Logger::DEBUG, 'This is a test for 127.0.0.1', ['foo' => ['bar' => '127.0.0.1']]); 68 | ``` 69 | 70 | ## License 71 | Package is licensed under the MIT License - see the LICENSE file for details 72 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "egeniq/monolog-gdpr", 3 | "description": "Some Monolog processors that will help in relation to the security requirements under GDPR.", 4 | "keywords": ["log", "logger", "monolog", "processor", "gdpr", "avg"], 5 | "homepage": "https://github.com/egeniq/monolog-gdpr", 6 | "type": "library", 7 | "license": "MIT", 8 | "require": { 9 | "monolog/monolog": "^1.17|^2.0" 10 | }, 11 | "require-dev": { 12 | "phpunit/phpunit": "^8.5" 13 | }, 14 | "autoload": { 15 | "psr-4": {"Egeniq\\Monolog\\Gdpr\\": "src/"} 16 | }, 17 | "autoload-dev": { 18 | "psr-4": {"Egeniq\\Monolog\\Gdpr\\": "tests/"} 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | tests 12 | 13 | 14 | 15 | 16 | src 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Processor/AbstractProcessor.php: -------------------------------------------------------------------------------- 1 | salt = $salt; 20 | } 21 | 22 | /** 23 | * @param string $value 24 | * 25 | * @return string 26 | */ 27 | protected function getHashedValue(string $value) 28 | { 29 | return sha1($value . $this->salt); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Processor/RedactEmailProcessor.php: -------------------------------------------------------------------------------- 1 | getHashedValue($matches[0]); 21 | }, 22 | $serialised 23 | ); 24 | 25 | if ($filtered) { 26 | return json_decode($filtered, true); 27 | } 28 | 29 | return $record; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Processor/RedactIpProcessor.php: -------------------------------------------------------------------------------- 1 | getHashedValue($matches[0]); 21 | }, 22 | $serialised 23 | ); 24 | 25 | if ($filtered) { 26 | return json_decode($filtered, true); 27 | } 28 | 29 | return $record; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Processor/RedactEmailProcessorTest.php: -------------------------------------------------------------------------------- 1 | processor = new RedactEmailProcessor(); 29 | 30 | $this->handler = new TestHandler(); 31 | $this->logger = new Logger('test', [$this->handler]); 32 | $this->logger->pushProcessor($this->processor); 33 | 34 | parent::setUp(); 35 | } 36 | 37 | public function testEmailIsRedacted() 38 | { 39 | $this->logger->log(Logger::DEBUG, 'This is a test for foo@bar.com', ['foo' => ['bar' => 'foo@bar.com']]); 40 | $records = $this->handler->getRecords(); 41 | 42 | $this->assertEquals('This is a test for 823776525776c8f23a87176c59d25759da7a52c4', $records[0]['message']); 43 | $this->assertEquals( 44 | [ 45 | 'foo' => [ 46 | 'bar' => '823776525776c8f23a87176c59d25759da7a52c4' 47 | ] 48 | ], 49 | $records[0]['context'] 50 | ); 51 | } 52 | 53 | public function testEmailIsRedactedAndSalted() 54 | { 55 | $this->processor->setSalt('h@tsefl@ts!'); 56 | 57 | $this->logger->log(Logger::DEBUG, 'This is a test for foo@bar.com', ['foo' => ['bar' => 'foo@bar.com']]); 58 | $records = $this->handler->getRecords(); 59 | 60 | $this->assertEquals('This is a test for 04a0efd2d95b09e74db7add638e48931ce242867', $records[0]['message']); 61 | $this->assertEquals( 62 | [ 63 | 'foo' => [ 64 | 'bar' => '04a0efd2d95b09e74db7add638e48931ce242867' 65 | ] 66 | ], 67 | $records[0]['context'] 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Processor/RedactIpProcessorTest.php: -------------------------------------------------------------------------------- 1 | processor = new RedactIpProcessor(); 29 | 30 | $this->handler = new TestHandler(); 31 | $this->logger = new Logger('test', [$this->handler]); 32 | $this->logger->pushProcessor($this->processor); 33 | 34 | parent::setUp(); 35 | } 36 | 37 | public function testIpIsRedacted() 38 | { 39 | $this->logger->log(Logger::DEBUG, 'This is a test for 127.0.0.1', ['foo' => ['bar' => '127.0.0.1']]); 40 | $records = $this->handler->getRecords(); 41 | 42 | $this->assertEquals('This is a test for 4b84b15bff6ee5796152495a230e45e3d7e947d9', $records[0]['message']); 43 | $this->assertEquals( 44 | [ 45 | 'foo' => [ 46 | 'bar' => '4b84b15bff6ee5796152495a230e45e3d7e947d9' 47 | ] 48 | ], 49 | $records[0]['context'] 50 | ); 51 | } 52 | 53 | public function testIpIsRedactedAndSalted() 54 | { 55 | $this->processor->setSalt('h@tsefl@ts!'); 56 | 57 | $this->logger->log(Logger::DEBUG, 'This is a test for 127.0.0.1', ['foo' => ['bar' => '127.0.0.1']]); 58 | $records = $this->handler->getRecords(); 59 | 60 | $this->assertEquals('This is a test for d0821e9da4f151084b4ab5f7d000f3813a578e49', $records[0]['message']); 61 | $this->assertEquals( 62 | [ 63 | 'foo' => [ 64 | 'bar' => 'd0821e9da4f151084b4ab5f7d000f3813a578e49' 65 | ] 66 | ], 67 | $records[0]['context'] 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |