Logger Plugin for Swift4

Swift Mailer is a fantastic library for sending email with php. Discuss this library or ask any questions about it here.

Moderators: Chris Corbyn, General Moderators

Post Reply
BrandonMUS
Forum Newbie
Posts: 15
Joined: Mon Nov 10, 2008 9:45 am

Logger Plugin for Swift4

Post by BrandonMUS »

Does anyone have an example plugin for swiftmailer 4? Since the docs are still a WIP, I'm a little unsure about how to approach a logging plugin. I need to log every email that gets sent by my app including all of the headers. I'll then end up using this log to guarantee that emails are not being duplicated and that the email system isn't being abused. I found Swift_Plugins_Logger and Swift_Plugins_LoggerPlugin, but I'm not sure how to properly extend them... I'll be logging to a database.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Logger Plugin for Swift4

Post by Chris Corbyn »

The logger plugin itself doesn't log the email itself since they can run into several MB each. I'm considering extending it to do this however.

It sounds like you need a simple plugin:

Code: Select all

class YourPlugin implements Swift_Events_SendListener {
  public function beforeSendPerformed(Swift_Events_SendEvent $evt) {
  }
  public function sendPerformed(Swift_Events_SendEvent $evt) {
    $message = $evt->getMessage();
    $emailSource = $message->toString();
    //You now have your .eml file contents to do with them what you please..
    
  }
}
 
$mailer->registerPlugin(new YourPlugin());
BrandonMUS
Forum Newbie
Posts: 15
Joined: Mon Nov 10, 2008 9:45 am

Re: Logger Plugin for Swift4

Post by BrandonMUS »

Thanks for the info. It looks pretty simple, so I'll give it a shot today if I get the time!
BrandonMUS
Forum Newbie
Posts: 15
Joined: Mon Nov 10, 2008 9:45 am

Re: Logger Plugin for Swift4

Post by BrandonMUS »

Am I missing something? I tried to use the example in the docs:

Code: Select all

require_once 'lib/swiftmailer4/swift_required.php';
 
$emailer = Swift_Mailer::newInstance(Swift_SmtpTransport::newInstance('localhost'));
$emailer->registerPlugin(new Swift_Plugins_Loggers_EchoLogger());
Here was my error message:

Code: Select all

Warning [4096] Argument 1 passed to Swift_Mailer::registerPlugin() must be an instance of Swift_Events_EventListener, instance of Swift_Plugins_Loggers_EchoLogger given, called in /home/httpd/vhosts/.../OT_EmailNotifier.php on line 107 and defined
Error on line 170 in file /home/httpd/vhosts/.../lib/swiftmailer4/classes/Swift/Mailer.php
Warning [4096] Argument 1 passed to Swift_Transport_AbstractSmtpTransport::registerPlugin() must be an instance of Swift_Events_EventListener, instance of Swift_Plugins_Loggers_EchoLogger given, called in /home/httpd/vhosts/.../lib/swiftmailer4/classes/Swift/Mailer.php on line 172 and defined
Error on line 239 in file /home/httpd/vhosts/.../lib/swiftmailer4/classes/Swift/Transport/AbstractSmtpTransport.php
Warning [4096] Argument 1 passed to Swift_Events_SimpleEventDispatcher::bindEventListener() must be an instance of Swift_Events_EventListener, instance of Swift_Plugins_Loggers_EchoLogger given, called in /home/httpd/vhosts/.../lib/swiftmailer4/classes/Swift/Transport/AbstractSmtpTransport.php on line 241 and defined
Error on line 133 in file /home/httpd/vhosts/.../lib/swiftmailer4/classes/Swift/Events/SimpleEventDispatcher.php
EDIT: Found the problem. In the docs, you walk through the steps, but failed to highlight it again in the code. In the EchoLogger snippet, you do not wrap the logger object in an instance of Swift_Plugins_LoggerPlugins. My updated code is:

Code: Select all

$logger = new Swift_Plugins_Loggers_EchoLogger();
$emailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger));
And it seems to work without issue.

