001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.net;
015
016import ch.qos.logback.classic.ClassicTestConstants;
017import ch.qos.logback.classic.Logger;
018import ch.qos.logback.classic.LoggerContext;
019import ch.qos.logback.classic.PatternLayout;
020import ch.qos.logback.classic.html.HTMLLayout;
021import ch.qos.logback.classic.html.XHTMLEntityResolver;
022import ch.qos.logback.classic.joran.JoranConfigurator;
023import ch.qos.logback.classic.spi.ILoggingEvent;
024import ch.qos.logback.core.Layout;
025import ch.qos.logback.core.joran.spi.JoranException;
026import ch.qos.logback.core.status.OnConsoleStatusListener;
027import ch.qos.logback.core.testUtil.EnvUtilForTests;
028import ch.qos.logback.core.testUtil.RandomUtil;
029import ch.qos.logback.core.util.StatusListenerConfigHelper;
030
031import com.icegreen.greenmail.util.GreenMail;
032import com.icegreen.greenmail.util.GreenMailUtil;
033import com.icegreen.greenmail.util.ServerSetup;
034
035import org.dom4j.DocumentException;
036import org.dom4j.io.SAXReader;
037import org.junit.After;
038import org.junit.Before;
039import org.junit.Test;
040import org.slf4j.MDC;
041
042import javax.mail.MessagingException;
043import javax.mail.internet.MimeMessage;
044import javax.mail.internet.MimeMultipart;
045
046import java.io.ByteArrayInputStream;
047import java.io.ByteArrayOutputStream;
048import java.io.IOException;
049import java.io.InputStream;
050import java.util.concurrent.TimeUnit;
051
052import static org.junit.Assert.*;
053
054public class SMTPAppender_GreenTest {
055
056    static final String HEADER = "HEADER\n";
057    static final String FOOTER = "FOOTER\n";
058    static final String DEFAULT_PATTERN = "%-4relative %mdc [%thread] %-5level %class - %msg%n";
059
060    static final boolean SYNCHRONOUS = false;
061    static final boolean ASYNCHRONOUS = true;
062
063    int port = RandomUtil.getRandomServerPort();
064    // GreenMail cannot be static. As a shared server induces race conditions
065    GreenMail greenMailServer;
066
067    SMTPAppender smtpAppender;
068    LoggerContext loggerContext = new LoggerContext();
069    Logger logger = loggerContext.getLogger(this.getClass());
070
071    @Before
072    public void setUp() throws Exception {
073
074        StatusListenerConfigHelper.addOnConsoleListenerInstance(loggerContext, new OnConsoleStatusListener());
075        MDC.clear();
076        ServerSetup serverSetup = new ServerSetup(port, "localhost", ServerSetup.PROTOCOL_SMTP);
077        greenMailServer = new GreenMail(serverSetup);
078        greenMailServer.start();
079        // give the server a head start
080        if (EnvUtilForTests.isRunningOnSlowJenkins()) {
081            Thread.sleep(2000);
082        } else {
083            Thread.sleep(50);
084        }
085    }
086
087    @After
088    public void tearDown() throws Exception {
089        greenMailServer.stop();
090    }
091
092    void buildSMTPAppender(String subject, boolean synchronicity) throws Exception {
093        smtpAppender = new SMTPAppender();
094        smtpAppender.setContext(loggerContext);
095        smtpAppender.setName("smtp");
096        smtpAppender.setFrom("user@host.dom");
097        smtpAppender.setSMTPHost("localhost");
098        smtpAppender.setSMTPPort(port);
099        smtpAppender.setSubject(subject);
100        smtpAppender.addTo("nospam@qos.ch");
101        smtpAppender.setAsynchronousSending(synchronicity);
102    }
103
104    private Layout<ILoggingEvent> buildPatternLayout(String pattern) {
105        PatternLayout layout = new PatternLayout();
106        layout.setContext(loggerContext);
107        layout.setFileHeader(HEADER);
108        layout.setOutputPatternAsHeader(false);
109        layout.setPattern(pattern);
110        layout.setFileFooter(FOOTER);
111        layout.start();
112        return layout;
113    }
114
115    private Layout<ILoggingEvent> buildHTMLLayout() {
116        HTMLLayout layout = new HTMLLayout();
117        layout.setContext(loggerContext);
118        layout.setPattern("%level%class%msg");
119        layout.start();
120        return layout;
121    }
122
123    private void waitForServerToReceiveEmails(int emailCount) throws InterruptedException {
124        greenMailServer.waitForIncomingEmail(5000, emailCount);
125    }
126
127    private MimeMultipart verifyAndExtractMimeMultipart(String subject) throws MessagingException, IOException, InterruptedException {
128        int oldCount = 0;
129        int expectedEmailCount = 1;
130        // wait for the server to receive the messages
131        waitForServerToReceiveEmails(expectedEmailCount);
132        MimeMessage[] mma = greenMailServer.getReceivedMessages();
133        assertNotNull(mma);
134        assertEquals(expectedEmailCount, mma.length);
135        MimeMessage mm = mma[oldCount];
136        // http://jira.qos.ch/browse/LBCLASSIC-67
137        assertEquals(subject, mm.getSubject());
138        return (MimeMultipart) mm.getContent();
139    }
140
141    void waitUntilEmailIsSent() throws InterruptedException {
142        loggerContext.getExecutorService().shutdown();
143        loggerContext.getExecutorService().awaitTermination(1000, TimeUnit.MILLISECONDS);
144    }
145
146    @Test
147    public void synchronousSmoke() throws Exception {
148        String subject = "synchronousSmoke";
149        buildSMTPAppender(subject, SYNCHRONOUS);
150
151        smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
152        smtpAppender.start();
153        logger.addAppender(smtpAppender);
154        logger.debug("hello");
155        logger.error("en error", new Exception("an exception"));
156
157        MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
158        String body = GreenMailUtil.getBody(mp.getBodyPart(0));
159        assertTrue(body.startsWith(HEADER.trim()));
160        assertTrue(body.endsWith(FOOTER.trim()));
161    }
162
163    @Test
164    public void asynchronousSmoke() throws Exception {
165        String subject = "asynchronousSmoke";
166        buildSMTPAppender(subject, ASYNCHRONOUS);
167        smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
168        smtpAppender.start();
169        logger.addAppender(smtpAppender);
170        logger.debug("hello");
171        logger.error("en error", new Exception("an exception"));
172
173        waitUntilEmailIsSent();
174        MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
175        String body = GreenMailUtil.getBody(mp.getBodyPart(0));
176        assertTrue(body.startsWith(HEADER.trim()));
177        assertTrue(body.endsWith(FOOTER.trim()));
178    }
179
180    // See also http://jira.qos.ch/browse/LOGBACK-734
181    @Test
182    public void callerDataShouldBeCorrectlySetWithAsynchronousSending() throws Exception {
183        String subject = "LOGBACK-734";
184        buildSMTPAppender("LOGBACK-734", ASYNCHRONOUS);
185        smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
186        smtpAppender.setIncludeCallerData(true);
187        smtpAppender.start();
188        logger.addAppender(smtpAppender);
189        logger.debug("LOGBACK-734");
190        logger.error("callerData", new Exception("ShouldBeCorrectlySetWithAsynchronousSending"));
191
192        waitUntilEmailIsSent();
193        MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
194        String body = GreenMailUtil.getBody(mp.getBodyPart(0));
195        assertTrue("actual [" + body + "]", body.contains("DEBUG " + this.getClass().getName() + " - LOGBACK-734"));
196    }
197
198    // lost MDC
199    @Test
200    public void LBCLASSIC_104() throws Exception {
201        String subject = "LBCLASSIC_104";
202        buildSMTPAppender(subject, SYNCHRONOUS);
203        smtpAppender.setAsynchronousSending(false);
204        smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
205        smtpAppender.start();
206        logger.addAppender(smtpAppender);
207        MDC.put("key", "val");
208        logger.debug("LBCLASSIC_104");
209        MDC.clear();
210        logger.error("en error", new Exception("test"));
211
212        MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
213        String body = GreenMailUtil.getBody(mp.getBodyPart(0));
214        assertTrue(body.startsWith(HEADER.trim()));
215        System.out.println(body);
216        assertTrue(body.contains("key=val"));
217        assertTrue(body.endsWith(FOOTER.trim()));
218    }
219
220    @Test
221    public void html() throws Exception {
222        String subject = "html";
223        buildSMTPAppender(subject, SYNCHRONOUS);
224        smtpAppender.setAsynchronousSending(false);
225        smtpAppender.setLayout(buildHTMLLayout());
226        smtpAppender.start();
227        logger.addAppender(smtpAppender);
228        logger.debug("html");
229        logger.error("en error", new Exception("an exception"));
230
231        MimeMultipart mp = verifyAndExtractMimeMultipart(subject);
232
233        // verifyAndExtractMimeMultipart strict adherence to xhtml1-strict.dtd
234        SAXReader reader = new SAXReader();
235        reader.setValidation(true);
236        reader.setEntityResolver(new XHTMLEntityResolver());
237        byte[] messageBytes = getAsByteArray(mp.getBodyPart(0).getInputStream());
238        ByteArrayInputStream bais = new ByteArrayInputStream(messageBytes);
239        try {
240            reader.read(bais);
241        } catch (DocumentException de) {
242            System.out.println("incoming message:");
243            System.out.println(new String(messageBytes));
244            throw de;
245        }
246    }
247
248    private byte[] getAsByteArray(InputStream inputStream) throws IOException {
249        ByteArrayOutputStream baos = new ByteArrayOutputStream();
250
251        byte[] buffer = new byte[1024];
252        int n = -1;
253        while ((n = inputStream.read(buffer)) != -1) {
254            baos.write(buffer, 0, n);
255        }
256        return baos.toByteArray();
257    }
258
259    private void configure(String file) throws JoranException {
260        JoranConfigurator jc = new JoranConfigurator();
261        jc.setContext(loggerContext);
262        loggerContext.putProperty("port", "" + port);
263        jc.doConfigure(file);
264    }
265
266    @Test
267    public void testCustomEvaluator() throws Exception {
268        configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "smtp/customEvaluator.xml");
269
270        logger.debug("test");
271        String msg2 = "CustomEvaluator";
272        logger.debug(msg2);
273        logger.debug("invisible");
274        waitUntilEmailIsSent();
275        MimeMultipart mp = verifyAndExtractMimeMultipart("testCustomEvaluator " + this.getClass().getName() + " - " + msg2);
276        String body = GreenMailUtil.getBody(mp.getBodyPart(0));
277        assertEquals("testCustomEvaluator", body);
278    }
279
280    @Test
281    public void testCustomBufferSize() throws Exception {
282        configure(ClassicTestConstants.JORAN_INPUT_PREFIX + "smtp/customBufferSize.xml");
283
284        logger.debug("invisible1");
285        logger.debug("invisible2");
286        String msg = "hello";
287        logger.error(msg);
288        waitUntilEmailIsSent();
289        MimeMultipart mp = verifyAndExtractMimeMultipart("testCustomBufferSize " + this.getClass().getName() + " - " + msg);
290        String body = GreenMailUtil.getBody(mp.getBodyPart(0));
291        assertEquals(msg, body);
292    }
293
294    // this test fails intermittently on Jenkins.
295    @Test
296    public void testMultipleTo() throws Exception {
297        buildSMTPAppender("testMultipleTo", SYNCHRONOUS);
298        smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
299        // buildSMTPAppender() already added one destination address
300        smtpAppender.addTo("Test <test@example.com>, other-test@example.com");
301        smtpAppender.start();
302        logger.addAppender(smtpAppender);
303        logger.debug("testMultipleTo hello");
304        logger.error("testMultipleTo en error", new Exception("an exception"));
305        Thread.yield();
306        int expectedEmailCount = 3;
307        waitForServerToReceiveEmails(expectedEmailCount);
308        MimeMessage[] mma = greenMailServer.getReceivedMessages();
309        assertNotNull(mma);
310        assertEquals(expectedEmailCount, mma.length);
311    }
312
313    // http://jira.qos.ch/browse/LBCLASSIC-221
314    @Test
315    public void bufferShouldBeResetBetweenMessages() throws Exception {
316        buildSMTPAppender("bufferShouldBeResetBetweenMessages", SYNCHRONOUS);
317        smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
318        smtpAppender.start();
319        logger.addAppender(smtpAppender);
320        String msg0 = "hello zero";
321        logger.debug(msg0);
322        logger.error("error zero");
323
324        String msg1 = "hello one";
325        logger.debug(msg1);
326        logger.error("error one");
327
328        Thread.yield();
329        int oldCount = 0;
330        int expectedEmailCount = oldCount + 2;
331        waitForServerToReceiveEmails(expectedEmailCount);
332
333        MimeMessage[] mma = greenMailServer.getReceivedMessages();
334        assertNotNull(mma);
335        assertEquals(expectedEmailCount, mma.length);
336
337        MimeMessage mm0 = mma[oldCount];
338        MimeMultipart content0 = (MimeMultipart) mm0.getContent();
339        @SuppressWarnings("unused")
340        String body0 = GreenMailUtil.getBody(content0.getBodyPart(0));
341
342        MimeMessage mm1 = mma[oldCount + 1];
343        MimeMultipart content1 = (MimeMultipart) mm1.getContent();
344        String body1 = GreenMailUtil.getBody(content1.getBodyPart(0));
345        // second body should not contain content from first message
346        assertFalse(body1.contains(msg0));
347    }
348
349    @Test
350    public void multiLineSubjectTruncatedAtFirstNewLine() throws Exception {
351        String line1 = "line 1 of subject";
352        String subject = line1 + "\nline 2 of subject\n";
353        buildSMTPAppender(subject, ASYNCHRONOUS);
354
355        smtpAppender.setLayout(buildPatternLayout(DEFAULT_PATTERN));
356        smtpAppender.start();
357        logger.addAppender(smtpAppender);
358        logger.debug("hello");
359        logger.error("en error", new Exception("an exception"));
360
361        Thread.yield();
362        waitUntilEmailIsSent();
363        waitForServerToReceiveEmails(1);
364
365        MimeMessage[] mma = greenMailServer.getReceivedMessages();
366        assertEquals(1, mma.length);
367        assertEquals(line1, mma[0].getSubject());
368    }
369}