001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache license, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the license for the specific language governing permissions and
015 * limitations under the license.
016 */
017package org.apache.logging.log4j.core.layout;
018
019import java.io.IOException;
020import java.io.Writer;
021import java.nio.charset.Charset;
022import java.nio.charset.StandardCharsets;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.apache.logging.log4j.core.Layout;
027import org.apache.logging.log4j.core.LogEvent;
028import org.apache.logging.log4j.core.config.Configuration;
029import org.apache.logging.log4j.core.config.DefaultConfiguration;
030import org.apache.logging.log4j.core.config.Node;
031import org.apache.logging.log4j.core.config.plugins.Plugin;
032import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
033import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
034import org.apache.logging.log4j.core.config.plugins.PluginFactory;
035
036/**
037 * Appends a series of JSON events as strings serialized as bytes.
038 *
039 * <h3>Complete well-formed JSON vs. fragment JSON</h3>
040 * <p>
041 * If you configure {@code complete="true"}, the appender outputs a well-formed JSON document. By default, with
042 * {@code complete="false"}, you should include the output as an <em>external file</em> in a separate file to form a
043 * well-formed JSON document.
044 * </p>
045 * <p>
046 * A well-formed JSON event follows this pattern:
047 * </p>
048 *
049 * <pre>
050 * {
051  "timeMillis": 1,
052  "thread": "MyThreadName",
053  "level": "DEBUG",
054  "loggerName": "a.B",
055  "marker": {
056    "name": "Marker1",
057    "parents": [{
058      "name": "ParentMarker1",
059      "parents": [{
060        "name": "GrandMotherMarker"
061      }, {
062        "name": "GrandFatherMarker"
063      }]
064    }, {
065      "name": "GrandFatherMarker"
066    }]
067  },
068  "message": "Msg",
069  "thrown": {
070    "cause": {
071      "commonElementCount": 27,
072      "extendedStackTrace": [{
073        "class": "org.apache.logging.log4j.core.layout.LogEventFixtures",
074        "method": "createLogEvent",
075        "file": "LogEventFixtures.java",
076        "line": 53,
077        "exact": false,
078        "location": "test-classes/",
079        "version": "?"
080      }],
081      "localizedMessage": "testNPEx",
082      "message": "testNPEx",
083      "name": "java.lang.NullPointerException"
084    },
085    "commonElementCount": 0,
086    "extendedStackTrace": [{
087      "class": "org.apache.logging.log4j.core.layout.LogEventFixtures",
088      "method": "createLogEvent",
089      "file": "LogEventFixtures.java",
090      "line": 56,
091      "exact": true,
092      "location": "test-classes/",
093      "version": "?"
094    }, {
095      "class": "org.apache.logging.log4j.core.layout.JsonLayoutTest",
096      "method": "testAllFeatures",
097      "file": "JsonLayoutTest.java",
098      "line": 105,
099      "exact": true,
100      "location": "test-classes/",
101      "version": "?"
102    }, {
103      "class": "org.apache.logging.log4j.core.layout.JsonLayoutTest",
104      "method": "testLocationOnCompactOnMdcOn",
105      "file": "JsonLayoutTest.java",
106      "line": 268,
107      "exact": true,
108      "location": "test-classes/",
109      "version": "?"
110    }, {
111      "class": "sun.reflect.NativeMethodAccessorImpl",
112      "method": "invoke",
113      "line": -1,
114      "exact": false,
115      "location": "?",
116      "version": "1.7.0_55"
117    }, {
118      "class": "sun.reflect.NativeMethodAccessorImpl",
119      "method": "invoke",
120      "line": -1,
121      "exact": false,
122      "location": "?",
123      "version": "1.7.0_55"
124    }, {
125      "class": "sun.reflect.DelegatingMethodAccessorImpl",
126      "method": "invoke",
127      "line": -1,
128      "exact": false,
129      "location": "?",
130      "version": "1.7.0_55"
131    }, {
132      "class": "java.lang.reflect.Method",
133      "method": "invoke",
134      "line": -1,
135      "exact": false,
136      "location": "?",
137      "version": "1.7.0_55"
138    }, {
139      "class": "org.junit.runners.model.FrameworkMethod$1",
140      "method": "runReflectiveCall",
141      "file": "FrameworkMethod.java",
142      "line": 47,
143      "exact": true,
144      "location": "junit-4.11.jar",
145      "version": "?"
146    }, {
147      "class": "org.junit.internal.runners.model.ReflectiveCallable",
148      "method": "run",
149      "file": "ReflectiveCallable.java",
150      "line": 12,
151      "exact": true,
152      "location": "junit-4.11.jar",
153      "version": "?"
154    }, {
155      "class": "org.junit.runners.model.FrameworkMethod",
156      "method": "invokeExplosively",
157      "file": "FrameworkMethod.java",
158      "line": 44,
159      "exact": true,
160      "location": "junit-4.11.jar",
161      "version": "?"
162    }, {
163      "class": "org.junit.internal.runners.statements.InvokeMethod",
164      "method": "evaluate",
165      "file": "InvokeMethod.java",
166      "line": 17,
167      "exact": true,
168      "location": "junit-4.11.jar",
169      "version": "?"
170    }, {
171      "class": "org.junit.runners.ParentRunner",
172      "method": "runLeaf",
173      "file": "ParentRunner.java",
174      "line": 271,
175      "exact": true,
176      "location": "junit-4.11.jar",
177      "version": "?"
178    }, {
179      "class": "org.junit.runners.BlockJUnit4ClassRunner",
180      "method": "runChild",
181      "file": "BlockJUnit4ClassRunner.java",
182      "line": 70,
183      "exact": true,
184      "location": "junit-4.11.jar",
185      "version": "?"
186    }, {
187      "class": "org.junit.runners.BlockJUnit4ClassRunner",
188      "method": "runChild",
189      "file": "BlockJUnit4ClassRunner.java",
190      "line": 50,
191      "exact": true,
192      "location": "junit-4.11.jar",
193      "version": "?"
194    }, {
195      "class": "org.junit.runners.ParentRunner$3",
196      "method": "run",
197      "file": "ParentRunner.java",
198      "line": 238,
199      "exact": true,
200      "location": "junit-4.11.jar",
201      "version": "?"
202    }, {
203      "class": "org.junit.runners.ParentRunner$1",
204      "method": "schedule",
205      "file": "ParentRunner.java",
206      "line": 63,
207      "exact": true,
208      "location": "junit-4.11.jar",
209      "version": "?"
210    }, {
211      "class": "org.junit.runners.ParentRunner",
212      "method": "runChildren",
213      "file": "ParentRunner.java",
214      "line": 236,
215      "exact": true,
216      "location": "junit-4.11.jar",
217      "version": "?"
218    }, {
219      "class": "org.junit.runners.ParentRunner",
220      "method": "access$000",
221      "file": "ParentRunner.java",
222      "line": 53,
223      "exact": true,
224      "location": "junit-4.11.jar",
225      "version": "?"
226    }, {
227      "class": "org.junit.runners.ParentRunner$2",
228      "method": "evaluate",
229      "file": "ParentRunner.java",
230      "line": 229,
231      "exact": true,
232      "location": "junit-4.11.jar",
233      "version": "?"
234    }, {
235      "class": "org.junit.internal.runners.statements.RunBefores",
236      "method": "evaluate",
237      "file": "RunBefores.java",
238      "line": 26,
239      "exact": true,
240      "location": "junit-4.11.jar",
241      "version": "?"
242    }, {
243      "class": "org.junit.internal.runners.statements.RunAfters",
244      "method": "evaluate",
245      "file": "RunAfters.java",
246      "line": 27,
247      "exact": true,
248      "location": "junit-4.11.jar",
249      "version": "?"
250    }, {
251      "class": "org.junit.runners.ParentRunner",
252      "method": "run",
253      "file": "ParentRunner.java",
254      "line": 309,
255      "exact": true,
256      "location": "junit-4.11.jar",
257      "version": "?"
258    }, {
259      "class": "org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference",
260      "method": "run",
261      "file": "JUnit4TestReference.java",
262      "line": 50,
263      "exact": true,
264      "location": ".cp/",
265      "version": "?"
266    }, {
267      "class": "org.eclipse.jdt.internal.junit.runner.TestExecution",
268      "method": "run",
269      "file": "TestExecution.java",
270      "line": 38,
271      "exact": true,
272      "location": ".cp/",
273      "version": "?"
274    }, {
275      "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
276      "method": "runTests",
277      "file": "RemoteTestRunner.java",
278      "line": 467,
279      "exact": true,
280      "location": ".cp/",
281      "version": "?"
282    }, {
283      "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
284      "method": "runTests",
285      "file": "RemoteTestRunner.java",
286      "line": 683,
287      "exact": true,
288      "location": ".cp/",
289      "version": "?"
290    }, {
291      "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
292      "method": "run",
293      "file": "RemoteTestRunner.java",
294      "line": 390,
295      "exact": true,
296      "location": ".cp/",
297      "version": "?"
298    }, {
299      "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
300      "method": "main",
301      "file": "RemoteTestRunner.java",
302      "line": 197,
303      "exact": true,
304      "location": ".cp/",
305      "version": "?"
306    }],
307    "localizedMessage": "testIOEx",
308    "message": "testIOEx",
309    "name": "java.io.IOException",
310    "suppressed": [{
311      "commonElementCount": 0,
312      "extendedStackTrace": [{
313        "class": "org.apache.logging.log4j.core.layout.LogEventFixtures",
314        "method": "createLogEvent",
315        "file": "LogEventFixtures.java",
316        "line": 57,
317        "exact": true,
318        "location": "test-classes/",
319        "version": "?"
320      }, {
321        "class": "org.apache.logging.log4j.core.layout.JsonLayoutTest",
322        "method": "testAllFeatures",
323        "file": "JsonLayoutTest.java",
324        "line": 105,
325        "exact": true,
326        "location": "test-classes/",
327        "version": "?"
328      }, {
329        "class": "org.apache.logging.log4j.core.layout.JsonLayoutTest",
330        "method": "testLocationOnCompactOnMdcOn",
331        "file": "JsonLayoutTest.java",
332        "line": 268,
333        "exact": true,
334        "location": "test-classes/",
335        "version": "?"
336      }, {
337        "class": "sun.reflect.NativeMethodAccessorImpl",
338        "method": "invoke",
339        "line": -1,
340        "exact": false,
341        "location": "?",
342        "version": "1.7.0_55"
343      }, {
344        "class": "sun.reflect.NativeMethodAccessorImpl",
345        "method": "invoke",
346        "line": -1,
347        "exact": false,
348        "location": "?",
349        "version": "1.7.0_55"
350      }, {
351        "class": "sun.reflect.DelegatingMethodAccessorImpl",
352        "method": "invoke",
353        "line": -1,
354        "exact": false,
355        "location": "?",
356        "version": "1.7.0_55"
357      }, {
358        "class": "java.lang.reflect.Method",
359        "method": "invoke",
360        "line": -1,
361        "exact": false,
362        "location": "?",
363        "version": "1.7.0_55"
364      }, {
365        "class": "org.junit.runners.model.FrameworkMethod$1",
366        "method": "runReflectiveCall",
367        "file": "FrameworkMethod.java",
368        "line": 47,
369        "exact": true,
370        "location": "junit-4.11.jar",
371        "version": "?"
372      }, {
373        "class": "org.junit.internal.runners.model.ReflectiveCallable",
374        "method": "run",
375        "file": "ReflectiveCallable.java",
376        "line": 12,
377        "exact": true,
378        "location": "junit-4.11.jar",
379        "version": "?"
380      }, {
381        "class": "org.junit.runners.model.FrameworkMethod",
382        "method": "invokeExplosively",
383        "file": "FrameworkMethod.java",
384        "line": 44,
385        "exact": true,
386        "location": "junit-4.11.jar",
387        "version": "?"
388      }, {
389        "class": "org.junit.internal.runners.statements.InvokeMethod",
390        "method": "evaluate",
391        "file": "InvokeMethod.java",
392        "line": 17,
393        "exact": true,
394        "location": "junit-4.11.jar",
395        "version": "?"
396      }, {
397        "class": "org.junit.runners.ParentRunner",
398        "method": "runLeaf",
399        "file": "ParentRunner.java",
400        "line": 271,
401        "exact": true,
402        "location": "junit-4.11.jar",
403        "version": "?"
404      }, {
405        "class": "org.junit.runners.BlockJUnit4ClassRunner",
406        "method": "runChild",
407        "file": "BlockJUnit4ClassRunner.java",
408        "line": 70,
409        "exact": true,
410        "location": "junit-4.11.jar",
411        "version": "?"
412      }, {
413        "class": "org.junit.runners.BlockJUnit4ClassRunner",
414        "method": "runChild",
415        "file": "BlockJUnit4ClassRunner.java",
416        "line": 50,
417        "exact": true,
418        "location": "junit-4.11.jar",
419        "version": "?"
420      }, {
421        "class": "org.junit.runners.ParentRunner$3",
422        "method": "run",
423        "file": "ParentRunner.java",
424        "line": 238,
425        "exact": true,
426        "location": "junit-4.11.jar",
427        "version": "?"
428      }, {
429        "class": "org.junit.runners.ParentRunner$1",
430        "method": "schedule",
431        "file": "ParentRunner.java",
432        "line": 63,
433        "exact": true,
434        "location": "junit-4.11.jar",
435        "version": "?"
436      }, {
437        "class": "org.junit.runners.ParentRunner",
438        "method": "runChildren",
439        "file": "ParentRunner.java",
440        "line": 236,
441        "exact": true,
442        "location": "junit-4.11.jar",
443        "version": "?"
444      }, {
445        "class": "org.junit.runners.ParentRunner",
446        "method": "access$000",
447        "file": "ParentRunner.java",
448        "line": 53,
449        "exact": true,
450        "location": "junit-4.11.jar",
451        "version": "?"
452      }, {
453        "class": "org.junit.runners.ParentRunner$2",
454        "method": "evaluate",
455        "file": "ParentRunner.java",
456        "line": 229,
457        "exact": true,
458        "location": "junit-4.11.jar",
459        "version": "?"
460      }, {
461        "class": "org.junit.internal.runners.statements.RunBefores",
462        "method": "evaluate",
463        "file": "RunBefores.java",
464        "line": 26,
465        "exact": true,
466        "location": "junit-4.11.jar",
467        "version": "?"
468      }, {
469        "class": "org.junit.internal.runners.statements.RunAfters",
470        "method": "evaluate",
471        "file": "RunAfters.java",
472        "line": 27,
473        "exact": true,
474        "location": "junit-4.11.jar",
475        "version": "?"
476      }, {
477        "class": "org.junit.runners.ParentRunner",
478        "method": "run",
479        "file": "ParentRunner.java",
480        "line": 309,
481        "exact": true,
482        "location": "junit-4.11.jar",
483        "version": "?"
484      }, {
485        "class": "org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference",
486        "method": "run",
487        "file": "JUnit4TestReference.java",
488        "line": 50,
489        "exact": true,
490        "location": ".cp/",
491        "version": "?"
492      }, {
493        "class": "org.eclipse.jdt.internal.junit.runner.TestExecution",
494        "method": "run",
495        "file": "TestExecution.java",
496        "line": 38,
497        "exact": true,
498        "location": ".cp/",
499        "version": "?"
500      }, {
501        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
502        "method": "runTests",
503        "file": "RemoteTestRunner.java",
504        "line": 467,
505        "exact": true,
506        "location": ".cp/",
507        "version": "?"
508      }, {
509        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
510        "method": "runTests",
511        "file": "RemoteTestRunner.java",
512        "line": 683,
513        "exact": true,
514        "location": ".cp/",
515        "version": "?"
516      }, {
517        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
518        "method": "run",
519        "file": "RemoteTestRunner.java",
520        "line": 390,
521        "exact": true,
522        "location": ".cp/",
523        "version": "?"
524      }, {
525        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
526        "method": "main",
527        "file": "RemoteTestRunner.java",
528        "line": 197,
529        "exact": true,
530        "location": ".cp/",
531        "version": "?"
532      }],
533      "localizedMessage": "I am suppressed exception 1",
534      "message": "I am suppressed exception 1",
535      "name": "java.lang.IndexOutOfBoundsException"
536    }, {
537      "commonElementCount": 0,
538      "extendedStackTrace": [{
539        "class": "org.apache.logging.log4j.core.layout.LogEventFixtures",
540        "method": "createLogEvent",
541        "file": "LogEventFixtures.java",
542        "line": 58,
543        "exact": true,
544        "location": "test-classes/",
545        "version": "?"
546      }, {
547        "class": "org.apache.logging.log4j.core.layout.JsonLayoutTest",
548        "method": "testAllFeatures",
549        "file": "JsonLayoutTest.java",
550        "line": 105,
551        "exact": true,
552        "location": "test-classes/",
553        "version": "?"
554      }, {
555        "class": "org.apache.logging.log4j.core.layout.JsonLayoutTest",
556        "method": "testLocationOnCompactOnMdcOn",
557        "file": "JsonLayoutTest.java",
558        "line": 268,
559        "exact": true,
560        "location": "test-classes/",
561        "version": "?"
562      }, {
563        "class": "sun.reflect.NativeMethodAccessorImpl",
564        "method": "invoke",
565        "line": -1,
566        "exact": false,
567        "location": "?",
568        "version": "1.7.0_55"
569      }, {
570        "class": "sun.reflect.NativeMethodAccessorImpl",
571        "method": "invoke",
572        "line": -1,
573        "exact": false,
574        "location": "?",
575        "version": "1.7.0_55"
576      }, {
577        "class": "sun.reflect.DelegatingMethodAccessorImpl",
578        "method": "invoke",
579        "line": -1,
580        "exact": false,
581        "location": "?",
582        "version": "1.7.0_55"
583      }, {
584        "class": "java.lang.reflect.Method",
585        "method": "invoke",
586        "line": -1,
587        "exact": false,
588        "location": "?",
589        "version": "1.7.0_55"
590      }, {
591        "class": "org.junit.runners.model.FrameworkMethod$1",
592        "method": "runReflectiveCall",
593        "file": "FrameworkMethod.java",
594        "line": 47,
595        "exact": true,
596        "location": "junit-4.11.jar",
597        "version": "?"
598      }, {
599        "class": "org.junit.internal.runners.model.ReflectiveCallable",
600        "method": "run",
601        "file": "ReflectiveCallable.java",
602        "line": 12,
603        "exact": true,
604        "location": "junit-4.11.jar",
605        "version": "?"
606      }, {
607        "class": "org.junit.runners.model.FrameworkMethod",
608        "method": "invokeExplosively",
609        "file": "FrameworkMethod.java",
610        "line": 44,
611        "exact": true,
612        "location": "junit-4.11.jar",
613        "version": "?"
614      }, {
615        "class": "org.junit.internal.runners.statements.InvokeMethod",
616        "method": "evaluate",
617        "file": "InvokeMethod.java",
618        "line": 17,
619        "exact": true,
620        "location": "junit-4.11.jar",
621        "version": "?"
622      }, {
623        "class": "org.junit.runners.ParentRunner",
624        "method": "runLeaf",
625        "file": "ParentRunner.java",
626        "line": 271,
627        "exact": true,
628        "location": "junit-4.11.jar",
629        "version": "?"
630      }, {
631        "class": "org.junit.runners.BlockJUnit4ClassRunner",
632        "method": "runChild",
633        "file": "BlockJUnit4ClassRunner.java",
634        "line": 70,
635        "exact": true,
636        "location": "junit-4.11.jar",
637        "version": "?"
638      }, {
639        "class": "org.junit.runners.BlockJUnit4ClassRunner",
640        "method": "runChild",
641        "file": "BlockJUnit4ClassRunner.java",
642        "line": 50,
643        "exact": true,
644        "location": "junit-4.11.jar",
645        "version": "?"
646      }, {
647        "class": "org.junit.runners.ParentRunner$3",
648        "method": "run",
649        "file": "ParentRunner.java",
650        "line": 238,
651        "exact": true,
652        "location": "junit-4.11.jar",
653        "version": "?"
654      }, {
655        "class": "org.junit.runners.ParentRunner$1",
656        "method": "schedule",
657        "file": "ParentRunner.java",
658        "line": 63,
659        "exact": true,
660        "location": "junit-4.11.jar",
661        "version": "?"
662      }, {
663        "class": "org.junit.runners.ParentRunner",
664        "method": "runChildren",
665        "file": "ParentRunner.java",
666        "line": 236,
667        "exact": true,
668        "location": "junit-4.11.jar",
669        "version": "?"
670      }, {
671        "class": "org.junit.runners.ParentRunner",
672        "method": "access$000",
673        "file": "ParentRunner.java",
674        "line": 53,
675        "exact": true,
676        "location": "junit-4.11.jar",
677        "version": "?"
678      }, {
679        "class": "org.junit.runners.ParentRunner$2",
680        "method": "evaluate",
681        "file": "ParentRunner.java",
682        "line": 229,
683        "exact": true,
684        "location": "junit-4.11.jar",
685        "version": "?"
686      }, {
687        "class": "org.junit.internal.runners.statements.RunBefores",
688        "method": "evaluate",
689        "file": "RunBefores.java",
690        "line": 26,
691        "exact": true,
692        "location": "junit-4.11.jar",
693        "version": "?"
694      }, {
695        "class": "org.junit.internal.runners.statements.RunAfters",
696        "method": "evaluate",
697        "file": "RunAfters.java",
698        "line": 27,
699        "exact": true,
700        "location": "junit-4.11.jar",
701        "version": "?"
702      }, {
703        "class": "org.junit.runners.ParentRunner",
704        "method": "run",
705        "file": "ParentRunner.java",
706        "line": 309,
707        "exact": true,
708        "location": "junit-4.11.jar",
709        "version": "?"
710      }, {
711        "class": "org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference",
712        "method": "run",
713        "file": "JUnit4TestReference.java",
714        "line": 50,
715        "exact": true,
716        "location": ".cp/",
717        "version": "?"
718      }, {
719        "class": "org.eclipse.jdt.internal.junit.runner.TestExecution",
720        "method": "run",
721        "file": "TestExecution.java",
722        "line": 38,
723        "exact": true,
724        "location": ".cp/",
725        "version": "?"
726      }, {
727        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
728        "method": "runTests",
729        "file": "RemoteTestRunner.java",
730        "line": 467,
731        "exact": true,
732        "location": ".cp/",
733        "version": "?"
734      }, {
735        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
736        "method": "runTests",
737        "file": "RemoteTestRunner.java",
738        "line": 683,
739        "exact": true,
740        "location": ".cp/",
741        "version": "?"
742      }, {
743        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
744        "method": "run",
745        "file": "RemoteTestRunner.java",
746        "line": 390,
747        "exact": true,
748        "location": ".cp/",
749        "version": "?"
750      }, {
751        "class": "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner",
752        "method": "main",
753        "file": "RemoteTestRunner.java",
754        "line": 197,
755        "exact": true,
756        "location": ".cp/",
757        "version": "?"
758      }],
759      "localizedMessage": "I am suppressed exception 2",
760      "message": "I am suppressed exception 2",
761      "name": "java.lang.IndexOutOfBoundsException"
762    }]
763  },
764  "loggerFQCN": "f.q.c.n",
765  "endOfBatch": false,
766  "contextMap": [{
767    "key": "MDC.B",
768    "value": "B_Value"
769  }, {
770    "key": "MDC.A",
771    "value": "A_Value"
772  }],
773  "contextStack": ["stack_msg1", "stack_msg2"],
774  "source": {
775    "class": "org.apache.logging.log4j.core.layout.LogEventFixtures",
776    "method": "createLogEvent",
777    "file": "LogEventFixtures.java",
778    "line": 54
779  }
780}
781 * </pre>
782 * <p>
783 * If {@code complete="false"}, the appender does not write the JSON open array character "[" at the start
784 * of the document, "]" and the end, nor comma "," between records.
785 * </p>
786 * <p>
787 * This approach enforces the independence of the JsonLayout and the appender where you embed it.
788 * </p>
789 * <h3>Encoding</h3>
790 * <p>
791 * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
792 * events containing non ASCII characters could result in corrupted log files.
793 * </p>
794 * <h3>Pretty vs. compact XML</h3>
795 * <p>
796 * By default, the JSON layout is not compact (a.k.a. "pretty") with {@code compact="false"}, which means the
797 * appender uses end-of-line characters and indents lines to format the text. If {@code compact="true"}, then no
798 * end-of-line or indentation is used. Message content may contain, of course, escaped end-of-lines.
799 * </p>
800 */
801@Plugin(name = "JsonLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
802public final class JsonLayout extends AbstractJacksonLayout {
803
804    private static final String DEFAULT_FOOTER = "]";
805
806    private static final String DEFAULT_HEADER = "[";
807
808    static final String CONTENT_TYPE = "application/json";
809
810    protected JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties,
811            final boolean encodeThreadContextAsList,
812            final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
813            final String footerPattern, final Charset charset) {
814        super(config, new JacksonFactory.JSON(encodeThreadContextAsList).newWriter(locationInfo, properties, compact),
815                charset, compact, complete, eventEol,
816                PatternLayout.createSerializer(config, null, headerPattern, DEFAULT_HEADER, null, false, false),
817                PatternLayout.createSerializer(config, null, footerPattern, DEFAULT_FOOTER, null, false, false));
818    }
819
820    /**
821     * Returns appropriate JSON header.
822     *
823     * @return a byte array containing the header, opening the JSON array.
824     */
825    @Override
826    public byte[] getHeader() {
827        if (!this.complete) {
828            return null;
829        }
830        final StringBuilder buf = new StringBuilder();
831        final String str = serializeToString(getHeaderSerializer());
832        if (str != null) {
833            buf.append(str);
834        }
835        buf.append(this.eol);
836        return getBytes(buf.toString());
837    }
838
839    /**
840     * Returns appropriate JSON footer.
841     *
842     * @return a byte array containing the footer, closing the JSON array.
843     */
844    @Override
845    public byte[] getFooter() {
846        if (!this.complete) {
847            return null;
848        }
849        final StringBuilder buf = new StringBuilder();
850        buf.append(this.eol);
851        final String str = serializeToString(getFooterSerializer());
852        if (str != null) {
853            buf.append(str);
854        }
855        buf.append(this.eol);
856        return getBytes(buf.toString());
857    }
858
859    @Override
860    public Map<String, String> getContentFormat() {
861        final Map<String, String> result = new HashMap<>();
862        result.put("version", "2.0");
863        return result;
864    }
865
866    @Override
867    /**
868     * @return The content type.
869     */
870    public String getContentType() {
871        return CONTENT_TYPE + "; charset=" + this.getCharset();
872    }
873
874    /**
875     * Creates a JSON Layout.
876     * @param config
877     *           The plugin configuration.
878     * @param locationInfo
879     *            If "true", includes the location information in the generated JSON.
880     * @param properties
881     *            If "true", includes the thread context map in the generated JSON.
882     * @param propertiesAsList
883     *            If true, the thread context map is included as a list of map entry objects, where each entry has
884     *            a "key" attribute (whose value is the key) and a "value" attribute (whose value is the value).
885     *            Defaults to false, in which case the thread context map is included as a simple map of key-value
886     *            pairs.
887     * @param complete
888     *            If "true", includes the JSON header and footer, and comma between records.
889     * @param compact
890     *            If "true", does not use end-of-lines and indentation, defaults to "false".
891     * @param eventEol
892     *            If "true", forces an EOL after each log event (even if compact is "true"), defaults to "false". This
893     *            allows one even per line, even in compact mode.
894     * @param headerPattern
895     *            The header pattern, defaults to {@code "["} if null.
896     * @param footerPattern
897     *            The header pattern, defaults to {@code "]"} if null.
898     * @param charset
899     *            The character set to use, if {@code null}, uses "UTF-8".
900     * @return A JSON Layout.
901     */
902    @PluginFactory
903    public static JsonLayout createLayout(
904            // @formatter:off
905            @PluginConfiguration final Configuration config,
906            @PluginAttribute(value = "locationInfo", defaultBoolean = false) final boolean locationInfo,
907            @PluginAttribute(value = "properties", defaultBoolean = false) final boolean properties,
908            @PluginAttribute(value = "propertiesAsList", defaultBoolean = false) final boolean propertiesAsList,
909            @PluginAttribute(value = "complete", defaultBoolean = false) final boolean complete,
910            @PluginAttribute(value = "compact", defaultBoolean = false) final boolean compact,
911            @PluginAttribute(value = "eventEol", defaultBoolean = false) final boolean eventEol,
912            @PluginAttribute(value = "header", defaultString = DEFAULT_HEADER) final String headerPattern,
913            @PluginAttribute(value = "footer", defaultString = DEFAULT_FOOTER) final String footerPattern,
914            @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset
915            // @formatter:on
916    ) {
917        final boolean encodeThreadContextAsList = properties && propertiesAsList;
918        return new JsonLayout(config, locationInfo, properties, encodeThreadContextAsList, complete, compact, eventEol,
919                headerPattern, footerPattern, charset);
920    }
921
922    /**
923     * Creates a JSON Layout using the default settings. Useful for testing.
924     *
925     * @return A JSON Layout.
926     */
927    public static JsonLayout createDefaultLayout() {
928        return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, false,
929                DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8);
930    }
931
932    @Override
933    public void toSerializable(final LogEvent event, final Writer writer) throws IOException {
934        if (complete && eventCount > 0) {
935            writer.append(", ");
936        }
937        super.toSerializable(event, writer);
938    }
939}