1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.classic.encoder;
16  
17  import ch.qos.logback.classic.spi.ILoggingEvent;
18  import ch.qos.logback.classic.spi.IThrowableProxy;
19  import ch.qos.logback.classic.spi.LoggerContextVO;
20  import ch.qos.logback.classic.spi.StackTraceElementProxy;
21  import ch.qos.logback.core.CoreConstants;
22  import ch.qos.logback.core.encoder.EncoderBase;
23  import org.slf4j.Marker;
24  import org.slf4j.event.KeyValuePair;
25  
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  
30  import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
31  import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
32  import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
33  import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
34  import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
35  import static ch.qos.logback.core.model.ModelConstants.NULL_STR;
36  
37  /**
38   * JSON encoder that produces one JSON object per line in JSON Lines format, suitable for structured logging.
39   * Each {@link ILoggingEvent} is encoded into a JSON object containing fields such as timestamp, level, message,
40   * and optional elements like MDC properties, markers, and stack traces.
41   *
42   * <p>This encoder supports extensive configuration through boolean flags to include or exclude specific fields
43   * in the output, allowing customization for different logging needs. For example, you can enable/disable
44   * sequence numbers, nanoseconds, thread names, logger context, markers, MDC, key-value pairs, arguments,
45   * and throwable information.</p>
46   *
47   * <p>The encoder is designed for extensibility: subclasses can override protected methods (e.g.,
48   * {@link #appendLoggerContext}, {@link #appendThrowableProxy}, {@link #appendMarkers}) to customize
49   * how specific parts of the JSON are generated. Additionally, the {@link #appendCustomFields} hook
50   * allows appending custom top-level fields to the JSON object.</p>
51   *
52   * <h3>Configuration</h3>
53   * <p>Use the setter methods (e.g., {@link #setWithSequenceNumber}, {@link #setWithTimestamp}) to control
54   * which fields are included. By default, most fields are enabled except {@code withFormattedMessage}.</p>
55   *
56   * <h3>Example Usage</h3>
57   * <pre>{@code
58   * <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
59   *   <encoder class="ch.qos.logback.classic.encoder.JsonEncoder">
60   *     <withSequenceNumber>false</withSequenceNumber>
61   *     <withNanoseconds>false</withNanoseconds>
62   *     <withThreadName>false</withThreadName>
63   *   </encoder>
64   * </appender>
65   * }</pre>
66   *
67   * <p>This produces output similar to the following (on a single line):
68   * </p>
69   *
70   * <pre>{"timestamp":1640995200000,"level":"INFO","loggerName":"com.example.MyClass","context":{"name":"default","birthdate":1640995200000,"properties":{}},"message":"Hello World"}</pre>
71   *
72   * @see <a href="https://jsonlines.org/">JSON Lines</a>
73   * @see <a href="https://datatracker.ietf.org/doc/html/rfc8259">RFC 8259 (The JavaScript Object Notation (JSON) Data Interchange Format)</a>
74   * @see ch.qos.logback.core.encoder.EncoderBase
75   * @since 1.3.8/1.4.8
76   * @author Ceki G&uuml;lc&uuml;
77   */
78  public class JsonEncoder extends EncoderBase<ILoggingEvent> {
79      static final boolean DO_NOT_ADD_QUOTE_KEY = false;
80      static final boolean ADD_QUOTE_KEY = true;
81      static int DEFAULT_SIZE = 1024;
82      static int DEFAULT_SIZE_WITH_THROWABLE = DEFAULT_SIZE * 8;
83  
84      static byte[] EMPTY_BYTES = new byte[0];
85  
86      public static final String CONTEXT_ATTR_NAME = "context";
87      public static final String NAME_ATTR_NAME = "name";
88      public static final String BIRTHDATE_ATTR_NAME = "birthdate";
89      public static final String CONTEXT_PROPERTIES_ATTR_NAME = "properties";
90  
91      public static final String TIMESTAMP_ATTR_NAME = "timestamp";
92  
93      public static final String NANOSECONDS_ATTR_NAME = "nanoseconds";
94  
95      public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumber";
96  
97      public static final String LEVEL_ATTR_NAME = "level";
98      public static final String MARKERS_ATTR_NAME = "markers";
99      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  }