1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.logging.log4j.core.layout;
18
19 import java.nio.charset.Charset;
20 import java.util.HashMap;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import java.util.Set;
25
26 import org.apache.logging.log4j.core.LogEvent;
27 import org.apache.logging.log4j.core.config.plugins.Plugin;
28 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
29 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
30 import org.apache.logging.log4j.core.helpers.Charsets;
31 import org.apache.logging.log4j.core.helpers.Throwables;
32 import org.apache.logging.log4j.core.helpers.Transform;
33 import org.apache.logging.log4j.message.Message;
34 import org.apache.logging.log4j.message.MultiformatMessage;
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85 @Plugin(name = "JSONLayout", category = "Core", elementType = "layout", printObject = true)
86 public class JSONLayout extends AbstractStringLayout {
87
88 private static final int DEFAULT_SIZE = 256;
89
90
91 private static final String DEFAULT_EOL = "\r\n";
92 private static final String COMPACT_EOL = "";
93 private static final String DEFAULT_INDENT = " ";
94 private static final String COMPACT_INDENT = "";
95
96 private static final String[] FORMATS = new String[] { "json" };
97
98 private final boolean locationInfo;
99 private final boolean properties;
100 private final boolean complete;
101 private final String eol;
102 private final String indent1;
103 private final String indent2;
104 private final String indent3;
105 private final String indent4;
106 private volatile boolean firstLayoutDone;
107
108 protected JSONLayout(final boolean locationInfo, final boolean properties, final boolean complete, boolean compact,
109 final Charset charset) {
110 super(charset);
111 this.locationInfo = locationInfo;
112 this.properties = properties;
113 this.complete = complete;
114 this.eol = compact ? COMPACT_EOL : DEFAULT_EOL;
115 this.indent1 = compact ? COMPACT_INDENT : DEFAULT_INDENT;
116 this.indent2 = this.indent1 + this.indent1;
117 this.indent3 = this.indent2 + this.indent1;
118 this.indent4 = this.indent3 + this.indent1;
119 }
120
121
122
123
124
125
126
127
128 @Override
129 public String toSerializable(final LogEvent event) {
130 final StringBuilder buf = new StringBuilder(DEFAULT_SIZE);
131
132 boolean check = this.firstLayoutDone;
133 if (!this.firstLayoutDone) {
134 synchronized(this) {
135 check = this.firstLayoutDone;
136 if (!check) {
137 this.firstLayoutDone = true;
138 } else {
139 buf.append(',');
140 buf.append(this.eol);
141 }
142 }
143 } else {
144 buf.append(',');
145 buf.append(this.eol);
146 }
147 buf.append(this.indent1);
148 buf.append('{');
149 buf.append(this.eol);
150 buf.append(this.indent2);
151 buf.append("\"logger\":\"");
152 String name = event.getLoggerName();
153 if (name.isEmpty()) {
154 name = "root";
155 }
156 buf.append(Transform.escapeJsonControlCharacters(name));
157 buf.append("\",");
158 buf.append(this.eol);
159 buf.append(this.indent2);
160 buf.append("\"timestamp\":\"");
161 buf.append(event.getMillis());
162 buf.append("\",");
163 buf.append(this.eol);
164 buf.append(this.indent2);
165 buf.append("\"level\":\"");
166 buf.append(Transform.escapeJsonControlCharacters(String.valueOf(event.getLevel())));
167 buf.append("\",");
168 buf.append(this.eol);
169 buf.append(this.indent2);
170 buf.append("\"thread\":\"");
171 buf.append(Transform.escapeJsonControlCharacters(event.getThreadName()));
172 buf.append("\",");
173 buf.append(this.eol);
174
175 final Message msg = event.getMessage();
176 if (msg != null) {
177 boolean jsonSupported = false;
178 if (msg instanceof MultiformatMessage) {
179 final String[] formats = ((MultiformatMessage) msg).getFormats();
180 for (final String format : formats) {
181 if (format.equalsIgnoreCase("JSON")) {
182 jsonSupported = true;
183 break;
184 }
185 }
186 }
187 buf.append(this.indent2);
188 buf.append("\"message\":\"");
189 if (jsonSupported) {
190 buf.append(((MultiformatMessage) msg).getFormattedMessage(FORMATS));
191 } else {
192 buf.append(Transform.escapeJsonControlCharacters(event.getMessage().getFormattedMessage()));
193 }
194 buf.append('\"');
195 }
196
197 if (event.getContextStack().getDepth() > 0) {
198 buf.append(",");
199 buf.append(this.eol);
200 buf.append("\"ndc\":");
201 buf.append(Transform.escapeJsonControlCharacters(event.getContextStack().toString()));
202 buf.append("\"");
203 }
204
205 final Throwable throwable = event.getThrown();
206 if (throwable != null) {
207 buf.append(",");
208 buf.append(this.eol);
209 buf.append(this.indent2);
210 buf.append("\"throwable\":\"");
211 final List<String> list = Throwables.toStringList(throwable);
212 for (final String str : list) {
213 buf.append(Transform.escapeJsonControlCharacters(str));
214 buf.append("\\\\n");
215 }
216 buf.append("\"");
217 }
218
219 if (this.locationInfo) {
220 final StackTraceElement element = event.getSource();
221 buf.append(",");
222 buf.append(this.eol);
223 buf.append(this.indent2);
224 buf.append("\"LocationInfo\":{");
225 buf.append(this.eol);
226 buf.append(this.indent3);
227 buf.append("\"class\":\"");
228 buf.append(Transform.escapeJsonControlCharacters(element.getClassName()));
229 buf.append("\",");
230 buf.append(this.eol);
231 buf.append(this.indent3);
232 buf.append("\"method\":\"");
233 buf.append(Transform.escapeJsonControlCharacters(element.getMethodName()));
234 buf.append("\",");
235 buf.append(this.eol);
236 buf.append(this.indent3);
237 buf.append("\"file\":\"");
238 buf.append(Transform.escapeJsonControlCharacters(element.getFileName()));
239 buf.append("\",");
240 buf.append(this.eol);
241 buf.append(this.indent3);
242 buf.append("\"line\":\"");
243 buf.append(element.getLineNumber());
244 buf.append("\"");
245 buf.append(this.eol);
246 buf.append(this.indent2);
247 buf.append("}");
248 }
249
250 if (this.properties && event.getContextMap().size() > 0) {
251 buf.append(",");
252 buf.append(this.eol);
253 buf.append(this.indent2);
254 buf.append("\"Properties\":[");
255 buf.append(this.eol);
256 final Set<Entry<String, String>> entrySet = event.getContextMap().entrySet();
257 int i = 1;
258 for (final Map.Entry<String, String> entry : entrySet) {
259 buf.append(this.indent3);
260 buf.append('{');
261 buf.append(this.eol);
262 buf.append(this.indent4);
263 buf.append("\"name\":\"");
264 buf.append(Transform.escapeJsonControlCharacters(entry.getKey()));
265 buf.append("\",");
266 buf.append(this.eol);
267 buf.append(this.indent4);
268 buf.append("\"value\":\"");
269 buf.append(Transform.escapeJsonControlCharacters(String.valueOf(entry.getValue())));
270 buf.append("\"");
271 buf.append(this.eol);
272 buf.append(this.indent3);
273 buf.append("}");
274 if (i < entrySet.size()) {
275 buf.append(",");
276 }
277 buf.append(this.eol);
278 i++;
279 }
280 buf.append(this.indent2);
281 buf.append("]");
282 }
283
284 buf.append(this.eol);
285 buf.append(this.indent1);
286 buf.append("}");
287
288 return buf.toString();
289 }
290
291
292
293
294
295
296 @Override
297 public byte[] getHeader() {
298 if (!this.complete) {
299 return null;
300 }
301 final StringBuilder buf = new StringBuilder();
302 buf.append('[');
303 buf.append(this.eol);
304 return buf.toString().getBytes(this.getCharset());
305 }
306
307
308
309
310
311
312 @Override
313 public byte[] getFooter() {
314 if (!this.complete) {
315 return null;
316 }
317 return (this.eol + "]" + this.eol).getBytes(this.getCharset());
318 }
319
320
321
322
323
324
325
326
327
328
329 @Override
330 public Map<String, String> getContentFormat() {
331 final Map<String, String> result = new HashMap<String, String>();
332 result.put("version", "2.0");
333 return result;
334 }
335
336 @Override
337
338
339
340 public String getContentType() {
341 return "application/json; charset=" + this.getCharset();
342 }
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359 @PluginFactory
360 public static JSONLayout createLayout(
361 @PluginAttribute("locationInfo") final String locationInfo,
362 @PluginAttribute("properties") final String properties,
363 @PluginAttribute("complete") final String completeStr,
364 @PluginAttribute("compact") final String compactStr,
365 @PluginAttribute("charset") final String charsetName) {
366 final Charset charset = Charsets.getSupportedCharset(charsetName, Charsets.UTF_8);
367 final boolean info = Boolean.parseBoolean(locationInfo);
368 final boolean props = Boolean.parseBoolean(properties);
369 final boolean complete = Boolean.parseBoolean(completeStr);
370 final boolean compact = Boolean.parseBoolean(compactStr);
371 return new JSONLayout(info, props, complete, compact, charset);
372 }
373 }