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.Arrays;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import static ch.qos.logback.core.CoreConstants.COLON_CHAR;
032import static ch.qos.logback.core.CoreConstants.COMMA_CHAR;
033import static ch.qos.logback.core.CoreConstants.DOUBLE_QUOTE_CHAR;
034import static ch.qos.logback.core.CoreConstants.UTF_8_CHARSET;
035import static ch.qos.logback.core.encoder.JsonEscapeUtil.jsonEscapeString;
036import static ch.qos.logback.core.model.ModelConstants.NULL_STR;
037
038/**
039 *
040 *
041 * https://jsonlines.org/ https://datatracker.ietf.org/doc/html/rfc8259
042 */
043public class JsonEncoder extends EncoderBase<ILoggingEvent> {
044    static final boolean DO_NOT_ADD_QUOTE_KEY = false;
045    static final boolean ADD_QUOTE_KEY = true;
046    static int DEFAULT_SIZE = 1024;
047    static int DEFAULT_SIZE_WITH_THROWABLE = DEFAULT_SIZE * 8;
048
049    static byte[] EMPTY_BYTES = new byte[0];
050
051    public static final String CONTEXT_ATTR_NAME = "context";
052    public static final String NAME_ATTR_NAME = "name";
053    public static final String BIRTHDATE_ATTR_NAME = "birthdate";
054    public static final String CONTEXT_PROPERTIES_ATTR_NAME = "properties";
055
056    public static final String TIMESTAMP_ATTR_NAME = "timestamp";
057
058    public static final String NANOSECONDS_ATTR_NAME = "nanoseconds";
059
060    public static final String SEQUENCE_NUMBER_ATTR_NAME = "sequenceNumber";
061
062    public static final String LEVEL_ATTR_NAME = "level";
063    public static final String MARKERS_ATTR_NAME = "markers";
064    public static final String THREAD_NAME_ATTR_NAME = "threadName";
065    public static final String MDC_ATTR_NAME = "mdc";
066    public static final String LOGGER_ATTR_NAME = "loggerName";
067
068    public static final String MESSAGE_ATTR_NAME = "message";
069
070    public static final String FORMATTED_MESSAGE_ATTR_NAME = "formattedMessage";
071
072    public static final String ARGUMENT_ARRAY_ATTR_NAME = "arguments";
073    public static final String KEY_VALUE_PAIRS_ATTR_NAME = "kvpList";
074
075    public static final String THROWABLE_ATTR_NAME = "throwable";
076
077    private static final String CYCLIC_THROWABLE_ATTR_NAME = "cyclic";
078
079    public static final String CAUSE_ATTR_NAME = "cause";
080
081    public static final String SUPPRESSED_ATTR_NAME = "suppressed";
082
083    public static final String COMMON_FRAMES_COUNT_ATTR_NAME = "commonFramesCount";
084
085    public static final String CLASS_NAME_ATTR_NAME = "className";
086    public static final String METHOD_NAME_ATTR_NAME = "methodName";
087    private static final String FILE_NAME_ATTR_NAME = "fileName";
088    private static final String LINE_NUMBER_ATTR_NAME = "lineNumber";
089
090    public static final String STEP_ARRAY_NAME_ATTRIBUTE = "stepArray";
091
092    private static final char OPEN_OBJ = '{';
093    private static final char CLOSE_OBJ = '}';
094    private static final char OPEN_ARRAY = '[';
095    private static final char CLOSE_ARRAY = ']';
096
097    private static final char QUOTE = DOUBLE_QUOTE_CHAR;
098    private static final char SP = ' ';
099    private static final char ENTRY_SEPARATOR = COLON_CHAR;
100
101    private static final String COL_SP = ": ";
102
103    private static final String QUOTE_COL = "\":";
104
105    private static final char VALUE_SEPARATOR = COMMA_CHAR;
106
107    private boolean withSequenceNumber = true;
108
109    private boolean withTimestamp = true;
110    private boolean withNanoseconds = true;
111
112    private boolean withLevel = true;
113    private boolean withThreadName = true;
114    private boolean withLoggerName = true;
115    private boolean withContext = true;
116    private boolean withMarkers = true;
117    private boolean withMDC = true;
118    private boolean withKVPList = true;
119    private boolean withMessage = true;
120    private boolean withArguments = true;
121    private boolean withThrowable = true;
122    private boolean withFormattedMessage = false;
123
124    @Override
125    public byte[] headerBytes() {
126        return EMPTY_BYTES;
127    }
128
129    @Override
130    public byte[] encode(ILoggingEvent event) {
131        final int initialCapacity = event.getThrowableProxy() == null ? DEFAULT_SIZE : DEFAULT_SIZE_WITH_THROWABLE;
132        StringBuilder sb = new StringBuilder(initialCapacity);
133        sb.append(OPEN_OBJ);
134
135        if (withSequenceNumber) {
136            appenderMemberWithLongValue(sb, SEQUENCE_NUMBER_ATTR_NAME, event.getSequenceNumber());
137        }
138
139        if (withTimestamp) {
140            appendValueSeparator(sb, withSequenceNumber);
141            appenderMemberWithLongValue(sb, TIMESTAMP_ATTR_NAME, event.getTimeStamp());
142        }
143
144        if (withNanoseconds) {
145            appendValueSeparator(sb, withSequenceNumber, withTimestamp);
146            appenderMemberWithLongValue(sb, NANOSECONDS_ATTR_NAME, event.getNanoseconds());
147        }
148
149        if (withLevel) {
150            appendValueSeparator(sb, withNanoseconds, withSequenceNumber, withTimestamp);
151            String levelStr = event.getLevel() != null ? event.getLevel().levelStr : NULL_STR;
152            appenderMember(sb, LEVEL_ATTR_NAME, levelStr);
153        }
154
155        if (withThreadName) {
156            appendValueSeparator(sb, withLevel, withNanoseconds, withSequenceNumber, withTimestamp);
157            appenderMember(sb, THREAD_NAME_ATTR_NAME, jsonEscape(event.getThreadName()));
158        }
159
160        if (withLoggerName) {
161            appendValueSeparator(sb, withThreadName, withLevel, withNanoseconds, withSequenceNumber, withTimestamp);
162            appenderMember(sb, LOGGER_ATTR_NAME, event.getLoggerName());
163        }
164
165        if (withContext) {
166            // at this stage we assume that at least one field was written
167            sb.append(VALUE_SEPARATOR);
168            appendLoggerContext(sb, event.getLoggerContextVO());
169        }
170
171        if (withMarkers)
172            appendMarkers(sb, event);
173
174        if (withMDC)
175            appendMDC(sb, event);
176
177        if (withKVPList)
178            appendKeyValuePairs(sb, event);
179
180        if (withMessage) {
181            sb.append(VALUE_SEPARATOR);
182            appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(event.getMessage()));
183        }
184
185        if (withFormattedMessage) {
186            sb.append(VALUE_SEPARATOR);
187            appenderMember(sb, FORMATTED_MESSAGE_ATTR_NAME, jsonEscape(event.getFormattedMessage()));
188        }
189
190        if (withArguments) {
191            appendArgumentArray(sb, event);
192        }
193
194        if (withThrowable)
195            appendThrowableProxy(sb, THROWABLE_ATTR_NAME, event.getThrowableProxy());
196
197        sb.append(CLOSE_OBJ);
198        sb.append(CoreConstants.JSON_LINE_SEPARATOR);
199        return sb.toString().getBytes(UTF_8_CHARSET);
200    }
201
202    void appendValueSeparator(StringBuilder sb, boolean... subsequentConditionals) {
203        boolean enabled = false;
204        for (boolean subsequent : subsequentConditionals) {
205            if (subsequent) {
206                enabled = true;
207                break;
208            }
209        }
210
211        if (enabled)
212            sb.append(VALUE_SEPARATOR);
213    }
214
215    private void appendLoggerContext(StringBuilder sb, LoggerContextVO loggerContextVO) {
216
217        sb.append(QUOTE).append(CONTEXT_ATTR_NAME).append(QUOTE_COL);
218        if (loggerContextVO == null) {
219            sb.append(NULL_STR);
220            return;
221        }
222
223        sb.append(OPEN_OBJ);
224        appenderMember(sb, NAME_ATTR_NAME, nullSafeStr(loggerContextVO.getName()));
225        sb.append(VALUE_SEPARATOR);
226        appenderMemberWithLongValue(sb, BIRTHDATE_ATTR_NAME, loggerContextVO.getBirthTime());
227        sb.append(VALUE_SEPARATOR);
228
229        appendMap(sb, CONTEXT_PROPERTIES_ATTR_NAME, loggerContextVO.getPropertyMap());
230        sb.append(CLOSE_OBJ);
231
232    }
233
234    private void appendMap(StringBuilder sb, String attrName, Map<String, String> map) {
235        sb.append(QUOTE).append(attrName).append(QUOTE_COL);
236        if (map == null) {
237            sb.append(NULL_STR);
238            return;
239        }
240
241        sb.append(OPEN_OBJ);
242
243        boolean addComma = false;
244        Set<Map.Entry<String, String>> entries = map.entrySet();
245        for (Map.Entry<String, String> entry : entries) {
246            if (addComma) {
247                sb.append(VALUE_SEPARATOR);
248            }
249            addComma = true;
250            appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
251        }
252
253        sb.append(CLOSE_OBJ);
254    }
255
256    private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp) {
257        appendThrowableProxy(sb, attributeName, itp, true);
258    }
259
260    private void appendThrowableProxy(StringBuilder sb, String attributeName, IThrowableProxy itp, boolean appendValueSeparator) {
261
262        if (appendValueSeparator)
263            sb.append(VALUE_SEPARATOR);
264
265        // in the nominal case, attributeName != null. However, attributeName will be null for suppressed
266        // IThrowableProxy array, in which case no attribute name is needed
267        if (attributeName != null) {
268            sb.append(QUOTE).append(attributeName).append(QUOTE_COL);
269            if (itp == null) {
270                sb.append(NULL_STR);
271                return;
272            }
273        }
274
275        sb.append(OPEN_OBJ);
276
277        appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(itp.getClassName()));
278
279        sb.append(VALUE_SEPARATOR);
280        appenderMember(sb, MESSAGE_ATTR_NAME, jsonEscape(itp.getMessage()));
281
282        if (itp.isCyclic()) {
283            sb.append(VALUE_SEPARATOR);
284            appenderMember(sb, CYCLIC_THROWABLE_ATTR_NAME, jsonEscape("true"));
285        }
286
287        sb.append(VALUE_SEPARATOR);
288        appendSTEPArray(sb, itp.getStackTraceElementProxyArray(), itp.getCommonFrames());
289
290        if (itp.getCommonFrames() != 0) {
291            sb.append(VALUE_SEPARATOR);
292            appenderMemberWithIntValue(sb, COMMON_FRAMES_COUNT_ATTR_NAME, itp.getCommonFrames());
293        }
294
295        IThrowableProxy cause = itp.getCause();
296        if (cause != null) {
297            appendThrowableProxy(sb, CAUSE_ATTR_NAME, cause);
298        }
299
300        IThrowableProxy[] suppressedArray = itp.getSuppressed();
301        if (suppressedArray != null && suppressedArray.length != 0) {
302            sb.append(VALUE_SEPARATOR);
303            sb.append(QUOTE).append(SUPPRESSED_ATTR_NAME).append(QUOTE_COL);
304            sb.append(OPEN_ARRAY);
305
306            boolean first = true;
307            for (IThrowableProxy suppressedITP : suppressedArray) {
308                appendThrowableProxy(sb, null, suppressedITP, !first);
309                if (first)
310                    first = false;
311            }
312            sb.append(CLOSE_ARRAY);
313        }
314
315        sb.append(CLOSE_OBJ);
316
317    }
318
319    private void appendSTEPArray(StringBuilder sb, StackTraceElementProxy[] stepArray, int commonFrames) {
320        sb.append(QUOTE).append(STEP_ARRAY_NAME_ATTRIBUTE).append(QUOTE_COL).append(OPEN_ARRAY);
321
322        int len = stepArray != null ? stepArray.length : 0;
323
324        if (commonFrames >= len) {
325            commonFrames = 0;
326        }
327
328        for (int i = 0; i < len - commonFrames; i++) {
329            if (i != 0)
330                sb.append(VALUE_SEPARATOR);
331
332            StackTraceElementProxy step = stepArray[i];
333
334            sb.append(OPEN_OBJ);
335            StackTraceElement ste = step.getStackTraceElement();
336
337            appenderMember(sb, CLASS_NAME_ATTR_NAME, nullSafeStr(ste.getClassName()));
338            sb.append(VALUE_SEPARATOR);
339
340            appenderMember(sb, METHOD_NAME_ATTR_NAME, nullSafeStr(ste.getMethodName()));
341            sb.append(VALUE_SEPARATOR);
342
343            appenderMember(sb, FILE_NAME_ATTR_NAME, nullSafeStr(ste.getFileName()));
344            sb.append(VALUE_SEPARATOR);
345
346            appenderMemberWithIntValue(sb, LINE_NUMBER_ATTR_NAME, ste.getLineNumber());
347            sb.append(CLOSE_OBJ);
348
349        }
350
351        sb.append(CLOSE_ARRAY);
352    }
353
354    private void appenderMember(StringBuilder sb, String key, String value) {
355        sb.append(QUOTE).append(key).append(QUOTE_COL).append(QUOTE).append(value).append(QUOTE);
356    }
357
358    private void appenderMemberWithIntValue(StringBuilder sb, String key, int value) {
359        sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
360    }
361
362    private void appenderMemberWithLongValue(StringBuilder sb, String key, long value) {
363        sb.append(QUOTE).append(key).append(QUOTE_COL).append(value);
364    }
365
366    private void appendKeyValuePairs(StringBuilder sb, ILoggingEvent event) {
367        List<KeyValuePair> kvpList = event.getKeyValuePairs();
368        if (kvpList == null || kvpList.isEmpty())
369            return;
370
371        sb.append(VALUE_SEPARATOR);
372        sb.append(QUOTE).append(KEY_VALUE_PAIRS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
373        final int len = kvpList.size();
374        for (int i = 0; i < len; i++) {
375            if (i != 0)
376                sb.append(VALUE_SEPARATOR);
377            KeyValuePair kvp = kvpList.get(i);
378            sb.append(OPEN_OBJ);
379            appenderMember(sb, jsonEscapedToString(kvp.key), jsonEscapedToString(kvp.value));
380            sb.append(CLOSE_OBJ);
381        }
382        sb.append(CLOSE_ARRAY);
383    }
384
385    private void appendArgumentArray(StringBuilder sb, ILoggingEvent event) {
386        Object[] argumentArray = event.getArgumentArray();
387        if (argumentArray == null)
388            return;
389
390        sb.append(VALUE_SEPARATOR);
391        sb.append(QUOTE).append(ARGUMENT_ARRAY_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
392        final int len = argumentArray.length;
393        for (int i = 0; i < len; i++) {
394            if (i != 0)
395                sb.append(VALUE_SEPARATOR);
396            sb.append(QUOTE).append(jsonEscapedToString(argumentArray[i])).append(QUOTE);
397
398        }
399        sb.append(CLOSE_ARRAY);
400    }
401
402    private void appendMarkers(StringBuilder sb, ILoggingEvent event) {
403        List<Marker> markerList = event.getMarkerList();
404        if (markerList == null)
405            return;
406
407        sb.append(VALUE_SEPARATOR);
408        sb.append(QUOTE).append(MARKERS_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_ARRAY);
409        final int len = markerList.size();
410        for (int i = 0; i < len; i++) {
411            if (i != 0)
412                sb.append(VALUE_SEPARATOR);
413            sb.append(QUOTE).append(jsonEscapedToString(markerList.get(i))).append(QUOTE);
414
415        }
416        sb.append(CLOSE_ARRAY);
417    }
418
419    private String jsonEscapedToString(Object o) {
420        if (o == null)
421            return NULL_STR;
422        return jsonEscapeString(o.toString());
423    }
424
425    private String nullSafeStr(String s) {
426        if (s == null)
427            return NULL_STR;
428        return s;
429    }
430
431    private String jsonEscape(String s) {
432        if (s == null)
433            return NULL_STR;
434        return jsonEscapeString(s);
435    }
436
437    private void appendMDC(StringBuilder sb, ILoggingEvent event) {
438        Map<String, String> map = event.getMDCPropertyMap();
439        sb.append(VALUE_SEPARATOR);
440        sb.append(QUOTE).append(MDC_ATTR_NAME).append(QUOTE_COL).append(SP).append(OPEN_OBJ);
441        if (isNotEmptyMap(map)) {
442            Set<Map.Entry<String, String>> entrySet = map.entrySet();
443            int i = 0;
444            for (Map.Entry<String, String> entry : entrySet) {
445                if (i != 0)
446                    sb.append(VALUE_SEPARATOR);
447                appenderMember(sb, jsonEscapedToString(entry.getKey()), jsonEscapedToString(entry.getValue()));
448                i++;
449            }
450
451        }
452        sb.append(CLOSE_OBJ);
453    }
454
455    boolean isNotEmptyMap(Map map) {
456        if (map == null)
457            return false;
458        return !map.isEmpty();
459    }
460
461    @Override
462    public byte[] footerBytes() {
463        return EMPTY_BYTES;
464    }
465
466    /**
467     * @param withSequenceNumber
468     * @since 1.5.0
469     */
470    public void setWithSequenceNumber(boolean withSequenceNumber) {
471        this.withSequenceNumber = withSequenceNumber;
472    }
473
474    /**
475     * @param withTimestamp
476     * @since 1.5.0
477     */
478    public void setWithTimestamp(boolean withTimestamp) {
479        this.withTimestamp = withTimestamp;
480    }
481
482    /**
483     * @param withNanoseconds
484     * @since 1.5.0
485     */
486    public void setWithNanoseconds(boolean withNanoseconds) {
487        this.withNanoseconds = withNanoseconds;
488    }
489
490    public void setWithLevel(boolean withLevel) {
491        this.withLevel = withLevel;
492    }
493
494    public void setWithThreadName(boolean withThreadName) {
495        this.withThreadName = withThreadName;
496    }
497
498    public void setWithLoggerName(boolean withLoggerName) {
499        this.withLoggerName = withLoggerName;
500    }
501
502    public void setWithContext(boolean withContext) {
503        this.withContext = withContext;
504    }
505
506    public void setWithMarkers(boolean withMarkers) {
507        this.withMarkers = withMarkers;
508    }
509
510    public void setWithMDC(boolean withMDC) {
511        this.withMDC = withMDC;
512    }
513
514    public void setWithKVPList(boolean withKVPList) {
515        this.withKVPList = withKVPList;
516    }
517
518    public void setWithMessage(boolean withMessage) {
519        this.withMessage = withMessage;
520    }
521
522    public void setWithArguments(boolean withArguments) {
523        this.withArguments = withArguments;
524    }
525
526    public void setWithThrowable(boolean withThrowable) {
527        this.withThrowable = withThrowable;
528    }
529
530    public void setWithFormattedMessage(boolean withFormattedMessage) {
531        this.withFormattedMessage = withFormattedMessage;
532    }
533
534}