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