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;
035import org.apache.logging.log4j.util.Strings;
036
037/**
038 * Appends a series of YAML events as strings serialized as bytes.
039 *
040 * <p>
041 * A YAML log event follows this pattern:
042 * </p>
043 *
044 * <pre>---
045timeMillis: 1
046thread: "MyThreadName"
047level: "DEBUG"
048loggerName: "a.B"
049marker:
050  name: "Marker1"
051  parents:
052  - name: "ParentMarker1"
053    parents:
054    - name: "GrandMotherMarker"
055    - name: "GrandFatherMarker"
056  - name: "ParentMarker2"
057message: "Msg"
058thrown:
059  commonElementCount: 0
060  localizedMessage: "testIOEx"
061  message: "testIOEx"
062  name: "java.io.IOException"
063  cause:
064    commonElementCount: 27
065    localizedMessage: "testNPEx"
066    message: "testNPEx"
067    name: "java.lang.NullPointerException"
068    extendedStackTrace:
069    - class: "org.apache.logging.log4j.core.layout.LogEventFixtures"
070      method: "createLogEvent"
071      file: "LogEventFixtures.java"
072      line: 52
073      exact: false
074      location: "test-classes/"
075      version: "?"
076  extendedStackTrace:
077  - class: "org.apache.logging.log4j.core.layout.LogEventFixtures"
078    method: "createLogEvent"
079    file: "LogEventFixtures.java"
080    line: 55
081    exact: true
082    location: "test-classes/"
083    version: "?"
084  - class: "org.apache.logging.log4j.core.layout.YamlLayoutTest"
085    method: "testAllFeatures"
086    file: "YamlLayoutTest.java"
087    line: 109
088    exact: true
089    location: "test-classes/"
090    version: "?"
091  - class: "org.apache.logging.log4j.core.layout.YamlLayoutTest"
092    method: "testLocationOnCompactOffEventEolOffMdcOn"
093    file: "YamlLayoutTest.java"
094    line: 280
095    exact: true
096    location: "test-classes/"
097    version: "?"
098  - class: "sun.reflect.NativeMethodAccessorImpl"
099    method: "invoke0"
100    file: "NativeMethodAccessorImpl.java"
101    line: -2
102    exact: false
103    location: "?"
104    version: "1.7.0_79"
105  - class: "sun.reflect.NativeMethodAccessorImpl"
106    method: "invoke"
107    file: "NativeMethodAccessorImpl.java"
108    line: 57
109    exact: false
110    location: "?"
111    version: "1.7.0_79"
112  - class: "sun.reflect.DelegatingMethodAccessorImpl"
113    method: "invoke"
114    file: "DelegatingMethodAccessorImpl.java"
115    line: 43
116    exact: false
117    location: "?"
118    version: "1.7.0_79"
119  - class: "java.lang.reflect.Method"
120    method: "invoke"
121    file: "Method.java"
122    line: 606
123    exact: false
124    location: "?"
125    version: "1.7.0_79"
126  - class: "org.junit.runners.model.FrameworkMethod$1"
127    method: "runReflectiveCall"
128    file: "FrameworkMethod.java"
129    line: 50
130    exact: true
131    location: "junit-4.12.jar"
132    version: "4.12"
133  - class: "org.junit.internal.runners.model.ReflectiveCallable"
134    method: "run"
135    file: "ReflectiveCallable.java"
136    line: 12
137    exact: true
138    location: "junit-4.12.jar"
139    version: "4.12"
140  - class: "org.junit.runners.model.FrameworkMethod"
141    method: "invokeExplosively"
142    file: "FrameworkMethod.java"
143    line: 47
144    exact: true
145    location: "junit-4.12.jar"
146    version: "4.12"
147  - class: "org.junit.internal.runners.statements.InvokeMethod"
148    method: "evaluate"
149    file: "InvokeMethod.java"
150    line: 17
151    exact: true
152    location: "junit-4.12.jar"
153    version: "4.12"
154  - class: "org.junit.runners.ParentRunner"
155    method: "runLeaf"
156    file: "ParentRunner.java"
157    line: 325
158    exact: true
159    location: "junit-4.12.jar"
160    version: "4.12"
161  - class: "org.junit.runners.BlockJUnit4ClassRunner"
162    method: "runChild"
163    file: "BlockJUnit4ClassRunner.java"
164    line: 78
165    exact: true
166    location: "junit-4.12.jar"
167    version: "4.12"
168  - class: "org.junit.runners.BlockJUnit4ClassRunner"
169    method: "runChild"
170    file: "BlockJUnit4ClassRunner.java"
171    line: 57
172    exact: true
173    location: "junit-4.12.jar"
174    version: "4.12"
175  - class: "org.junit.runners.ParentRunner$3"
176    method: "run"
177    file: "ParentRunner.java"
178    line: 290
179    exact: true
180    location: "junit-4.12.jar"
181    version: "4.12"
182  - class: "org.junit.runners.ParentRunner$1"
183    method: "schedule"
184    file: "ParentRunner.java"
185    line: 71
186    exact: true
187    location: "junit-4.12.jar"
188    version: "4.12"
189  - class: "org.junit.runners.ParentRunner"
190    method: "runChildren"
191    file: "ParentRunner.java"
192    line: 288
193    exact: true
194    location: "junit-4.12.jar"
195    version: "4.12"
196  - class: "org.junit.runners.ParentRunner"
197    method: "access$000"
198    file: "ParentRunner.java"
199    line: 58
200    exact: true
201    location: "junit-4.12.jar"
202    version: "4.12"
203  - class: "org.junit.runners.ParentRunner$2"
204    method: "evaluate"
205    file: "ParentRunner.java"
206    line: 268
207    exact: true
208    location: "junit-4.12.jar"
209    version: "4.12"
210  - class: "org.junit.internal.runners.statements.RunBefores"
211    method: "evaluate"
212    file: "RunBefores.java"
213    line: 26
214    exact: true
215    location: "junit-4.12.jar"
216    version: "4.12"
217  - class: "org.junit.internal.runners.statements.RunAfters"
218    method: "evaluate"
219    file: "RunAfters.java"
220    line: 27
221    exact: true
222    location: "junit-4.12.jar"
223    version: "4.12"
224  - class: "org.junit.runners.ParentRunner"
225    method: "run"
226    file: "ParentRunner.java"
227    line: 363
228    exact: true
229    location: "junit-4.12.jar"
230    version: "4.12"
231  - class: "org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference"
232    method: "run"
233    file: "JUnit4TestReference.java"
234    line: 86
235    exact: true
236    location: ".cp/"
237    version: "?"
238  - class: "org.eclipse.jdt.internal.junit.runner.TestExecution"
239    method: "run"
240    file: "TestExecution.java"
241    line: 38
242    exact: true
243    location: ".cp/"
244    version: "?"
245  - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
246    method: "runTests"
247    file: "RemoteTestRunner.java"
248    line: 459
249    exact: true
250    location: ".cp/"
251    version: "?"
252  - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
253    method: "runTests"
254    file: "RemoteTestRunner.java"
255    line: 675
256    exact: true
257    location: ".cp/"
258    version: "?"
259  - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
260    method: "run"
261    file: "RemoteTestRunner.java"
262    line: 382
263    exact: true
264    location: ".cp/"
265    version: "?"
266  - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
267    method: "main"
268    file: "RemoteTestRunner.java"
269    line: 192
270    exact: true
271    location: ".cp/"
272    version: "?"
273  suppressed:
274  - commonElementCount: 0
275    localizedMessage: "I am suppressed exception 1"
276    message: "I am suppressed exception 1"
277    name: "java.lang.IndexOutOfBoundsException"
278    extendedStackTrace:
279    - class: "org.apache.logging.log4j.core.layout.LogEventFixtures"
280      method: "createLogEvent"
281      file: "LogEventFixtures.java"
282      line: 56
283      exact: true
284      location: "test-classes/"
285      version: "?"
286    - class: "org.apache.logging.log4j.core.layout.YamlLayoutTest"
287      method: "testAllFeatures"
288      file: "YamlLayoutTest.java"
289      line: 109
290      exact: true
291      location: "test-classes/"
292      version: "?"
293    - class: "org.apache.logging.log4j.core.layout.YamlLayoutTest"
294      method: "testLocationOnCompactOffEventEolOffMdcOn"
295      file: "YamlLayoutTest.java"
296      line: 280
297      exact: true
298      location: "test-classes/"
299      version: "?"
300    - class: "sun.reflect.NativeMethodAccessorImpl"
301      method: "invoke0"
302      file: "NativeMethodAccessorImpl.java"
303      line: -2
304      exact: false
305      location: "?"
306      version: "1.7.0_79"
307    - class: "sun.reflect.NativeMethodAccessorImpl"
308      method: "invoke"
309      file: "NativeMethodAccessorImpl.java"
310      line: 57
311      exact: false
312      location: "?"
313      version: "1.7.0_79"
314    - class: "sun.reflect.DelegatingMethodAccessorImpl"
315      method: "invoke"
316      file: "DelegatingMethodAccessorImpl.java"
317      line: 43
318      exact: false
319      location: "?"
320      version: "1.7.0_79"
321    - class: "java.lang.reflect.Method"
322      method: "invoke"
323      file: "Method.java"
324      line: 606
325      exact: false
326      location: "?"
327      version: "1.7.0_79"
328    - class: "org.junit.runners.model.FrameworkMethod$1"
329      method: "runReflectiveCall"
330      file: "FrameworkMethod.java"
331      line: 50
332      exact: true
333      location: "junit-4.12.jar"
334      version: "4.12"
335    - class: "org.junit.internal.runners.model.ReflectiveCallable"
336      method: "run"
337      file: "ReflectiveCallable.java"
338      line: 12
339      exact: true
340      location: "junit-4.12.jar"
341      version: "4.12"
342    - class: "org.junit.runners.model.FrameworkMethod"
343      method: "invokeExplosively"
344      file: "FrameworkMethod.java"
345      line: 47
346      exact: true
347      location: "junit-4.12.jar"
348      version: "4.12"
349    - class: "org.junit.internal.runners.statements.InvokeMethod"
350      method: "evaluate"
351      file: "InvokeMethod.java"
352      line: 17
353      exact: true
354      location: "junit-4.12.jar"
355      version: "4.12"
356    - class: "org.junit.runners.ParentRunner"
357      method: "runLeaf"
358      file: "ParentRunner.java"
359      line: 325
360      exact: true
361      location: "junit-4.12.jar"
362      version: "4.12"
363    - class: "org.junit.runners.BlockJUnit4ClassRunner"
364      method: "runChild"
365      file: "BlockJUnit4ClassRunner.java"
366      line: 78
367      exact: true
368      location: "junit-4.12.jar"
369      version: "4.12"
370    - class: "org.junit.runners.BlockJUnit4ClassRunner"
371      method: "runChild"
372      file: "BlockJUnit4ClassRunner.java"
373      line: 57
374      exact: true
375      location: "junit-4.12.jar"
376      version: "4.12"
377    - class: "org.junit.runners.ParentRunner$3"
378      method: "run"
379      file: "ParentRunner.java"
380      line: 290
381      exact: true
382      location: "junit-4.12.jar"
383      version: "4.12"
384    - class: "org.junit.runners.ParentRunner$1"
385      method: "schedule"
386      file: "ParentRunner.java"
387      line: 71
388      exact: true
389      location: "junit-4.12.jar"
390      version: "4.12"
391    - class: "org.junit.runners.ParentRunner"
392      method: "runChildren"
393      file: "ParentRunner.java"
394      line: 288
395      exact: true
396      location: "junit-4.12.jar"
397      version: "4.12"
398    - class: "org.junit.runners.ParentRunner"
399      method: "access$000"
400      file: "ParentRunner.java"
401      line: 58
402      exact: true
403      location: "junit-4.12.jar"
404      version: "4.12"
405    - class: "org.junit.runners.ParentRunner$2"
406      method: "evaluate"
407      file: "ParentRunner.java"
408      line: 268
409      exact: true
410      location: "junit-4.12.jar"
411      version: "4.12"
412    - class: "org.junit.internal.runners.statements.RunBefores"
413      method: "evaluate"
414      file: "RunBefores.java"
415      line: 26
416      exact: true
417      location: "junit-4.12.jar"
418      version: "4.12"
419    - class: "org.junit.internal.runners.statements.RunAfters"
420      method: "evaluate"
421      file: "RunAfters.java"
422      line: 27
423      exact: true
424      location: "junit-4.12.jar"
425      version: "4.12"
426    - class: "org.junit.runners.ParentRunner"
427      method: "run"
428      file: "ParentRunner.java"
429      line: 363
430      exact: true
431      location: "junit-4.12.jar"
432      version: "4.12"
433    - class: "org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference"
434      method: "run"
435      file: "JUnit4TestReference.java"
436      line: 86
437      exact: true
438      location: ".cp/"
439      version: "?"
440    - class: "org.eclipse.jdt.internal.junit.runner.TestExecution"
441      method: "run"
442      file: "TestExecution.java"
443      line: 38
444      exact: true
445      location: ".cp/"
446      version: "?"
447    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
448      method: "runTests"
449      file: "RemoteTestRunner.java"
450      line: 459
451      exact: true
452      location: ".cp/"
453      version: "?"
454    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
455      method: "runTests"
456      file: "RemoteTestRunner.java"
457      line: 675
458      exact: true
459      location: ".cp/"
460      version: "?"
461    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
462      method: "run"
463      file: "RemoteTestRunner.java"
464      line: 382
465      exact: true
466      location: ".cp/"
467      version: "?"
468    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
469      method: "main"
470      file: "RemoteTestRunner.java"
471      line: 192
472      exact: true
473      location: ".cp/"
474      version: "?"
475  - commonElementCount: 0
476    localizedMessage: "I am suppressed exception 2"
477    message: "I am suppressed exception 2"
478    name: "java.lang.IndexOutOfBoundsException"
479    extendedStackTrace:
480    - class: "org.apache.logging.log4j.core.layout.LogEventFixtures"
481      method: "createLogEvent"
482      file: "LogEventFixtures.java"
483      line: 57
484      exact: true
485      location: "test-classes/"
486      version: "?"
487    - class: "org.apache.logging.log4j.core.layout.YamlLayoutTest"
488      method: "testAllFeatures"
489      file: "YamlLayoutTest.java"
490      line: 109
491      exact: true
492      location: "test-classes/"
493      version: "?"
494    - class: "org.apache.logging.log4j.core.layout.YamlLayoutTest"
495      method: "testLocationOnCompactOffEventEolOffMdcOn"
496      file: "YamlLayoutTest.java"
497      line: 280
498      exact: true
499      location: "test-classes/"
500      version: "?"
501    - class: "sun.reflect.NativeMethodAccessorImpl"
502      method: "invoke0"
503      file: "NativeMethodAccessorImpl.java"
504      line: -2
505      exact: false
506      location: "?"
507      version: "1.7.0_79"
508    - class: "sun.reflect.NativeMethodAccessorImpl"
509      method: "invoke"
510      file: "NativeMethodAccessorImpl.java"
511      line: 57
512      exact: false
513      location: "?"
514      version: "1.7.0_79"
515    - class: "sun.reflect.DelegatingMethodAccessorImpl"
516      method: "invoke"
517      file: "DelegatingMethodAccessorImpl.java"
518      line: 43
519      exact: false
520      location: "?"
521      version: "1.7.0_79"
522    - class: "java.lang.reflect.Method"
523      method: "invoke"
524      file: "Method.java"
525      line: 606
526      exact: false
527      location: "?"
528      version: "1.7.0_79"
529    - class: "org.junit.runners.model.FrameworkMethod$1"
530      method: "runReflectiveCall"
531      file: "FrameworkMethod.java"
532      line: 50
533      exact: true
534      location: "junit-4.12.jar"
535      version: "4.12"
536    - class: "org.junit.internal.runners.model.ReflectiveCallable"
537      method: "run"
538      file: "ReflectiveCallable.java"
539      line: 12
540      exact: true
541      location: "junit-4.12.jar"
542      version: "4.12"
543    - class: "org.junit.runners.model.FrameworkMethod"
544      method: "invokeExplosively"
545      file: "FrameworkMethod.java"
546      line: 47
547      exact: true
548      location: "junit-4.12.jar"
549      version: "4.12"
550    - class: "org.junit.internal.runners.statements.InvokeMethod"
551      method: "evaluate"
552      file: "InvokeMethod.java"
553      line: 17
554      exact: true
555      location: "junit-4.12.jar"
556      version: "4.12"
557    - class: "org.junit.runners.ParentRunner"
558      method: "runLeaf"
559      file: "ParentRunner.java"
560      line: 325
561      exact: true
562      location: "junit-4.12.jar"
563      version: "4.12"
564    - class: "org.junit.runners.BlockJUnit4ClassRunner"
565      method: "runChild"
566      file: "BlockJUnit4ClassRunner.java"
567      line: 78
568      exact: true
569      location: "junit-4.12.jar"
570      version: "4.12"
571    - class: "org.junit.runners.BlockJUnit4ClassRunner"
572      method: "runChild"
573      file: "BlockJUnit4ClassRunner.java"
574      line: 57
575      exact: true
576      location: "junit-4.12.jar"
577      version: "4.12"
578    - class: "org.junit.runners.ParentRunner$3"
579      method: "run"
580      file: "ParentRunner.java"
581      line: 290
582      exact: true
583      location: "junit-4.12.jar"
584      version: "4.12"
585    - class: "org.junit.runners.ParentRunner$1"
586      method: "schedule"
587      file: "ParentRunner.java"
588      line: 71
589      exact: true
590      location: "junit-4.12.jar"
591      version: "4.12"
592    - class: "org.junit.runners.ParentRunner"
593      method: "runChildren"
594      file: "ParentRunner.java"
595      line: 288
596      exact: true
597      location: "junit-4.12.jar"
598      version: "4.12"
599    - class: "org.junit.runners.ParentRunner"
600      method: "access$000"
601      file: "ParentRunner.java"
602      line: 58
603      exact: true
604      location: "junit-4.12.jar"
605      version: "4.12"
606    - class: "org.junit.runners.ParentRunner$2"
607      method: "evaluate"
608      file: "ParentRunner.java"
609      line: 268
610      exact: true
611      location: "junit-4.12.jar"
612      version: "4.12"
613    - class: "org.junit.internal.runners.statements.RunBefores"
614      method: "evaluate"
615      file: "RunBefores.java"
616      line: 26
617      exact: true
618      location: "junit-4.12.jar"
619      version: "4.12"
620    - class: "org.junit.internal.runners.statements.RunAfters"
621      method: "evaluate"
622      file: "RunAfters.java"
623      line: 27
624      exact: true
625      location: "junit-4.12.jar"
626      version: "4.12"
627    - class: "org.junit.runners.ParentRunner"
628      method: "run"
629      file: "ParentRunner.java"
630      line: 363
631      exact: true
632      location: "junit-4.12.jar"
633      version: "4.12"
634    - class: "org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference"
635      method: "run"
636      file: "JUnit4TestReference.java"
637      line: 86
638      exact: true
639      location: ".cp/"
640      version: "?"
641    - class: "org.eclipse.jdt.internal.junit.runner.TestExecution"
642      method: "run"
643      file: "TestExecution.java"
644      line: 38
645      exact: true
646      location: ".cp/"
647      version: "?"
648    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
649      method: "runTests"
650      file: "RemoteTestRunner.java"
651      line: 459
652      exact: true
653      location: ".cp/"
654      version: "?"
655    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
656      method: "runTests"
657      file: "RemoteTestRunner.java"
658      line: 675
659      exact: true
660      location: ".cp/"
661      version: "?"
662    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
663      method: "run"
664      file: "RemoteTestRunner.java"
665      line: 382
666      exact: true
667      location: ".cp/"
668      version: "?"
669    - class: "org.eclipse.jdt.internal.junit.runner.RemoteTestRunner"
670      method: "main"
671      file: "RemoteTestRunner.java"
672      line: 192
673      exact: true
674      location: ".cp/"
675      version: "?"
676contextStack:
677- "stack_msg1"
678- "stack_msg2"
679endOfBatch: false
680loggerFqcn: "f.q.c.n"
681contextMap:
682- key: "MDC.B"
683  value: "B_Value"
684- key: "MDC.A"
685  value: "A_Value"
686threadId: 1
687threadPriority: 5
688source:
689  class: "org.apache.logging.log4j.core.layout.LogEventFixtures"
690  method: "createLogEvent"
691  file: "LogEventFixtures.java"
692  line: 53</pre>
693 * <p>
694 * This approach enforces the independence of the YamlLayout and the appender where you embed it.
695 * </p>
696 * <h3>Encoding</h3>
697 * <p>
698 * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
699 * events containing non ASCII characters could result in corrupted log files.
700 * </p>
701 */
702@Plugin(name = "YamlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
703public final class YamlLayout extends AbstractJacksonLayout {
704
705    private static final String DEFAULT_FOOTER = Strings.EMPTY;
706
707    private static final String DEFAULT_HEADER = Strings.EMPTY;
708
709    static final String CONTENT_TYPE = "application/yaml";
710
711    protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
712            final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
713            final String footerPattern, final Charset charset) {
714        super(config, new JacksonFactory.YAML().newWriter(locationInfo, properties, compact), charset, compact,
715                complete, eventEol,
716                PatternLayout.createSerializer(config, null, headerPattern, DEFAULT_HEADER, null, false, false),
717                PatternLayout.createSerializer(config, null, footerPattern, DEFAULT_FOOTER, null, false, false));
718    }
719
720    /**
721     * Returns appropriate YAML header.
722     *
723     * @return a byte array containing the header, opening the YAML array.
724     */
725    @Override
726    public byte[] getHeader() {
727        if (!this.complete) {
728            return null;
729        }
730        final StringBuilder buf = new StringBuilder();
731        final String str = serializeToString(getHeaderSerializer());
732        if (str != null) {
733            buf.append(str);
734        }
735        buf.append(this.eol);
736        return getBytes(buf.toString());
737    }
738
739    /**
740     * Returns appropriate YAML footer.
741     *
742     * @return a byte array containing the footer, closing the YAML array.
743     */
744    @Override
745    public byte[] getFooter() {
746        if (!this.complete) {
747            return null;
748        }
749        final StringBuilder buf = new StringBuilder();
750        buf.append(this.eol);
751        final String str = serializeToString(getFooterSerializer());
752        if (str != null) {
753            buf.append(str);
754        }
755        buf.append(this.eol);
756        return getBytes(buf.toString());
757    }
758
759    @Override
760    public Map<String, String> getContentFormat() {
761        final Map<String, String> result = new HashMap<>();
762        result.put("version", "2.0");
763        return result;
764    }
765
766    @Override
767    /**
768     * @return The content type.
769     */
770    public String getContentType() {
771        return CONTENT_TYPE + "; charset=" + this.getCharset();
772    }
773
774    /**
775     * Creates a YAML Layout.
776     * 
777     * @param config
778     *            The plugin configuration.
779     * @param locationInfo
780     *            If "true", includes the location information in the generated YAML.
781     * @param properties
782     *            If "true", includes the thread context map in the generated YAML.
783     * @param headerPattern
784     *            The header pattern, defaults to {@code ""} if null.
785     * @param footerPattern
786     *            The header pattern, defaults to {@code ""} if null.
787     * @param charset
788     *            The character set to use, if {@code null}, uses "UTF-8".
789     * @return A YAML Layout.
790     */
791    @PluginFactory
792    public static AbstractJacksonLayout createLayout(
793            // @formatter:off
794            @PluginConfiguration final Configuration config,
795            @PluginAttribute(value = "locationInfo", defaultBoolean = false) final boolean locationInfo,
796            @PluginAttribute(value = "properties", defaultBoolean = false) final boolean properties,
797            @PluginAttribute(value = "header", defaultString = DEFAULT_HEADER) final String headerPattern,
798            @PluginAttribute(value = "footer", defaultString = DEFAULT_FOOTER) final String footerPattern,
799            @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset
800            // @formatter:on
801    ) {
802        return new YamlLayout(config, locationInfo, properties, false, false, true, headerPattern, footerPattern,
803                charset);
804    }
805
806    /**
807     * Creates a YAML Layout using the default settings. Useful for testing.
808     *
809     * @return A YAML Layout.
810     */
811    public static AbstractJacksonLayout createDefaultLayout() {
812        return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER,
813                DEFAULT_FOOTER, StandardCharsets.UTF_8);
814    }
815
816    @Override
817    public void toSerializable(final LogEvent event, final Writer writer) throws IOException {
818        if (complete && eventCount > 0) {
819            writer.append(", ");
820        }
821        super.toSerializable(event, writer);
822    }
823}