View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.net;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.util.Date;
23  import java.util.Properties;
24  
25  import javax.activation.DataSource;
26  import javax.mail.Authenticator;
27  import javax.mail.Message;
28  import javax.mail.MessagingException;
29  import javax.mail.PasswordAuthentication;
30  import javax.mail.Session;
31  import javax.mail.Transport;
32  import javax.mail.internet.InternetHeaders;
33  import javax.mail.internet.MimeBodyPart;
34  import javax.mail.internet.MimeMessage;
35  import javax.mail.internet.MimeMultipart;
36  import javax.mail.internet.MimeUtility;
37  import javax.mail.util.ByteArrayDataSource;
38  
39  import org.apache.logging.log4j.LoggingException;
40  import org.apache.logging.log4j.core.Layout;
41  import org.apache.logging.log4j.core.LogEvent;
42  import org.apache.logging.log4j.core.appender.AbstractManager;
43  import org.apache.logging.log4j.core.appender.ManagerFactory;
44  import org.apache.logging.log4j.core.helpers.CyclicBuffer;
45  import org.apache.logging.log4j.core.helpers.NameUtil;
46  import org.apache.logging.log4j.core.helpers.NetUtils;
47  import org.apache.logging.log4j.util.PropertiesUtil;
48  
49  /**
50   * Manager for sending SMTP events.
51   */
52  public class SMTPManager extends AbstractManager {
53      private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory();
54  
55      private final Session session;
56  
57      private final CyclicBuffer<LogEvent> buffer;
58  
59      private volatile MimeMessage message;
60  
61      private final FactoryData data;
62  
63      protected SMTPManager(final String name, final Session session, final MimeMessage message,
64                            final FactoryData data) {
65          super(name);
66          this.session = session;
67          this.message = message;
68          this.data = data;
69          this.buffer = new CyclicBuffer<LogEvent>(LogEvent.class, data.numElements);
70      }
71  
72      public void add(final LogEvent event) {
73          buffer.add(event);
74      }
75  
76      public static SMTPManager getSMTPManager(final String to, final String cc, final String bcc,
77                                               final String from, final String replyTo,
78                                               final String subject, String protocol, final String host,
79                                               final int port, final String username, final String password,
80                                               final boolean isDebug, final String filterName, final int numElements) {
81          if (protocol == null || protocol.length() == 0) {
82              protocol = "smtp";
83          }
84  
85          final StringBuilder sb = new StringBuilder();
86          if (to != null) {
87              sb.append(to);
88          }
89          sb.append(":");
90          if (cc != null) {
91              sb.append(cc);
92          }
93          sb.append(":");
94          if (bcc != null) {
95              sb.append(bcc);
96          }
97          sb.append(":");
98          if (from != null) {
99              sb.append(from);
100         }
101         sb.append(":");
102         if (replyTo != null) {
103             sb.append(replyTo);
104         }
105         sb.append(":");
106         if (subject != null) {
107             sb.append(subject);
108         }
109         sb.append(":");
110         sb.append(protocol).append(":").append(host).append(":").append("port").append(":");
111         if (username != null) {
112             sb.append(username);
113         }
114         sb.append(":");
115         if (password != null) {
116             sb.append(password);
117         }
118         sb.append(isDebug ? ":debug:" : "::");
119         sb.append(filterName);
120 
121         final String name = "SMTP:" + NameUtil.md5(sb.toString());
122 
123         return getManager(name, FACTORY, new FactoryData(to, cc, bcc, from, replyTo, subject,
124             protocol, host, port, username, password, isDebug, numElements));
125     }
126 
127     /**
128      * Send the contents of the cyclic buffer as an e-mail message.
129      * @param layout The layout for formatting the events.
130      * @param appendEvent The event that triggered the send.
131      */
132     public void sendEvents(final Layout<?> layout, final LogEvent appendEvent) {
133         if (message == null) {
134             connect();
135         }
136         try {
137             final LogEvent[] priorEvents = buffer.removeAll();
138             final byte[] rawBytes = formatContentToBytes(priorEvents, appendEvent, layout);
139 
140             final String contentType = layout.getContentType();
141             final String encoding = getEncoding(rawBytes, contentType);
142             final byte[] encodedBytes = encodeContentToBytes(rawBytes, encoding);
143 
144             final InternetHeaders headers = getHeaders(contentType, encoding);
145             final MimeMultipart mp = getMimeMultipart(encodedBytes, headers);
146 
147             sendMultipartMessage(message, mp);
148         } catch (final MessagingException e) {
149             LOGGER.error("Error occurred while sending e-mail notification.", e);
150             throw new LoggingException("Error occurred while sending email", e);
151         } catch (final IOException e) {
152             LOGGER.error("Error occurred while sending e-mail notification.", e);
153             throw new LoggingException("Error occurred while sending email", e);
154         } catch (final RuntimeException e) {
155             LOGGER.error("Error occurred while sending e-mail notification.", e);
156             throw new LoggingException("Error occurred while sending email", e);
157         }
158     }
159 
160     protected byte[] formatContentToBytes(final LogEvent[] priorEvents, final LogEvent appendEvent,
161                                           final Layout<?> layout) throws IOException {
162         final ByteArrayOutputStream raw = new ByteArrayOutputStream();
163         writeContent(priorEvents, appendEvent, layout, raw);
164         return raw.toByteArray();
165     }
166 
167     private void writeContent(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
168                               final ByteArrayOutputStream out)
169         throws IOException {
170         writeHeader(layout, out);
171         writeBuffer(priorEvents, appendEvent, layout, out);
172         writeFooter(layout, out);
173     }
174 
175     protected void writeHeader(final Layout<?> layout, final OutputStream out) throws IOException {
176         final byte[] header = layout.getHeader();
177         if (header != null) {
178             out.write(header);
179         }
180     }
181 
182     protected void writeBuffer(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
183                                final OutputStream out) throws IOException {
184         for (final LogEvent priorEvent : priorEvents) {
185             final byte[] bytes = layout.toByteArray(priorEvent);
186             out.write(bytes);
187         }
188 
189         final byte[] bytes = layout.toByteArray(appendEvent);
190         out.write(bytes);
191     }
192 
193     protected void writeFooter(final Layout<?> layout, final OutputStream out) throws IOException {
194         final byte[] footer = layout.getFooter();
195         if (footer != null) {
196             out.write(footer);
197         }
198     }
199 
200     protected String getEncoding(final byte[] rawBytes, final String contentType) {
201         final DataSource dataSource = new ByteArrayDataSource(rawBytes, contentType);
202         return MimeUtility.getEncoding(dataSource);
203     }
204 
205     protected byte[] encodeContentToBytes(final byte[] rawBytes, final String encoding)
206         throws MessagingException, IOException {
207         final ByteArrayOutputStream encoded = new ByteArrayOutputStream();
208         encodeContent(rawBytes, encoding, encoded);
209         return encoded.toByteArray();
210     }
211 
212     protected void encodeContent(final byte[] bytes, final String encoding, final ByteArrayOutputStream out)
213         throws MessagingException, IOException {
214         final OutputStream encoder = MimeUtility.encode(out, encoding);
215         encoder.write(bytes);
216         encoder.close();
217     }
218 
219     protected InternetHeaders getHeaders(final String contentType, final String encoding) {
220         final InternetHeaders headers = new InternetHeaders();
221         headers.setHeader("Content-Type", contentType + "; charset=UTF-8");
222         headers.setHeader("Content-Transfer-Encoding", encoding);
223         return headers;
224     }
225 
226     protected MimeMultipart getMimeMultipart(final byte[] encodedBytes, final InternetHeaders headers)
227         throws MessagingException {
228         final MimeMultipart mp = new MimeMultipart();
229         final MimeBodyPart part = new MimeBodyPart(headers, encodedBytes);
230         mp.addBodyPart(part);
231         return mp;
232     }
233 
234     protected void sendMultipartMessage(final MimeMessage message, final MimeMultipart mp) throws MessagingException {
235         synchronized (message) {
236             message.setContent(mp);
237             message.setSentDate(new Date());
238             Transport.send(message);
239         }
240     }
241 
242     /**
243      * Factory data.
244      */
245     private static class FactoryData {
246         private final String to;
247         private final String cc;
248         private final String bcc;
249         private final String from;
250         private final String replyto;
251         private final String subject;
252         private final String protocol;
253         private final String host;
254         private final int port;
255         private final String username;
256         private final String password;
257         private final boolean isDebug;
258         private final int numElements;
259 
260         public FactoryData(final String to, final String cc, final String bcc, final String from, final String replyTo,
261                            final String subject, final String protocol, final String host, final int port,
262                            final String username, final String password, final boolean isDebug, final int numElements) {
263             this.to = to;
264             this.cc = cc;
265             this.bcc = bcc;
266             this.from = from;
267             this.replyto = replyTo;
268             this.subject = subject;
269             this.protocol = protocol;
270             this.host = host;
271             this.port = port;
272             this.username = username;
273             this.password = password;
274             this.isDebug = isDebug;
275             this.numElements = numElements;
276         }
277     }
278 
279     private synchronized void connect() {
280         if (message != null) {
281             return;
282         }
283         try {
284             message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto)
285                 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc)
286                 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage();
287         } catch (final MessagingException e) {
288             LOGGER.error("Could not set SMTPAppender message options.", e);
289             message = null;
290         }
291     }
292 
293     /**
294      * Factory to create the SMTP Manager.
295      */
296     private static class SMTPManagerFactory implements ManagerFactory<SMTPManager, FactoryData> {
297 
298         public SMTPManager createManager(final String name, final FactoryData data) {
299             final String prefix = "mail." + data.protocol;
300 
301             final Properties properties = PropertiesUtil.getSystemProperties();
302             properties.put("mail.transport.protocol", data.protocol);
303             if (properties.getProperty("mail.host") == null) {
304                 // Prevent an UnknownHostException in Java 7
305                 properties.put("mail.host", NetUtils.getLocalHostname());
306             }
307 
308             if (null != data.host) {
309                 properties.put(prefix + ".host", data.host);
310             }
311             if (data.port > 0) {
312                 properties.put(prefix + ".port", String.valueOf(data.port));
313             }
314 
315             final Authenticator authenticator = buildAuthenticator(data.username, data.password);
316             if (null != authenticator) {
317                 properties.put(prefix + ".auth", "true");
318             }
319 
320             final Session session = Session.getInstance(properties, authenticator);
321             session.setProtocolForAddress("rfc822", data.protocol);
322             session.setDebug(data.isDebug);
323             MimeMessage message;
324 
325             try {
326                 message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto)
327                     .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc)
328                     .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage();
329             } catch (final MessagingException e) {
330                 LOGGER.error("Could not set SMTPAppender message options.", e);
331                 message = null;
332             }
333 
334             return new SMTPManager(name, session, message, data);
335         }
336 
337         private Authenticator buildAuthenticator(final String username, final String password) {
338             if (null != password && null != username) {
339                 return new Authenticator() {
340                     private final PasswordAuthentication passwordAuthentication =
341                         new PasswordAuthentication(username, password);
342 
343                     @Override
344                     protected PasswordAuthentication getPasswordAuthentication() {
345                         return passwordAuthentication;
346                     }
347                 };
348             }
349             return null;
350         }
351     }
352 }