diff --git a/logback-core/src/main/java/ch/qos/logback/core/encoder/LayoutWrappingEncoder.java b/logback-core/src/main/java/ch/qos/logback/core/encoder/LayoutWrappingEncoder.java index 7a8e9ddf9e..41c44f7c5f 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/encoder/LayoutWrappingEncoder.java +++ b/logback-core/src/main/java/ch/qos/logback/core/encoder/LayoutWrappingEncoder.java @@ -111,6 +111,9 @@ private byte[] convertToBytes(String s) { } public byte[] encode(E event) { + if (layout == null) { + return null; + } String txt = layout.doLayout(event); return convertToBytes(txt); } @@ -120,6 +123,10 @@ public boolean isStarted() { } public void start() { + if (layout == null) { + addError("No layout set for the encoder. This encoder will produce no output. " + + "Please set a layout, or check for an ignored // block in your configuration."); + } if (immediateFlush != null) { if (parent instanceof OutputStreamAppender) { addWarn("Setting the \"immediateFlush\" property of the enclosing appender to " + immediateFlush); diff --git a/logback-core/src/test/java/ch/qos/logback/core/encoder/LayoutWrappingEncoderTest.java b/logback-core/src/test/java/ch/qos/logback/core/encoder/LayoutWrappingEncoderTest.java new file mode 100644 index 0000000000..6acd546f9b --- /dev/null +++ b/logback-core/src/test/java/ch/qos/logback/core/encoder/LayoutWrappingEncoderTest.java @@ -0,0 +1,67 @@ +/* + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2026, QOS.ch. All rights reserved. + * + * This program and the accompanying materials are dual-licensed under + * either the terms of the Eclipse Public License v2.0 as published by + * the Eclipse Foundation + * + * or (per the licensee's choosing) + * + * under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation. + */ +package ch.qos.logback.core.encoder; + +import ch.qos.logback.core.Context; +import ch.qos.logback.core.ContextBase; +import ch.qos.logback.core.LayoutBase; +import ch.qos.logback.core.status.Status; +import ch.qos.logback.core.status.testUtil.StatusChecker; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +public class LayoutWrappingEncoderTest { + + Context context = new ContextBase(); + StatusChecker statusChecker = new StatusChecker(context); + LayoutWrappingEncoder encoder = new LayoutWrappingEncoder(); + + static class EchoLayout extends LayoutBase { + public String doLayout(Object event) { + return String.valueOf(event); + } + } + + @BeforeEach + public void setUp() { + encoder.setContext(context); + } + + // https://github.com/qos-ch/logback/issues/1046 + // A wrapping encoder left without a layout (e.g. when an // + // block is silently ignored) used to start "successfully" and then throw a + // NullPointerException on every logging event, causing silent log loss. + @Test + public void nullLayoutReportsErrorOnStartAndDoesNotThrowOnEncode() { + encoder.start(); + statusChecker.assertContainsMatch(Status.ERROR, "No layout set for the encoder"); + + byte[] result = assertDoesNotThrow(() -> encoder.encode(new Object())); + assertNull(result); + } + + @Test + public void encodesWhenLayoutIsSet() { + encoder.setLayout(new EchoLayout()); + encoder.start(); + statusChecker.assertIsErrorFree(); + + byte[] result = encoder.encode("hello"); + assertEquals("hello", new String(result)); + } +}