Zend Framework: Zend_Log_Writer_Mail Component Proposal
| Proposed Component Name | Zend_Log_Writer_Mail |
|---|---|
| Developer Notes | http://framework.zend.com/wiki/display/ZFDEV/Zend_Log_Writer_Mail |
| Proposers | Brian DeShong |
| Revision | 1.0 - 26 January 2008 - Initial creation (wiki revision: 23) |
Table of Contents
1. Overview
Zend_Log_Writer_Mail is a Zend_Log writer for sending log entries to recipient(s) via email.
Proposal of this class is motivated by use in a batch script environment, where logs need to be kept, but developers also need to be notified via email of any notices, warnings, errors, etc.; will be illustrated below in "Use Cases."
2. References
- Original Zend_Log rewrite proposal
- Note how mention is made of a Mail-based log writer, but one does not exist at this time.
3. Component Requirements, Constraints, and Acceptance Criteria
- This component will attempt to deliver appropriate log entries to given recipient(s)
- This component will adhere to standard Zend_Log_Writer formatting and filtering conventions
- This component will allow for a minimum of a plaintext message body, with an optional HTML message body
- This component will dynamically set mail subject to include the number of entries that occurred per-priority level if the caller so chooses
- This component will not open connections to the Zend_Mail transport until a message is ready to be sent
- This component will not allow for on-demand mailing of log entries
4. Dependencies on Other Framework Components
- Zend_Mail
- Zend_Log_Writer_Abstract
- Zend_Log_Exception
- Zend_Layout
5. Theory of Operation
User instantiates a Zend_Mail object and populates it with data for recipients that should be notified of any log entries. Zend_Mail object is then passed to constructor for Zend_Log_Writer_Mail object.
Optionally, user may instantiate an instance of Zend_Layout to provide an HTML-based message body.
Zend_Log_Writer_Mail::_write() builds up an array of log entry lines to use as the body of the email message to the recipients.
Email should not be sent upon the call of Zend_Log_Writer_Mail::_write(); the email should be sent upon the call to shutdown() if there are log entry lines to use in the body.
Once email has been sent to recipients, reference to Zend_Mail object is NOT removed as the user may want to continue using it.
6. Milestones / Tasks
- Milestone 1: [DONE] Initial class code is drafted for proposal
- Milestone 2: [DONE] Proposal finalized and readied for review
- Milestone 3: Working prototype checked in to incubator for community review
- Milestone 3: Unit tests are created with 90%+ code coverage (assume that we can't test the actual receiving of email?)
- Milestone 4: Initial documentation completed
7. Class Index
- Zend_Log_Writer_Mail
8. Use Cases
| UC-01 |
|---|
| UC-02 |
|---|
| UC-03 |
|---|
| UC-04 |
|---|
| UC-05 |
|---|
9. Class Skeletons
You only get one email with all of the log messages in it, regardless of what their log level is.
Note how write() builds up an array of the log entries, while shutdown() actually sends the email to the recipient(s).
Does that make it a bit more clear?
HI
Yes thanks. Thats good. I think this log writer is a good enhancement.
But, as Lars said already - Sometimes this behavior is wanted but generaly it can be a great danger to send logs via mail on every request.
Perhaps it would be better to have two options for this writer. First Option ,lets say a Collector, which collects the logs in a serialized form in a file and sends it on a defined time or in an defined interval.
And the other option right this way you proposed it already.
Tom
But as far as I understand, you get one mail per request? Logging straightly per mail is considered a bad practice as it increases the danger of getting DOSsed by accident. If error XYZ (the one, where you send the mail) occurs it will likely occur more often and therefore the application might bring down the mail server too. So I'm not sure if it is a good idea to include such a component in general.
In this case, the developer could implement Zend_Log_Filter_Interface when determining if the log entry should be accepted. The mechanics of this would be something along the lines of storing recently-sent log entries in persistent storage (a temp file, for example) and checking if that entry has been sent within the past X minutes.
My personal argument is that if, say, you're writing a file with a batch process, but have run out of disk space, the developer(s) should be notified so they can work to correct that problem ASAP. In an event such as this, receiving a regular email goes a long way to getting someone notified of the problem and having it dealt with in a timely manner.
Point being: some cases need frequent sending of these emails, while you may want to be more forgiving with others. You leave this choice up to the developer by allowing them to use a filter.
What do you think?
I agree that the developer should decide if it's ok to use this.
I would like to see the load problem addressed. Monitoring for disks is pretty easy to implement, throughput control for an email system is much harder. I fear if we provider Zend_Log_Writer_Mail we encourage users to do that.
Filtering doesn't solve that problem. If a certain condition is met under which email logging is used it is likely to occur again and again. Log to the database, and send mails from a batch process.
I'm not sure that I understand this comment. By "load problem," are you referring to the potentially large amount of emails that could be sent during some sort of a critical failure?
If so, then what solution would you propose that is A) portable, B) doesn't have any unreasonable dependencies, and C) is going to work well for the majority of users?
Off the top of my head, I can dream up a solution along these lines:
1) When building messages to be mailed, exclude or otherwise strip off any date/time values so you're just dealing with the raw error messages being logged/emailed; you want to examine the unique error messages, so stripping off the date/time ensures that they're not always unique strings.
2) Take that string of messages and hash it somehow (MD5, etc.)
3) Upon sending email, store the hash of the error messages to a flat file on disk along with the Unix timestamp of when those messages were last sent and the email recipient(s) that it was sent to.
4) Before sending future emails, read from file of MD5s to see if current error messages to recipient(s) were emailed within the past X seconds or minutes (where X is able to be configured by the user). If error messages were emailed within that time period, don't email them again. Otherwise, send the mail normally.
However, I don't necessarily feel that this should be enabled by default as most users may not want to use such functionality. Thus, they should have to enable it somehow and explicitly request that the class throttle messages in the manner outlined above or something similar.
Regarding "log to a database," I definitely wouldn't want to introduce any database dependencies here as this seems like an inappropriate place.
What do you think? Also, let me know if I totally misinterpreted your comment as I found it a bit unclear. Thanks!
I'm using this implementation in a new project and it seems to work fine. I'm filtering the log messages to mail at a very low level (Zend_Log::CRIT). The rest is logged into a database.
A nice other tric I did, was to initially set the level for the mail writer to Zend_Log::WARN, and when the database connection is succesful I alter the filter level to Zend_Log::CRIT (subclassed Zend_Log_Filter_Priority with a setPriority() function)
How about a possibility to change the subject depending on the different levels of errors, warnings and notices? Having this running for cronjobs it might be interesting to make reports with more critical warnings seen better. Or is it better to implement two different writers with different priorities for this case?
Also what about chaning the subject to display the number of log entries related to in this mail?
Hi Benjamin,
Good feedback, thanks!
Off the top of my head, it seems like the simplest way to implement something like this would be with, say, some pre-defined tokens in the subject line. For example, you could set your subjects to:
...or even...
...which would yield:
For the record, I'd use curly braces instead of brackets, but the curly braces don't seem to render well on the wiki page.
Also, you suggest that you may want to change the subject for different levels of errors. I'd argue that this is unnecessary sheerly because, during the course of a script, it can generate many log messages at each level. For example, if a run generated, say, three (3) warnings and (4) errors, then what would the subject line say?
That's why I suggested the tokens above, which would be replaced just before the message itself is sent.
However, I would also argue that such token replacement could (and maybe should?) be done by the caller. As the proposer of this, I can't assume that all consumers of it will want to display the log message data in the same way. I can see how it might be useful, but perhaps its place is not in the Zend_Log_Writer_Mail class itself.
What do you all think? If there's enough +1s for this, it'd be worth adding.
Hi,
I think it is a very good matter to configurable the mail subject by mail content but I would set the percent characters instead of curly braces because it is used in Zend_Log_Formater.
In my opinion is it the best way to make the mail subject configurable in Zend_Log_Writer_Mail using the Zend_Log_Formater to format the subject because if I set the subject in the mail object given to the log writer the log writer can't change this:
'Zend_Mail_Exception' with message 'Subject set twice'
1) Which formaters can be allowed to format a subject ?
2) Is it a good idea to set a Zend_Mail object directly to the writer or is it better to set only a Zend_Mail_Transporter to it and implement configurable from/to/subject/header - part in Zend_Log_Writer_Mail or change the Zend_Mail class to implement overwritable mail subject ?
I've implemented something along these lines in the final proposal. Now the number of entries per-priority level can be optionally appended to the email subject by using the setSubjectPrependText() method.
Note that I had to go this route because Zend_Mail::setSubject() will only allow the subject to be set once. As such, the client developer cannot call Zend_Mail::setSubject(), then have Zend_Log_Writer_Mail reset it to append on any entry counts.
It seems like this is a nice middle ground.
Just a note that this is also being dealt with in the Issue Tracker at:
What about making the sending behaviour configurable? Imagine some kind of daemon or long-running script - logs would "never" be mailed, or at least surely too late.
Here what changes could look like:
However, even if I prefer logging to syslog and let syslog monitoring software send all kind of notifications (mail, sms, jabber...) depending on log facility and other filters, I like this proposal! It could be helpful in a lot of places, mostly if your scripts are not running on your own servers!
Best regards,
Thomas Gelf
this proposal had lots of review, why not promote it to ready for recommendation? its quite important to be in a next stable release imho (maybe 1.7 already?)
So, I'd like to mark this proposal as ready for recommendation very soon. However, I have two action items that I'd like to take on before considering it final:
1) Integrating optional use of Zend_Layout; would be nice to be able to use HTML-enabled emails with a header and a footer if the user so desires
2) Integrating ability to send on-demand or deferred; primary use case supporting this is for use in a long-running script
It's now 10/24; I'll aim to integrate these two items by 10/31 or 11/7, then mark this proposal as ready for recommendation.
Thanks, everyone! Sorry I've been a slacker on this; it's been a crazy few months for me.
I've decided against pursuing the idea of "deferred" sending of messages vs. immediate.
The main driver behind this is that _write() is called every time a message is logged. As such, you would send one email for each line.
This makes sense in a long-running script that seldom generates errors, but I'm hesitant to include this for fear that it may be misused and misunderstood by a sizable portion of consumers.
If anyone objects to this, please speak up.
Otherwise, I'm integrating Zend_Layout usage now and will be updating the proposal this evening.
I have added the initial code showing Zend_Layout integration as of 10/30/2008. I have also added use case #04 outlining a simple Zend_Layout usage.
I will leave this open for comments through 6:00 PM ET 11/03/2008, at which time I will be marking this proposal "Ready for Recommendation" barring any showstopper comments or problems.
That's probably short notice, but I want to get this proposal into the meat of the review process sooner rather than later. Thanks, everyone!
Just a note that I want to refactor something related to my Zend_Layout integration; should be able to do that and adjust the proposal by 11/10.
I am now considering this proposal done and will mark it as Ready for Recommendation. Over the past two days, I have made the following changes:
1) Added a setLayoutFormatter() method to allow for one formatter to be used with the plaintext email body, and a separate formatter to be used for the HTML email body. In the event that a specific formatter is now defined for the Zend_Layout entries, the default formatter will be used. The primary use case for this is when a plaintext email body needs its entry lines to end with PHP_EOL (newlines), but an HTML email body needs its lines to end in "<br/>".
2) Added a setSubjectPrependText() method. Zend_Mail will only allow the subject to be set once, so it cannot be reset. Adding this method allows me to append the number of entries per-priority level to the mail subject upon shutdown(). For example, the consuming developer would NOT call setSubject() on the Zend_Mail object if they wanted to include the entry counts in the subject line. Instead, they would call something like "$mailWriter->setSubjectPrependText('My script')", which would end up rendering the subject line as "My script (WARN=X, ERR=Y, ...)". Use of this functionality is optional; the caller can choose to set a static subject on the mail message, but if they want the counts appended to the subject line, they can use this method.
3) When using Zend_Layout, the layout templates must use "$this->_layout()->events" to render the entry lines from within the Zend_Layout templates.
4) Added a private _getFormattedNumEntriesPerPriority() method that returns the entry count per-priority level for use in appending to the subject line, if applicable.
These changes are a direct result of feedback received since the original proposal.
As such, I am now marking it "Ready for Recommendation" and look forward to further feedback and its acceptance!
ZF Home Page
Code Browser
Wiki Dashboard
Whats happening in a case when you got more than one message of one type. Do you get a Mail per message, or do you collect messages of one type and sending all messages in one mail?