001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017
018package org.apache.logging.log4j.core.appender;
019
020import java.io.Serializable;
021
022import org.apache.logging.log4j.core.Appender;
023import org.apache.logging.log4j.core.Core;
024import org.apache.logging.log4j.core.Filter;
025import org.apache.logging.log4j.core.Layout;
026import org.apache.logging.log4j.core.LogEvent;
027import org.apache.logging.log4j.core.config.Configuration;
028import org.apache.logging.log4j.core.config.DefaultConfiguration;
029import org.apache.logging.log4j.core.config.Property;
030import org.apache.logging.log4j.core.config.plugins.Plugin;
031import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
032import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
033import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
034import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
035import org.apache.logging.log4j.core.config.plugins.PluginElement;
036import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
037import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
038import org.apache.logging.log4j.core.filter.ThresholdFilter;
039import org.apache.logging.log4j.core.layout.HtmlLayout;
040import org.apache.logging.log4j.core.net.SmtpManager;
041import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
042import org.apache.logging.log4j.core.util.Booleans;
043import org.apache.logging.log4j.core.util.Integers;
044
045/**
046 * Send an e-mail when a specific logging event occurs, typically on errors or
047 * fatal errors.
048 *
049 * <p>
050 * The number of logging events delivered in this e-mail depend on the value of
051 * <b>BufferSize</b> option. The <code>SmtpAppender</code> keeps only the last
052 * <code>BufferSize</code> logging events in its cyclic buffer. This keeps
053 * memory requirements at a reasonable level while still delivering useful
054 * application context.
055 *
056 * By default, an email message will formatted as HTML. This can be modified by
057 * setting a layout for the appender.
058 *
059 * By default, an email message will be sent when an ERROR or higher severity
060 * message is appended. This can be modified by setting a filter for the
061 * appender.
062 */
063@Plugin(name = "SMTP", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
064public final class SmtpAppender extends AbstractAppender {
065
066    private static final int DEFAULT_BUFFER_SIZE = 512;
067
068    /** The SMTP Manager */
069    private final SmtpManager manager;
070
071    private SmtpAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
072            final SmtpManager manager, final boolean ignoreExceptions, final Property[] properties) {
073        super(name, filter, layout, ignoreExceptions, properties);
074        this.manager = manager;
075    }
076
077    /**
078     * @since 2.13.2
079     */
080    public static class Builder extends AbstractAppender.Builder<Builder>
081            implements org.apache.logging.log4j.core.util.Builder<SmtpAppender> {
082        @PluginBuilderAttribute
083        private String to;
084
085        @PluginBuilderAttribute
086        private String cc;
087
088        @PluginBuilderAttribute
089        private String bcc;
090
091        @PluginBuilderAttribute
092        private String from;
093
094        @PluginBuilderAttribute
095        private String replyTo;
096
097        @PluginBuilderAttribute
098        private String subject;
099
100        @PluginBuilderAttribute
101        private String smtpProtocol = "smtp";
102
103        @PluginBuilderAttribute
104        private String smtpHost;
105
106        @PluginBuilderAttribute
107        @ValidPort
108        private int smtpPort;
109
110        @PluginBuilderAttribute
111        private String smtpUsername;
112
113        @PluginBuilderAttribute(sensitive = true)
114        private String smtpPassword;
115
116        @PluginBuilderAttribute
117        private boolean smtpDebug;
118
119        @PluginBuilderAttribute
120        private int bufferSize = DEFAULT_BUFFER_SIZE;
121
122        @PluginElement("SSL")
123        private SslConfiguration sslConfiguration;
124
125        /**
126         * Comma-separated list of recipient email addresses.
127         */
128        public Builder setTo(final String to) {
129            this.to = to;
130            return this;
131        }
132
133        /**
134         * Comma-separated list of CC email addresses.
135         */
136        public Builder setCc(final String cc) {
137            this.cc = cc;
138            return this;
139        }
140
141        /**
142         * Comma-separated list of BCC email addresses.
143         */
144        public Builder setBcc(final String bcc) {
145            this.bcc = bcc;
146            return this;
147        }
148
149        /**
150         * Email address of the sender.
151         */
152        public Builder setFrom(final String from) {
153            this.from = from;
154            return this;
155        }
156
157        /**
158         * Comma-separated list of Reply-To email addresses.
159         */
160        public Builder setReplyTo(final String replyTo) {
161            this.replyTo = replyTo;
162            return this;
163        }
164
165        /**
166         * Subject template for the email messages.
167         * @see org.apache.logging.log4j.core.layout.PatternLayout
168         */
169        public Builder setSubject(final String subject) {
170            this.subject = subject;
171            return this;
172        }
173
174        /**
175         * Transport protocol to use for SMTP such as "smtp" or "smtps". Defaults to "smtp".
176         */
177        public Builder setSmtpProtocol(final String smtpProtocol) {
178            this.smtpProtocol = smtpProtocol;
179            return this;
180        }
181
182        /**
183         * Host name of SMTP server to send messages through.
184         */
185        public Builder setSmtpHost(final String smtpHost) {
186            this.smtpHost = smtpHost;
187            return this;
188        }
189
190        /**
191         * Port number of SMTP server to send messages through.
192         */
193        public Builder setSmtpPort(final int smtpPort) {
194            this.smtpPort = smtpPort;
195            return this;
196        }
197
198        /**
199         * Username to authenticate with SMTP server.
200         */
201        public Builder setSmtpUsername(final String smtpUsername) {
202            this.smtpUsername = smtpUsername;
203            return this;
204        }
205
206        /**
207         * Password to authenticate with SMTP server.
208         */
209        public Builder setSmtpPassword(final String smtpPassword) {
210            this.smtpPassword = smtpPassword;
211            return this;
212        }
213
214        /**
215         * Enables or disables mail session debugging on STDOUT. Disabled by default.
216         */
217        public Builder setSmtpDebug(final boolean smtpDebug) {
218            this.smtpDebug = smtpDebug;
219            return this;
220        }
221
222        /**
223         * Number of log events to buffer before sending an email. Defaults to {@value #DEFAULT_BUFFER_SIZE}.
224         */
225        public Builder setBufferSize(final int bufferSize) {
226            this.bufferSize = bufferSize;
227            return this;
228        }
229
230        /**
231         * Specifies an SSL configuration for smtps connections.
232         */
233        public Builder setSslConfiguration(final SslConfiguration sslConfiguration) {
234            this.sslConfiguration = sslConfiguration;
235            return this;
236        }
237
238        /**
239         * Specifies the layout used for the email message body. By default, this uses the
240         * {@linkplain HtmlLayout#createDefaultLayout() default HTML layout}.
241         */
242        @Override
243        public Builder setLayout(final Layout<? extends Serializable> layout) {
244            return super.setLayout(layout);
245        }
246
247        /**
248         * Specifies the filter used for this appender. By default, uses a {@link ThresholdFilter} with a level of
249         * ERROR.
250         */
251        @Override
252        public Builder setFilter(final Filter filter) {
253            return super.setFilter(filter);
254        }
255
256        @Override
257        public SmtpAppender build() {
258            if (getLayout() == null) {
259                setLayout(HtmlLayout.createDefaultLayout());
260            }
261            if (getFilter() == null) {
262                setFilter(ThresholdFilter.createFilter(null, null, null));
263            }
264            final SmtpManager smtpManager = SmtpManager.getSmtpManager(getConfiguration(), to, cc, bcc, from, replyTo,
265                    subject, smtpProtocol, smtpHost, smtpPort, smtpUsername, smtpPassword, smtpDebug,
266                    getFilter().toString(), bufferSize, sslConfiguration);
267            return new SmtpAppender(getName(), getFilter(), getLayout(), smtpManager, isIgnoreExceptions(), getPropertyArray());
268        }
269    }
270
271    /**
272     * @since 2.13.2
273     */
274    @PluginBuilderFactory
275    public static Builder newBuilder() {
276        return new Builder();
277    }
278
279    /**
280     * Create a SmtpAppender.
281     * @deprecated Use {@link #newBuilder()} to create and configure a {@link Builder} instance.
282     * @see Builder
283     */
284    @Deprecated
285    public static SmtpAppender createAppender(
286            @PluginConfiguration final Configuration config,
287            @PluginAttribute("name") @Required final String name,
288            @PluginAttribute("to") final String to,
289            @PluginAttribute("cc") final String cc,
290            @PluginAttribute("bcc") final String bcc,
291            @PluginAttribute("from") final String from,
292            @PluginAttribute("replyTo") final String replyTo,
293            @PluginAttribute("subject") final String subject,
294            @PluginAttribute("smtpProtocol") final String smtpProtocol,
295            @PluginAttribute("smtpHost") final String smtpHost,
296            @PluginAttribute(value = "smtpPort", defaultString = "0") @ValidPort final String smtpPortStr,
297            @PluginAttribute("smtpUsername") final String smtpUsername,
298            @PluginAttribute(value = "smtpPassword", sensitive = true) final String smtpPassword,
299            @PluginAttribute("smtpDebug") final String smtpDebug,
300            @PluginAttribute("bufferSize") final String bufferSizeStr,
301            @PluginElement("Layout") Layout<? extends Serializable> layout,
302            @PluginElement("Filter") Filter filter,
303            @PluginAttribute("ignoreExceptions") final String ignore) {
304        if (name == null) {
305            LOGGER.error("No name provided for SmtpAppender");
306            return null;
307        }
308
309        final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
310        final int smtpPort = AbstractAppender.parseInt(smtpPortStr, 0);
311        final boolean isSmtpDebug = Boolean.parseBoolean(smtpDebug);
312        final int bufferSize = bufferSizeStr == null ? DEFAULT_BUFFER_SIZE : Integers.parseInt(bufferSizeStr);
313
314        if (layout == null) {
315            layout = HtmlLayout.createDefaultLayout();
316        }
317        if (filter == null) {
318            filter = ThresholdFilter.createFilter(null, null, null);
319        }
320        final Configuration configuration = config != null ? config : new DefaultConfiguration();
321
322        final SmtpManager manager = SmtpManager.getSmtpManager(configuration, to, cc, bcc, from, replyTo, subject, smtpProtocol,
323            smtpHost, smtpPort, smtpUsername, smtpPassword, isSmtpDebug, filter.toString(),  bufferSize, null);
324        if (manager == null) {
325            return null;
326        }
327
328        return new SmtpAppender(name, filter, layout, manager, ignoreExceptions, null);
329    }
330
331    /**
332     * Capture all events in CyclicBuffer.
333     * @param event The Log event.
334     * @return true if the event should be filtered.
335     */
336    @Override
337    public boolean isFiltered(final LogEvent event) {
338        final boolean filtered = super.isFiltered(event);
339        if (filtered) {
340            manager.add(event);
341        }
342        return filtered;
343    }
344
345    /**
346     * Perform SmtpAppender specific appending actions, mainly adding the event
347     * to a cyclic buffer and checking if the event triggers an e-mail to be
348     * sent.
349     * @param event The Log event.
350     */
351    @Override
352    public void append(final LogEvent event) {
353        manager.sendEvents(getLayout(), event);
354    }
355}