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.util.CyclicBuffer;
    45  import org.apache.logging.log4j.core.util.NameUtil;
    46  import org.apache.logging.log4j.core.util.NetUtils;
    47  import org.apache.logging.log4j.util.PropertiesUtil;
    48  import org.apache.logging.log4j.util.Strings;
    49
    50  /**
    51   * Manager for sending SMTP events.
    52   */
    53  public class SmtpManager extends AbstractManager {
    54      private static final SMTPManagerFactory FACTORY = new SMTPManagerFactory();
    55
    56      private final Session session;
    57
    58      private final CyclicBuffer<LogEvent> buffer;
    59
    60      private volatile MimeMessage message;
    61
    62      private final FactoryData data;
    63
    64      protected SmtpManager(final String name, final Session session, final MimeMessage message,
    65                            final FactoryData data) {
    66          super(name);
    67          this.session = session;
    68          this.message = message;
    69          this.data = data;
    70          this.buffer = new CyclicBuffer<LogEvent>(LogEvent.class, data.numElements);
    71      }
    72
    73      public void add(final LogEvent event) {
    74          buffer.add(event);
    75      }
    76
    77      public static SmtpManager getSMTPManager(final String to, final String cc, final String bcc,
    78                                               final String from, final String replyTo,
    79                                               final String subject, String protocol, final String host,
    80                                               final int port, final String username, final String password,
    81                                               final boolean isDebug, final String filterName, final int numElements) {
    82          if (Strings.isEmpty(protocol)) {
    83              protocol = "smtp";
    84          }
    85
    86          final StringBuilder sb = new StringBuilder();
    87          if (to != null) {
    88              sb.append(to);
    89          }
    90          sb.append(':');
    91          if (cc != null) {
    92              sb.append(cc);
    93          }
    94          sb.append(':');
    95          if (bcc != null) {
    96              sb.append(bcc);
    97          }
    98          sb.append(':');
    99          if (from != null) {
    100             sb.append(from);
    101         }
    102         sb.append(':');
    103         if (replyTo != null) {
    104             sb.append(replyTo);
    105         }
    106         sb.append(':');
    107         if (subject != null) {
    108             sb.append(subject);
    109         }
    110         sb.append(':');
    111         sb.append(protocol).append(':').append(host).append(':').append("port").append(':');
    112         if (username != null) {
    113             sb.append(username);
    114         }
    115         sb.append(':');
    116         if (password != null) {
    117             sb.append(password);
    118         }
    119         sb.append(isDebug ? ":debug:" : "::");
    120         sb.append(filterName);
    121
    122         final String name = "SMTP:" + NameUtil.md5(sb.toString());
    123
    124         return getManager(name, FACTORY, new FactoryData(to, cc, bcc, from, replyTo, subject,
    125             protocol, host, port, username, password, isDebug, numElements));
    126     }
    127
    128     /**
    129      * Send the contents of the cyclic buffer as an e-mail message.
    130      * @param layout The layout for formatting the events.
    131      * @param appendEvent The event that triggered the send.
    132      */
    133     public void sendEvents(final Layout<?> layout, final LogEvent appendEvent) {
    134         if (message == null) {
    135             connect();
    136         }
    137         try {
    138             final LogEvent[] priorEvents = buffer.removeAll();
    139             // LOG4J-310: log appendEvent even if priorEvents is empty
    140
    141             final byte[] rawBytes = formatContentToBytes(priorEvents, appendEvent, layout);
    142
    143             final String contentType = layout.getContentType();
    144             final String encoding = getEncoding(rawBytes, contentType);
    145             final byte[] encodedBytes = encodeContentToBytes(rawBytes, encoding);
    146
    147             final InternetHeaders headers = getHeaders(contentType, encoding);
    148             final MimeMultipart mp = getMimeMultipart(encodedBytes, headers);
    149
    150             sendMultipartMessage(message, mp);
    151         } catch (final MessagingException 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 IOException e) {
    155             LOGGER.error("Error occurred while sending e-mail notification.", e);
    156             throw new LoggingException("Error occurred while sending email", e);
    157         } catch (final RuntimeException e) {
    158             LOGGER.error("Error occurred while sending e-mail notification.", e);
    159             throw new LoggingException("Error occurred while sending email", e);
    160         }
    161     }
    162
    163     protected byte[] formatContentToBytes(final LogEvent[] priorEvents, final LogEvent appendEvent,
    164                                           final Layout<?> layout) throws IOException {
    165         final ByteArrayOutputStream raw = new ByteArrayOutputStream();
    166         writeContent(priorEvents, appendEvent, layout, raw);
    167         return raw.toByteArray();
    168     }
    169
    170     private void writeContent(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
    171                               final ByteArrayOutputStream out)
    172         throws IOException {
    173         writeHeader(layout, out);
    174         writeBuffer(priorEvents, appendEvent, layout, out);
    175         writeFooter(layout, out);
    176     }
    177
    178     protected void writeHeader(final Layout<?> layout, final OutputStream out) throws IOException {
    179         final byte[] header = layout.getHeader();
    180         if (header != null) {
    181             out.write(header);
    182         }
    183     }
    184
    185     protected void writeBuffer(final LogEvent[] priorEvents, final LogEvent appendEvent, final Layout<?> layout,
    186                                final OutputStream out) throws IOException {
    187         for (final LogEvent priorEvent : priorEvents) {
    188             final byte[] bytes = layout.toByteArray(priorEvent);
    189             out.write(bytes);
    190         }
    191
    192         final byte[] bytes = layout.toByteArray(appendEvent);
    193         out.write(bytes);
    194     }
    195
    196     protected void writeFooter(final Layout<?> layout, final OutputStream out) throws IOException {
    197         final byte[] footer = layout.getFooter();
    198         if (footer != null) {
    199             out.write(footer);
    200         }
    201     }
    202
    203     protected String getEncoding(final byte[] rawBytes, final String contentType) {
    204         final DataSource dataSource = new ByteArrayDataSource(rawBytes, contentType);
    205         return MimeUtility.getEncoding(dataSource);
    206     }
    207
    208     protected byte[] encodeContentToBytes(final byte[] rawBytes, final String encoding)
    209         throws MessagingException, IOException {
    210         final ByteArrayOutputStream encoded = new ByteArrayOutputStream();
    211         encodeContent(rawBytes, encoding, encoded);
    212         return encoded.toByteArray();
    213     }
    214
    215     protected void encodeContent(final byte[] bytes, final String encoding, final ByteArrayOutputStream out)
    216         throws MessagingException, IOException {
    217         final OutputStream encoder = MimeUtility.encode(out, encoding);
    218         encoder.write(bytes);
    219         encoder.close();
    220     }
    221
    222     protected InternetHeaders getHeaders(final String contentType, final String encoding) {
    223         final InternetHeaders headers = new InternetHeaders();
    224         headers.setHeader("Content-Type", contentType + "; charset=UTF-8");
    225         headers.setHeader("Content-Transfer-Encoding", encoding);
    226         return headers;
    227     }
    228
    229     protected MimeMultipart getMimeMultipart(final byte[] encodedBytes, final InternetHeaders headers)
    230         throws MessagingException {
    231         final MimeMultipart mp = new MimeMultipart();
    232         final MimeBodyPart part = new MimeBodyPart(headers, encodedBytes);
    233         mp.addBodyPart(part);
    234         return mp;
    235     }
    236
    237     protected void sendMultipartMessage(final MimeMessage message, final MimeMultipart mp) throws MessagingException {
    238         synchronized (message) {
    239             message.setContent(mp);
    240             message.setSentDate(new Date());
    241             Transport.send(message);
    242         }
    243     }
    244
    245     /**
    246      * Factory data.
    247      */
    248     private static class FactoryData {
    249         private final String to;
    250         private final String cc;
    251         private final String bcc;
    252         private final String from;
    253         private final String replyto;
    254         private final String subject;
    255         private final String protocol;
    256         private final String host;
    257         private final int port;
    258         private final String username;
    259         private final String password;
    260         private final boolean isDebug;
    261         private final int numElements;
    262
    263         public FactoryData(final String to, final String cc, final String bcc, final String from, final String replyTo,
    264                            final String subject, final String protocol, final String host, final int port,
    265                            final String username, final String password, final boolean isDebug, final int numElements) {
    266             this.to = to;
    267             this.cc = cc;
    268             this.bcc = bcc;
    269             this.from = from;
    270             this.replyto = replyTo;
    271             this.subject = subject;
    272             this.protocol = protocol;
    273             this.host = host;
    274             this.port = port;
    275             this.username = username;
    276             this.password = password;
    277             this.isDebug = isDebug;
    278             this.numElements = numElements;
    279         }
    280     }
    281
    282     private synchronized void connect() {
    283         if (message != null) {
    284             return;
    285         }
    286         try {
    287             message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto)
    288                 .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc)
    289                 .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage();
    290         } catch (final MessagingException e) {
    291             LOGGER.error("Could not set SmtpAppender message options.", e);
    292             message = null;
    293         }
    294     }
    295
    296     /**
    297      * Factory to create the SMTP Manager.
    298      */
    299     private static class SMTPManagerFactory implements ManagerFactory<SmtpManager, FactoryData> {
    300
    301         @Override
    302         public SmtpManager createManager(final String name, final FactoryData data) {
    303             final String prefix = "mail." + data.protocol;
    304
    305             final Properties properties = PropertiesUtil.getSystemProperties();
    306             properties.put("mail.transport.protocol", data.protocol);
    307             if (properties.getProperty("mail.host") == null) {
    308                 // Prevent an UnknownHostException in Java 7
    309                 properties.put("mail.host", NetUtils.getLocalHostname());
    310             }
    311
    312             if (null != data.host) {
    313                 properties.put(prefix + ".host", data.host);
    314             }
    315             if (data.port > 0) {
    316                 properties.put(prefix + ".port", String.valueOf(data.port));
    317             }
    318
    319             final Authenticator authenticator = buildAuthenticator(data.username, data.password);
    320             if (null != authenticator) {
    321                 properties.put(prefix + ".auth", "true");
    322             }
    323
    324             final Session session = Session.getInstance(properties, authenticator);
    325             session.setProtocolForAddress("rfc822", data.protocol);
    326             session.setDebug(data.isDebug);
    327             MimeMessage message;
    328
    329             try {
    330                 message = new MimeMessageBuilder(session).setFrom(data.from).setReplyTo(data.replyto)
    331                     .setRecipients(Message.RecipientType.TO, data.to).setRecipients(Message.RecipientType.CC, data.cc)
    332                     .setRecipients(Message.RecipientType.BCC, data.bcc).setSubject(data.subject).getMimeMessage();
    333             } catch (final MessagingException e) {
    334                 LOGGER.error("Could not set SmtpAppender message options.", e);
    335                 message = null;
    336             }
    337
    338             return new SmtpManager(name, session, message, data);
    339         }
    340
    341         private Authenticator buildAuthenticator(final String username, final String password) {
    342             if (null != password && null != username) {
    343                 return new Authenticator() {
    344                     private final PasswordAuthentication passwordAuthentication =
    345                         new PasswordAuthentication(username, password);
    346
    347                     @Override
    348                     protected PasswordAuthentication getPasswordAuthentication() {
    349                         return passwordAuthentication;
    350                     }
    351                 };
    352             }
    353             return null;
    354         }
    355     }
    356 }