Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Log filtered mails #3

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ This package provides a customizable middleware pipeline for email messages, all
- Modify email content, set headers, add attachments, or perform any email transformation needed.
- Middleware can inspect emails, log information, or integrate with other services.

- **Advanced Logging Options:**
- Configure logging to use custom channels.
- Set custom log levels (e.g., 'debug', 'info', 'error', etc.).
- Enable mail middleware to add individual log messages during email processing.
- Choose whether to include middleware logs, email message headers or message bodies in the logs.

> **Important Note:**
>
> This package utilizes Laravel's `MessageSending` event to inspect and modify outgoing emails. If your application has custom listeners or modifications affecting this event, please thoroughly test the package to ensure it integrates seamlessly and maintains the correct filtering functionality.
Expand Down Expand Up @@ -67,6 +73,11 @@ MAIL_ALLOWLIST_ALLOWED_EMAILS="[email protected];[email protected]"
MAIL_ALLOWLIST_GLOBAL_TO="[email protected];[email protected]"
MAIL_ALLOWLIST_GLOBAL_CC="[email protected];[email protected]"
MAIL_ALLOWLIST_GLOBAL_BCC="[email protected];[email protected]"

# Configure logging
MAIL_ALLOWLIST_LOG_ENABLED=true
MAIL_ALLOWLIST_LOG_CHANNEL=stack # optional, defaults to Laravel's logging.default
MAIL_ALLOWLIST_LOG_LEVEL=error # optional, defaults to info
```

### Customizing the Middleware Pipeline
Expand Down Expand Up @@ -135,6 +146,76 @@ Then add it to your middleware pipeline. This can be done as a class-string whic
],
```

### Customizing the Logging Behavior

You can control most of the logging behavior from environment variables or the configuration file. For more advanced use cases, you might want to have full control over how log messages are generated and where they are sent. You can achieve this by binding your own implementations of the log content generation action and/or the logging action itself.

#### Customizing the log message content

Create a new class that implements `GenerateLogMessageContract` to define how log messages are generated:

```php
use TobMoeller\LaravelMailAllowlist\Actions\Logs\GenerateLogMessageContract;
use TobMoeller\LaravelMailAllowlist\Facades\LaravelMailAllowlist;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\MessageContext;

class CustomLogMessage implements GenerateLogMessageContract
{
public function generate(MessageContext $messageContext): string
{
// Generate your own log message
}
}
```

#### Customizing the log message content

Create a new class that implements `LogMessageContract` to define how log messages are handled:

```php
use TobMoeller\LaravelMailAllowlist\Actions\Logs\LogMessageContract;
use TobMoeller\LaravelMailAllowlist\Facades\LaravelMailAllowlist;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\MessageContext;

class CustomMessageLogging implements LogMessageContract
{
/**
* Optional:
* Inject the message generator into your class to use the default
* message generation (is resolved by the service container)
*/
public function __construct(
public GenerateLogMessageContract $generateLogMessage
) {}

public function log(MessageContext $messageContext): void
{
// Handle logging yourself
}
}
```

#### Binding Custom Implementations

To instruct Laravel to use your custom classes, you need to bind them in your application's service container. This is typically done in a service provider like `App\Providers\AppServiceProvider`.

```php
use TobMoeller\LaravelMailAllowlist\Actions\Logs\GenerateLogMessageContract;
use TobMoeller\LaravelMailAllowlist\Actions\Logs\LogMessageContract;

class AppServiceProvider extends ServiceProvider
{
public function register()
{
// Bind the custom log message generator
$this->app->bind(GenerateLogMessageContract::class, CustomLogMessage::class);

// Bind the custom log handler
$this->app->bind(LogMessageContract::class, CustomMessageLogging::class);
}
}
```

## Testing

