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