Just an FYI.
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Logger Plugin for Swift4

Post by Chris Corbyn »

Ouch, sorry, I'll fix the docs.
BrandonMUS
Forum Newbie
Posts: 15
Joined: Mon Nov 10, 2008 9:45 am

Re: Logger Plugin for Swift4

Post by BrandonMUS »

So I'm a little confused and I'm not sure the best way to do this. I'm trying to instill a fair amount of logging into this email system (as I think any email system should), and Im' running into some issues that seem to stem from the overly simplistic Swift_Mailer class.

First, I wanted to keep a record of the server (ala Swift_Plugins_LoggerPlugin) so that in the event of a send failure, I would hopefully be able to gain some added insight (beyond just the failed recipients list). Unfortunately, what I found was that after I registered the plugin, there was no way to get back to it. This is solved by storing the instance in a separate variable, but now I would have to inject it as a dependency to other objects if they need to use it. Doable, just annoying. I'm a little suprised to not see a plugin container so that we could get access back to the patterns (eg. Swift_Mailer::getPlugin('plugin_name');) or something. On that same topic, unregisterPlugin()?

Secondly, there is no way to troubleshoot a send without already having a SendListener in place. There are some simple checks I want to know after a send fails like: Was it manually cancelled (bubbleCancelled)? Was there a partial send or is it still queued (getResult)? If it was a partial send, who received it (technically I could compare my failed recipients to the expected recipients I guess, but that makes me get back into Swift_Message which wouldn't account for any event listeners)?

Maybe I'm missing the concept, but I couldn't find an easy way to accomplish what I thought would be fairly straight forward. Just my $.02
User avatar
Chris Corbyn
Breakbeat Nuttzer
Posts: 13098
Joined: Wed Mar 24, 2004 7:57 am
Location: Melbourne, Australia

Re: Logger Plugin for Swift4

Post by Chris Corbyn »

Hi Brandon,

My original design had ways to get the plugin back out via getPlugin() but I removed it in the interest of simplicity since as you say, you just need to store it in a variable, in just the same way you'd have to store the Transport in a variable if you want a copy of that. I hadn't realised this would cause so much of a problem. I can re-evaluate that decision if it's really not workable.

Do you have a use case for unregisterPlugin()? This was another feature that I considered unneeded in the interest of simplicity, but it would be easy for me to add (it would just accept the plugin as a parameter). All the plugins finish up inside a shared instance of the Swift_Events_EventDispatcher class (which is basically the Plugin container you were looking for).

Again, the reason there's no way to get high-level debugging information without having a SendListener (a plugin for anybody else reading this) in place was in the interest of simplicity (and memory use). Each Transport takes a different approach to sending the email so the Mailer class itself can't make any assumptions there. What sort of interface would you envisage that I should have included? I'll consider it :)

If a send fails, it's failed. There's no such thing as it being "still queued" within Swift Mailer itself. If your code adds this functionality then you'd have to account for that with some custom coding. If it was manually cancelled the bubbleCancelled() will return true. You can work out who received it as you mention, but comparing the failed recipients list with the recipients that were being sent to.

Is the library presenting you with a brick wall here? It sounds as though you're able to do what you need, you're just unhappy that it's not a "drop in" solution for this high-level logging? :)

If you provide some specific information (requirements of what exactly you'd be logging and where you'd be storing it) we can work through writing a plugin here, perhaps with the intention of including it in the library.

I had considered writing a DebuggerTransport that acts as a wrapper around the actual Transport, though I think it would have been confusing having such a thing in addition to the logger.

Code: Select all

$mailer = new Swift_Mailer(new Swift_DebuggerTransport(new Swift_SmtpTransport(...)));
BrandonMUS
Forum Newbie
Posts: 15
Joined: Mon Nov 10, 2008 9:45 am

Re: Logger Plugin for Swift4

Post by BrandonMUS »

Chris Corbyn wrote:My original design had ways to get the plugin back out via getPlugin() but I removed it in the interest of simplicity since as you say, you just need to store it in a variable, in just the same way you'd have to store the Transport in a variable if you want a copy of that. I hadn't realised this would cause so much of a problem. I can re-evaluate that decision if it's really not workable.
I only call it a problem because it makes it harder to add this type of checking as an after thought. If I know that down the road something in my system will need access to the plugins (I'm sticking with my debug/listener plugin for now), I have to plan ahead and inject that plugin reference to every class until I get to the point where it is needed. My emailer is only two classes, so it is relatively easy for me to implement, but if someone was using this in a more complex newsletter system, I could see it getting messy. IMO, an ideal solution would have it stored in the Swift_Mailer instance somehow with a public method like Swift_Mailer::getPlugin('plugin_name'); or something. How the plugins are reigsted would probably also need to be altered so that we can name plugins (or it could just default to the plugin class name maybe?).
Chris Corbyn wrote:Do you have a use case for unregisterPlugin()? This was another feature that I considered unneeded in the interest of simplicity, but it would be easy for me to add (it would just accept the plugin as a parameter). All the plugins finish up inside a shared instance of the Swift_Events_EventDispatcher class (which is basically the Plugin container you were looking for).
Nah, I can't see any reason that it would be needed, I was just thinking out loud.
Chris Corbyn wrote:Again, the reason there's no way to get high-level debugging information without having a SendListener (a plugin for anybody else reading this) in place was in the interest of simplicity (and memory use). Each Transport takes a different approach to sending the email so the Mailer class itself can't make any assumptions there. What sort of interface would you envisage that I should have included? I'll consider it :)

If a send fails, it's failed. There's no such thing as it being "still queued" within Swift Mailer itself. If your code adds this functionality then you'd have to account for that with some custom coding. If it was manually cancelled the bubbleCancelled() will return true. You can work out who received it as you mention, but comparing the failed recipients list with the recipients that were being sent to.
I got the "still queued" idea from the Swift_Events_SendEvent Result_ constants. I know that the transports I use will only ever come back as Success, Tentative or Failure, but I was just using it as an example of extra information that is out there, just not readily accessible. As for the bubbleCancelled, I think this is a very important thing for debugging. If you have an event listener that manually cancelled the send, I think it's important to be able to tell. A cancelled send isn't a big deal, but an unhandled failure is bad and I would want to know about it.
Chris Corbyn wrote:Is the library presenting you with a brick wall here? It sounds as though you're able to do what you need, you're just unhappy that it's not a "drop in" solution for this high-level logging? :)
It's not so much a brick wall (as I found the workaround fairly quickly) but more of a heads-up. Really, the part I don't like is that I have to rely on a plugin to debug my sends. I would much rather be able to do the debugging in the same spot I do the sending in my application. I really don't know what the interface would look like, but the ability to get to the Swift_Send_Event would be ideal

Code: Select all

try{
//getEmailer returns Swift_Mailer
$this->getEmailer()->send(...);
catch (Swift_Exception $swift_e) {
//$this->getEmailer()->getSendEvent();  //returns Swift_Send_Event just like a plugin receives; and would return null if send wasn't attempted yet
}
Chris Corbyn wrote:If you provide some specific information (requirements of what exactly you'd be logging and where you'd be storing it) we can work through writing a plugin here, perhaps with the intention of including it in the library.

I had considered writing a DebuggerTransport that acts as a wrapper around the actual Transport, though I think it would have been confusing having such a thing in addition to the logger.

Code: Select all

$mailer = new Swift_Mailer(new Swift_DebuggerTransport(new Swift_SmtpTransport(...)));
The plugin architecture seems solid. I didn't have any problems digging the information I needed out of the Swift_Send_Event object, I was just uneasy? with where the logic had to go.
About the only thing I didn't see the ability to access in my SendListener is my ArrayLogger plugin. In an ideal world, I would attach the complete log of communication, but I can live without it.
Post Reply