```bash
Expand Down
65 changes: 55 additions & 10 deletions config/mail-allowlist.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use Psr\Log\LogLevel;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\Addresses\AddGlobalBcc;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\Addresses\AddGlobalCc;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\Addresses\AddGlobalTo;
Expand All @@ -9,15 +10,15 @@
use TobMoeller\LaravelMailAllowlist\MailMiddleware\Addresses\ToFilter;

return [
/**
* Enables the mail allowlist
/*
* Enables the mail allowlist.
*/
'enabled' => env('MAIL_ALLOWLIST_ENABLED', false),

/**
/*
* Define the mail middleware every message should be passed through.
* Can be either a class-string or an instance. Class-strings will
* be instantiated through Laravel's service container
* be instantiated through Laravel's service container.
*
* All middleware must implement the MailMiddlewareContract
*/
Expand All @@ -31,17 +32,17 @@
EnsureRecipients::class,
],

/**
/*
* Define the domains and email addresses that are allowed
* to receive mails from your application.
* All other recipients will be filtered out
* All other recipients will be filtered out.
*/
'allowed' => [

/**
/*
* Can either be a singular domain string,
* a semicolon separated list of domains or
* an array of domain strings
* an array of domain strings.
*
* e.g.
* 'bar.com'
Expand All @@ -50,7 +51,7 @@
*/
'domains' => env('MAIL_ALLOWLIST_ALLOWED_DOMAINS'),

/**
/*
* Can either be a singular email address string,
* a semicolon separated list of email addresses or
* an array of email address strings (only in config).
Expand All @@ -63,7 +64,7 @@
'emails' => env('MAIL_ALLOWLIST_ALLOWED_EMAILS'),
],

/**
/*
* Define global recipients to be added to every mail sent.
* Each one can either be a singular email address string,
* a semicolon separated list of email addresses or
Expand All @@ -79,4 +80,48 @@
'cc' => env('MAIL_ALLOWLIST_GLOBAL_CC'),
'bcc' => env('MAIL_ALLOWLIST_GLOBAL_BCC'),
],

/*
* Configure the logging of filtered mails.
*/
'log' => [
/*
* Enables the log.
*/
'enabled' => env('MAIL_ALLOWLIST_LOG_ENABLED', false),

/*
* Define a custom logging channel for your filtered message
* logs. Leave empty (null) to default to Laravel's default
* logging channel (config: logging.default). If this is
* undefined, it will fall back to the 'stack' channel.
*/
'channel' => env('MAIL_ALLOWLIST_LOG_CHANNEL'),

/*
* Define the log level to log your filtered messages in.
*/
'level' => env('MAIL_ALLOWLIST_LOG_LEVEL', LogLevel::INFO),

/*
* Define, what parts of the message should be logged.
*/
'include' => [
/*
* Each middleware can add messages to the log through the
* message context that is passed through the pipeline.
*/
'middleware' => true,

/*
* Log the final message headers.
*/
'headers' => true,

/*
* Log the final message body.
*/
'body' => false,
],
],
];
70 changes: 70 additions & 0 deletions src/Actions/Logs/GenerateLogMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

namespace TobMoeller\LaravelMailAllowlist\Actions\Logs;

use TobMoeller\LaravelMailAllowlist\Facades\LaravelMailAllowlist;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\MessageContext;

class GenerateLogMessage implements GenerateLogMessageContract
{
public function generate(MessageContext $messageContext): string
{
$logMessage = 'LaravelMailAllowlist.MessageSending:';

if (! $messageContext->shouldSendMessage()) {
$logMessage .= PHP_EOL.'Message was canceled by Middleware!';
}

if (LaravelMailAllowlist::logMiddleware()) {
$logMessage .= $this->generateMiddlewareMessage($messageContext);
}

if (LaravelMailAllowlist::logHeaders()) {
$logMessage .= $this->generateHeadersMessage($messageContext);
}

if (LaravelMailAllowlist::logBody()) {
$logMessage .= $this->generateBodyMessage($messageContext);
}

return $logMessage;
}

protected function generateMiddlewareMessage(MessageContext $messageContext): string
{
$logMessage = <<<'LOG_MIDDLEWARE'

----------
MIDDLEWARE
----------
LOG_MIDDLEWARE;

foreach ($messageContext->getLog() as $logEntry) {
$logMessage .= PHP_EOL.$logEntry;
}

return $logMessage;
}

protected function generateHeadersMessage(MessageContext $messageContext): string
{
return <<<LOG_HEADERS

----------
HEADERS
----------
{$messageContext->getMessage()->getHeaders()->toString()}
LOG_HEADERS;
}

protected function generateBodyMessage(MessageContext $messageContext): string
{
return <<<LOG_BODY

----------
BODY
----------
{$messageContext->getMessage()->getBody()->toString()}
LOG_BODY;
}
}
10 changes: 10 additions & 0 deletions src/Actions/Logs/GenerateLogMessageContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace TobMoeller\LaravelMailAllowlist\Actions\Logs;

use TobMoeller\LaravelMailAllowlist\MailMiddleware\MessageContext;

interface GenerateLogMessageContract
{
public function generate(MessageContext $messageContext): string;
}
20 changes: 20 additions & 0 deletions src/Actions/Logs/LogMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace TobMoeller\LaravelMailAllowlist\Actions\Logs;

use Illuminate\Support\Facades\Log;
use TobMoeller\LaravelMailAllowlist\Facades\LaravelMailAllowlist;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\MessageContext;

class LogMessage implements LogMessageContract
{
public function __construct(public GenerateLogMessageContract $generateLogMessage) {}

public function log(MessageContext $messageContext): void
{
$message = $this->generateLogMessage->generate($messageContext);

Log::channel(LaravelMailAllowlist::logChannel())
->log(LaravelMailAllowlist::logLevel(), $message);
}
}
10 changes: 10 additions & 0 deletions src/Actions/Logs/LogMessageContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace TobMoeller\LaravelMailAllowlist\Actions\Logs;

use TobMoeller\LaravelMailAllowlist\MailMiddleware\MessageContext;

interface LogMessageContract
{
public function log(MessageContext $messageContext): void;
}
50 changes: 50 additions & 0 deletions src/LaravelMailAllowlist.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
namespace TobMoeller\LaravelMailAllowlist;

use Illuminate\Support\Facades\Config;
use InvalidArgumentException;
use Psr\Log\LogLevel;
use ReflectionClass;
use Throwable;
use TobMoeller\LaravelMailAllowlist\MailMiddleware\MailMiddlewareContract;

class LaravelMailAllowlist
Expand Down Expand Up @@ -90,4 +94,50 @@ protected function extractArrayFromConfig(mixed $configValue): array

return [];
}

public function logEnabled(): bool
{
return (bool) Config::get('mail-allowlist.log.enabled', false);
}

public function logChannel(): string
{
$channel = Config::get('mail-allowlist.log.channel');
$channel = is_string($channel) ? $channel : Config::get('logging.default');

return is_string($channel) ? $channel : 'stack';
}

/**
* @throws Throwable
*/
public function logLevel(): string
{
$level = Config::get('mail-allowlist.log.level');
$allowedLevels = array_values((new ReflectionClass(LogLevel::class))->getConstants());

throw_unless(
is_string($level) &&
in_array($level = mb_strtolower($level), $allowedLevels),
InvalidArgumentException::class,
'Invalid log level provided'
);

return $level;
}

public function logMiddleware(): bool
{
return (bool) Config::get('mail-allowlist.log.include.middleware');
}

public function logHeaders(): bool
{
return (bool) Config::get('mail-allowlist.log.include.headers');
}

public function logBody(): bool
{
return (bool) Config::get('mail-allowlist.log.include.body');
}
}
Loading