001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2023, 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 */
014
015package ch.qos.logback.classic.encoder;
016
017import ch.qos.logback.classic.spi.ILoggingEvent;
018import ch.qos.logback.classic.spi.IThrowableProxy;
019import ch.qos.logback.classic.spi.LoggerContextVO;
020import ch.qos.logback.classic.spi.StackTraceElementProxy;
021import ch.qos.logback.core.CoreConstants;
022import ch.qos.logback.core.encoder.EncoderBase;
023import org.slf4j.Marker;
024import org.slf4j.event.KeyValuePair;
025
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
031import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
032import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
033import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
034import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
035import static ch.qos.logback.core.model.ModelConstants.NULL_STR;
036
037/**
038 * JSON encoder that produces one JSON object per line in JSON Lines format, suitable for structured logging.
039 * Each {@link ILoggingEvent} is encoded into a JSON object containing fields such as timestamp, level, message,
040 * and optional elements like MDC properties, markers, and stack traces.
041 *
042 * <p>This encoder supports extensive configuration through boolean flags to include or exclude specific fields
043 * in the output, allowing customization for different logging needs. For example, you can enable/disable
044 * sequence numbers, nanoseconds, thread names, logger context, markers, MDC, key-value pairs, arguments,
045 * and throwable information.</p>
046 *
047 * <p>The encoder is designed for extensibility: subclasses can override protected methods (e.g.,
048 * {@link #appendLoggerContext}, {@link #appendThrowableProxy}, {@link #appendMarkers}) to customize
049 * how specific parts of the JSON are generated. Additionally, the {@link #appendCustomFields} hook
050 * allows appending custom top-level fields to the JSON object.</p>
051 *
052 * <h3>Configuration</h3>
053 * <p>Use the setter methods (e.g., {@link #setWithSequenceNumber}, {@link #setWithTimestamp}) to control
054 * which fields are included. By default, most fields are enabled except {@code withFormattedMessage}.</p>
055 *
056 * <h3>Example Usage</h3>
057 * <pre>{@code
058 * <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
059 *   <encoder class="ch.qos.logback.classic.encoder.JsonEncoder">
060 *     <withSequenceNumber>false</withSequenceNumber>
061 *     <withNanoseconds>false</withNanoseconds>
062 *     <withThreadName>false</withThreadName>
063 *   </encoder>
064 * </appender>
065 * }</pre>
066 *
067 * <p>This produces output similar to the following (on a single line):
068 * </p>
069 *
070 * <pre>{"timestamp":1640995200000,"level":"INFO","loggerName":"com.example.MyClass","context":{"name":"default","birthdate":1640995200000,"properties":{}},"message":"Hello World"}</pre>
071 *
072 * @see <a href="https://jsonlines.org/">JSON Lines</a>
073 * @see <a href="https://datatracker.ietf.org/doc/html/rfc8259">RFC 8259 (The JavaScript Object Notation (JSON) Data Interchange Format)</a>
074 * @see ch.qos.logback.core.encoder.EncoderBase
075 * @since 1.3.8/1.4.8
076 * @author Ceki G&uuml;lc&uuml;
077 */
078public class JsonEncoder extends EncoderBase<ILoggingEvent> {
079    static final boolean DO_NOT_ADD_QUOTE_KEY = false;
080    static final boolean ADD_QUOTE_KEY = true;
081    static int DEFAULT_SIZE = 1024;
082    static int DEFAULT_SIZE_WITH_THROWABLE = DEFAULT_SIZE * 8;
083
084    static byte[] EMPTY_BYTES = new byte[0];
085
086    public static final String CONTEXT_ATTR_NAME = "context";
087    public static final String NAME_ATTR_NAME = "name";
088    public static final String BIRTHDATE_ATTR_NAME = "birthdate";
089    public static final String CONTEXT_PROPERTIES_ATTR_NAME = "properties";
090
091    public static final String TIMESTAMP_ATTR_NAME = "timestamp";
092
093    public static final String NANOSECONDS_ATTR_NAME = "nanoseconds";
094
095    public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumber";
096
097    public static final String LEVEL_ATTR_NAME = "level";
098    public static final String MARKERS_ATTR_NAME = "markers";
099    public static final String THREAD_NAME_ATTR_NAME = "threadName";
100    public static final String MDC_ATTR_NAME = "mdc";
101    public static final String LOGGER_ATTR_NAME = "loggerName";
102
103    public static final String MESSAGE_ATTR_NAME = "message";
104
105    public static final String FORMATTED_MESSAGE_ATTR_NAME = "formattedMessage";
106
107    public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments";
108    public static final String KEY_VALUE_PAIRS_ATTR_NAME = "kvpList";
109
110    public static final String THROWABLE_ATTR_NAME = "throwable";
111
112    private static final String CYCLIC_THROWABLE_ATTR_NAME = "cyclic";
113
114    public static final String CAUSE_ATTR_NAME = "cause";
115
116    public static final String SUPPRESSED_ATTR_NAME = "suppressed";
117
118    public static final String COMMON_FRAMES_COUNT_ATTR_NAME = "commonFramesCount";
119
120    public static final String CLASS_NAME_ATTR_NAME = "className";
121    public static final String METHOD_NAME_ATTR_NAME = "methodName";
122    private static final String FILE_NAME_ATTR_NAME = "fileName";
123    private static final String LINE_NUMBER_ATTR_NAME = "lineNumber";
124
125    public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
126
127    protected static final char OPEN_OBJ = '{';
128    protected static final char CLOSE_OBJ = '}';
129    protected static final char OPEN_ARRAY = '[';
130    protected static final char CLOSE_ARRAY = ']';
131
132    protected static final char QUOTE = DOUBLE_QUOTE_CHAR;
133    protected static final char SP = ' ';
134    protected static final char ENTRY_SEPARATOR = COLON_CHAR;
135
136    protected static final String COL_SP = ": ";
137
138    protected static final String QUOTE_COL = "\":";
139
140    protected static final char VALUE_SEPARATOR = COMMA_CHAR;
141
142    private boolean withSequenceNumber = true;
143
144    private boolean withTimestamp = true;
145    private boolean withNanoseconds = true;
146
147    private boolean withLevel = true;
148    private boolean withThreadName = true;
149    private boolean withLoggerName = true;
150    private boolean withContext = true;
151    private boolean withMarkers = true;
152    private boolean withMDC = true;
153    private boolean withKVPList = true;
154    private boolean withMessage = true;
155    private boolean withArguments = true;
156    private boolean withThrowable = true;
157    private boolean withFormattedMessage = false;
158
159    @Override
160    public byte[] headerBytes() {
161        return EMPTY_BYTES;
162    }
163
164    @Override
165    public byte[] encode(ILoggingEvent event) {
166        final int initialCapacity = event.getThrowableProxy() == null ? DEFAULT_SIZE : DEFAULT_SIZE_WITH_THROWABLE;
167        StringBuilder sb = new StringBuilder(initialCapacity);
168        sb.append(OPEN_OBJ);
169
170        if (withSequenceNumber) {
171            appenderMemberWithLongValue(sb, SEQUENCE_NUMBER_ATTR_NAME, event.getSequenceNumber());
172        }
173
174        if (withTimestamp) {
175            appendValueSeparator(sb, withSequenceNumber);
176            appenderMemberWithLongValue(sb, TIMESTAMP_ATTR_NAME, event.getTimeStamp());
177        }
178
179        if (withNanoseconds) {
180            appendValueSeparator(sb, withSequenceNumber, withTimestamp);
181            appenderMemberWithLongValue(sb, NANOSECONDS_ATTR_NAME, event.getNanoseconds());
182        }
183
184        if (withLevel) {
185            appendValueSeparator(sb, withNanoseconds, withSequenceNumber, withTimestamp);
186            String levelStr = event.getLevel() != null ? event.getLevel().levelStr : NULL_STR;
187            appenderMember(sb, LEVEL_ATTR_NAME, levelStr);
188        }
189
190        if (withThreadName) {
191            appendValueSeparator(sb, withLevel, withNanoseconds, withSequenceNumber, withTimestamp);
192            appenderMember(sb, THREAD_NAME_ATTR_NAME, jsonEscape(event.getThreadName()));
193        }
194
195        if (withLoggerName) {
196            appendValueSeparator(sb, withThreadName, withLevel, withNanoseconds, withSequenceNumber, withTimestamp);
197            appenderMember(sb, LOGGER_ATTR_NAME, event.getLoggerName());
198        }
199
200        if (withContext) {
201            // at this stage we assume that at least one field was written
202            sb.append(VALUE_SEPARATOR);
203            appendLoggerContext(sb, event.getLoggerContextVO());
204        }
205
206        if (withMarkers)
207            appendMarkers(sb, event);
208
209        if (withMDC)
210            appendMDC(sb, event);
211
212        if (withKVPList)
213            appendKeyValuePairs(sb, event);
214
215        if (withMessage) {
216            sb.append(VALUE_SEPARATOR);
217            appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(event.getMessage()));
218        }
219
220        if (withFormattedMessage) {
221            sb.append(VALUE_SEPARATOR);
222            appenderMember(sb, FORMATTED_MESSAGE_ATTR_NAME, jsonEscape(event.getFormattedMessage()));
223        }
224
225        if (withArguments) {
226            appendArgumentArray(sb, event);
227        }
228
229        if (withThrowable)
230            appendThrowableProxy(sb, THROWABLE_ATTR_NAME, event.getThrowableProxy());
231
232        // allow subclasses to append custom top-level fields; default implementation is a no-op
233        appendCustomFields(sb, event);
234
235        sb.append(CLOSE_OBJ);
236        sb.append(CoreConstants.JSON_LINE_SEPARATOR);
237        return sb.toString().getBytes(UTF_8_CHARSET);
238    }
239
240    /**
241     * Append a JSON value separator (a comma) to the provided {@link StringBuilder}
242     * when any of the supplied boolean flags indicate that a subsequent element
243     * is present.
244     *
245     * <p>Callers pass a sequence of booleans that represent whether subsequent
246     * JSON members will be written. If at least one of those booleans is
247     * {@code true}, this method appends a single comma (',') to separate JSON
248     * fields.</p>
249     *
250     * <p>This method is protected so subclasses that extend the encoder can
251     * reuse or override the logic for inserting separators between generated
252     * JSON members.</p>
253     *
254     * @param sb the {@link StringBuilder} to append the separator to; must not be {@code null}
255     * @param subsequentConditionals one or more booleans indicating whether
256     *                               subsequent JSON elements will be written.
257     *                               If any value is {@code true}, a comma is appended.
258     */
259    protected void appendValueSeparator(StringBuilder sb, boolean... subsequentConditionals) {
260        boolean enabled = false;
261        for (boolean subsequent : subsequentConditionals) {
262            if (subsequent) {
263                enabled = true;
264                break;
265            }
266        }
267
268        if (enabled)
269            sb.append(VALUE_SEPARATOR);
270    }
271
272    protected void appendLoggerContext(StringBuilder sb, LoggerContextVO loggerContextVO) {
273
274        sb.append(QUOTE).append(CONTEXT_ATTR_NAME).append(QUOTE_COL);
275        if (loggerContextVO == null) {
276            sb.append(NULL_STR);
277            return;
278        }
279
280        sb.append(OPEN_OBJ);
281        appenderMember(sb, NAME_ATTR_NAME, nullSafeStr(loggerContextVO.getName()));
282        sb.append(VALUE_SEPARATOR);
283        appenderMemberWithLongValue(sb, BIRTHDATE_ATTR_NAME, loggerContextVO.getBirthTime());
284        sb.append(VALUE_SEPARATOR);
285
286        appendMap(sb, CONTEXT_PROPERTIES_ATTR_NAME, loggerContextVO.getPropertyMap());
287        sb.append(CLOSE_OBJ);
288
289    }
290
291    protected void appendMap(StringBuilder sb, String attrName, Map<String, String> map) {
292        sb.append(QUOTE).append(attrName).append(QUOTE_COL);
293        if (map == null) {
294            sb.append(NULL_STR);
295            return;
296        }
297
298        sb.append(OPEN_OBJ);
299
300        boolean addComma = false;
301        Set<Map.Entry<String, String>> entries = map.entrySet();
302        for (Map.Entry<String, String> entry : entries) {
303            if (addComma) {
304                sb.append(VALUE_SEPARATOR);
305            }
306            addComma = true;
307            appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
308        }
309
310        sb.append(CLOSE_OBJ);
311    }
312
313    protected void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {
314        appendThrowableProxy(sb, attributeName, itp, true);
315    }
316
317    protected void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp, boolean appendValueSeparator) {
318
319        if (appendValueSeparator)
320            sb.append(VALUE_SEPARATOR);
321
322        // in the nominal case, attributeName != null. However, attributeName will be null for suppressed
323        // IThrowableProxy array, in which case no attribute name is needed
324        if (attributeName != null) {
325            sb.append(QUOTE).append(attributeName).append(QUOTE_COL);
326            if (itp == null) {
327                sb.append(NULL_STR);
328                return;
329            }
330        }
331
332        sb.append(OPEN_OBJ);
333
334        appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(itp.getClassName()));
335
336        sb.append(VALUE_SEPARATOR);
337        appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(itp.getMessage()));
338
339        if (itp.isCyclic()) {
340            sb.append(VALUE_SEPARATOR);
341            appenderMember(sb, CYCLIC_THROWABLE_ATTR_NAME, jsonEscape("true"));
342        }
343
344        sb.append(VALUE_SEPARATOR);
345        appendSTEPArray(sb, itp.getStackTraceElementProxyArray(), itp.getCommonFrames());
346
347        if (itp.getCommonFrames() != 0) {
348            sb.append(VALUE_SEPARATOR);
349            appenderMemberWithIntValue(sb, COMMON_FRAMES_COUNT_ATTR_NAME, itp.getCommonFrames());
350        }
351
352        IThrowableProxy cause = itp.getCause();
353        if (cause != null) {
354            appendThrowableProxy(sb, CAUSE_ATTR_NAME, cause);
355        }
356
357        IThrowableProxy[] suppressedArray = itp.getSuppressed();
358        if (suppressedArray != null && suppressedArray.length != 0) {
359            sb.append(VALUE_SEPARATOR);
360            sb.append(QUOTE).append(SUPPRESSED_ATTR_NAME).append(QUOTE_COL);
361            sb.append(OPEN_ARRAY);
362
363            boolean first = true;
364            for (IThrowableProxy suppressedITP : suppressedArray) {
365                appendThrowableProxy(sb, null, suppressedITP, !first);
366                if (first)
367                    first = false;
368            }
369            sb.append(CLOSE_ARRAY);
370        }
371
372        sb.append(CLOSE_OBJ);
373
374    }
375
376    protected void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArray, int commonFrames) {
377        sb.append(QUOTE).append(STEP_ARRAY_NAME_ATTRIBUTE).append(QUOTE_COL).append(OPEN_ARRAY);
378
379        // If there are no stack trace elements, write an empty array and return early.
380        if (stepArray == null || stepArray.length == 0) {
381            sb.append(CLOSE_ARRAY);
382            return;
383        }
384
385        int len = stepArray.length;
386
387        if (commonFrames >= len) {
388            commonFrames = 0;
389        }
390
391        for (int i = 0; i < len - commonFrames; i++) {
392            if (i != 0)
393                sb.append(VALUE_SEPARATOR);
394
395            StackTraceElementProxy step = stepArray[i];
396
397            sb.append(OPEN_OBJ);
398            StackTraceElement ste = step.getStackTraceElement();
399
400            appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(ste.getClassName()));
401            sb.append(VALUE_SEPARATOR);
402
403            appenderMember(sb, METHOD_NAME_ATTR_NAME, nullSafeStr(ste.getMethodName()));
404            sb.append(VALUE_SEPARATOR);
405
406            appenderMember(sb, FILE_NAME_ATTR_NAME, nullSafeStr(ste.getFileName()));
407            sb.append(VALUE_SEPARATOR);
408
409            appenderMemberWithIntValue(sb, LINE_NUMBER_ATTR_NAME, ste.getLineNumber());
410            sb.append(CLOSE_OBJ);
411
412        }
413
414        sb.append(CLOSE_ARRAY);
415    }
416
417    /**
418     * Hook allowing subclasses to append additional fields into the root JSON object.
419     * Default implementation is a no-op.
420     *
421     * <p>Subclasses may append additional top-level JSON members here. If a
422     * subclass writes additional members it should prepend them with
423     * {@link #VALUE_SEPARATOR} (a comma) if necessary to keep the JSON valid.
424     * Implementations must not close the root JSON object or write the final
425     * line separator; {@link JsonEncoder} handles those.</p>
426     *
427     * @param sb the StringBuilder that accumulates the JSON output; never null
428     * @param event the logging event being encoded; never null
429     */
430    protected void appendCustomFields(StringBuilder sb, ILoggingEvent event) {
431        // no-op by default; subclasses may append VALUE_SEPARATOR then their fields
432    }
433
434    protected void appenderMember(StringBuilder sb, String key, String value) {
435        sb.append(QUOTE).append(key).append(QUOTE_COL).append(QUOTE).append(value).append(QUOTE);
436    }
437
438    protected void appenderMemberWithIntValue(StringBuilder sb, String key, int value) {
439        sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
440    }
441
442    protected void appenderMemberWithLongValue(StringBuilder sb, String key, long value) {
443        sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
444    }
445
446    protected void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
447        List<KeyValuePair> kvpList = event.getKeyValuePairs();
448        if (kvpList == null || kvpList.isEmpty())
449            return;
450
451        sb.append(VALUE_SEPARATOR);
452        sb.append(QUOTE).append(KEY_VALUE_PAIRS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
453        final int len = kvpList.size();
454        for (int i = 0; i < len; i++) {
455            if (i != 0)
456                sb.append(VALUE_SEPARATOR);
457            KeyValuePair kvp = kvpList.get(i);
458            sb.append(OPEN_OBJ);
459            appenderMember(sb, jsonEscapedToString(kvp.key), jsonEscapedToString(kvp.value));
460            sb.append(CLOSE_OBJ);
461        }
462        sb.append(CLOSE_ARRAY);
463    }
464
465    protected void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
466        Object[] argumentArray = event.getArgumentArray();
467        if (argumentArray == null)
468            return;
469
470        sb.append(VALUE_SEPARATOR);
471        sb.append(QUOTE).append(ARGUMENT_ARRAY_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
472        final int len = argumentArray.length;
473        for (int i = 0; i < len; i++) {
474            if (i != 0)
475                sb.append(VALUE_SEPARATOR);
476            sb.append(QUOTE).append(jsonEscapedToString(argumentArray[i])).append(QUOTE);
477
478        }
479        sb.append(CLOSE_ARRAY);
480    }
481
482    protected void appendMarkers(StringBuilder sb, ILoggingEvent event) {
483        List<Marker> markerList = event.getMarkerList();
484        if (markerList == null)
485            return;
486
487        sb.append(VALUE_SEPARATOR);
488        sb.append(QUOTE).append(MARKERS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
489        final int len = markerList.size();
490        for (int i = 0; i < len; i++) {
491            if (i != 0)
492                sb.append(VALUE_SEPARATOR);
493            sb.append(QUOTE).append(jsonEscapedToString(markerList.get(i))).append(QUOTE);
494
495        }
496        sb.append(CLOSE_ARRAY);
497    }
498
499    private String jsonEscapedToString(Object o) {
500        if (o == null)
501            return NULL_STR;
502        return jsonEscapeString(o.toString());
503    }
504
505    private String nullSafeStr(String s) {
506        if (s == null)
507            return NULL_STR;
508        return s;
509    }
510
511    private String jsonEscape(String s) {
512        if (s == null)
513            return NULL_STR;
514        return jsonEscapeString(s);
515    }
516
517    protected void appendMDC(StringBuilder sb, ILoggingEvent event) {
518        Map<String, String> map = event.getMDCPropertyMap();
519        sb.append(VALUE_SEPARATOR);
520        sb.append(QUOTE).append(MDC_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_OBJ);
521        if (isNotEmptyMap(map)) {
522            Set<Map.Entry<String, String>> entrySet = map.entrySet();
523            int i = 0;
524            for (Map.Entry<String, String> entry : entrySet) {
525                if (i != 0)
526                    sb.append(VALUE_SEPARATOR);
527                appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
528                i++;
529            }
530
531        }
532        sb.append(CLOSE_OBJ);
533    }
534
535    /**
536     * Return {@code true} when the provided map is non-null and non-empty.
537     *
538     * @param map the map to check; may be null
539     * @return {@code true} if the map contains at least one entry
540     */
541    boolean isNotEmptyMap(Map<?, ?> map) {
542        if (map == null)
543            return false;
544        return !map.isEmpty();
545    }
546
547    @Override
548    public byte[] footerBytes() {
549        return EMPTY_BYTES;
550    }
551
552    /**
553     * Set whether the sequence number is included in each encoded event.
554     * @param withSequenceNumber {@code true} to include the sequence number in the output
555     * @since 1.5.0
556     */
557    public void setWithSequenceNumber(boolean withSequenceNumber) {
558        this.withSequenceNumber = withSequenceNumber;
559    }
560
561    /**
562     * Set whether the event timestamp is included in each encoded event.
563     * @param withTimestamp {@code true} to include the event timestamp in the output
564     * @since 1.5.0
565     */
566    public void setWithTimestamp(boolean withTimestamp) {
567        this.withTimestamp = withTimestamp;
568    }
569
570    /**
571     * Set whether nanoseconds will be included in the timestamp output.
572     * @param withNanoseconds {@code true} to include nanoseconds in the timestamp output
573     * @since 1.5.0
574     */
575    public void setWithNanoseconds(boolean withNanoseconds) {
576        this.withNanoseconds = withNanoseconds;
577    }
578
579    /**
580     * Enable or disable the inclusion of the log level in the encoded output.
581     *
582     * @param withLevel {@code true} to include the log level. Default is {@code true}.
583     */
584     public void setWithLevel(boolean withLevel) {
585         this.withLevel = withLevel;
586     }
587
588     /**
589      * Enable or disable the inclusion of the thread name in the encoded output.
590      *
591      * @param withThreadName {@code true} to include the thread name. Default is {@code true}.
592      */
593     public void setWithThreadName(boolean withThreadName) {
594         this.withThreadName = withThreadName;
595     }
596
597     /**
598      * Enable or disable the inclusion of the logger name in the encoded output.
599      *
600      * @param withLoggerName {@code true} to include the logger name. Default is {@code true}.
601      */
602     public void setWithLoggerName(boolean withLoggerName) {
603         this.withLoggerName = withLoggerName;
604     }
605
606     /**
607      * Enable or disable the inclusion of the logger context information.
608      *
609      * @param withContext {@code true} to include the logger context. Default is {@code true}.
610      */
611     public void setWithContext(boolean withContext) {
612         this.withContext = withContext;
613     }
614
615     /**
616      * Enable or disable the inclusion of markers in the encoded output.
617      *
618      * @param withMarkers {@code true} to include markers. Default is {@code true}.
619      */
620     public void setWithMarkers(boolean withMarkers) {
621         this.withMarkers = withMarkers;
622     }
623
624     /**
625      * Enable or disable the inclusion of MDC properties in the encoded output.
626      *
627      * @param withMDC {@code true} to include MDC properties. Default is {@code true}.
628      */
629     public void setWithMDC(boolean withMDC) {
630         this.withMDC = withMDC;
631     }
632
633     /**
634      * Enable or disable the inclusion of key-value pairs attached to the logging event.
635      *
636      * @param withKVPList {@code true} to include the key/value pairs list. Default is {@code true}.
637      */
638     public void setWithKVPList(boolean withKVPList) {
639         this.withKVPList = withKVPList;
640     }
641
642     /**
643      * Enable or disable the inclusion of the raw message text in the encoded output.
644      *
645      * @param withMessage {@code true} to include the message. Default is {@code true}.
646      */
647     public void setWithMessage(boolean withMessage) {
648         this.withMessage = withMessage;
649     }
650
651     /**
652      * Enable or disable the inclusion of the event argument array in the encoded output.
653      *
654      * @param withArguments {@code true} to include the argument array. Default is {@code true}.
655      */
656     public void setWithArguments(boolean withArguments) {
657         this.withArguments = withArguments;
658     }
659
660     /**
661      * Enable or disable the inclusion of throwable information in the encoded output.
662      *
663      * @param withThrowable {@code true} to include throwable/stacktrace information. Default is {@code true}.
664      */
665     public void setWithThrowable(boolean withThrowable) {
666         this.withThrowable = withThrowable;
667     }
668
669     /**
670      * Enable or disable the inclusion of the formatted message in the encoded output.
671      *
672      * @param withFormattedMessage {@code true} to include the formatted message. Default is {@code false}.
673      */
674     public void setWithFormattedMessage(boolean withFormattedMessage) {
675         this.withFormattedMessage = withFormattedMessage;
676     }
677
678 }