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.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.OutputStream;
22 import java.io.PrintWriter;
23 import java.io.StringWriter;
24 import java.math.BigDecimal;
25 import java.nio.charset.StandardCharsets;
26 import java.util.Collections;
27 import java.util.Map;
28 import java.util.zip.DeflaterOutputStream;
29 import java.util.zip.GZIPOutputStream;
30
31 import org.apache.logging.log4j.Level;
32 import org.apache.logging.log4j.core.Layout;
33 import org.apache.logging.log4j.core.LogEvent;
34 import org.apache.logging.log4j.core.config.Node;
35 import org.apache.logging.log4j.core.config.plugins.Plugin;
36 import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
37 import org.apache.logging.log4j.core.config.plugins.PluginElement;
38 import org.apache.logging.log4j.core.config.plugins.PluginFactory;
39 import org.apache.logging.log4j.core.net.Severity;
40 import org.apache.logging.log4j.core.util.KeyValuePair;
41 import org.apache.logging.log4j.status.StatusLogger;
42 import org.apache.logging.log4j.util.Strings;
43
44 import com.fasterxml.jackson.core.io.JsonStringEncoder;
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 @Plugin(name = "GelfLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
73 public final class GelfLayout extends AbstractStringLayout {
74
75 public static enum CompressionType {
76
77 GZIP {
78 @Override
79 public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) throws IOException {
80 return new GZIPOutputStream(os);
81 }
82 },
83 ZLIB {
84 @Override
85 public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) throws IOException {
86 return new DeflaterOutputStream(os);
87 }
88 },
89 OFF {
90 @Override
91 public DeflaterOutputStream createDeflaterOutputStream(final OutputStream os) throws IOException {
92 return null;
93 }
94 };
95
96 public abstract DeflaterOutputStream createDeflaterOutputStream(OutputStream os) throws IOException;
97 }
98
99 private static final char C = ',';
100 private static final int COMPRESSION_THRESHOLD = 1024;
101 private static final char Q = '\"';
102 private static final String QC = "\",";
103 private static final String QU = "\"_";
104 private static final long serialVersionUID = 1L;
105 private static final BigDecimal TIME_DIVISOR = new BigDecimal(1000);
106
107 private final KeyValuePair[] additionalFields;
108 private final int compressionThreshold;
109 private final CompressionType compressionType;
110 private final String host;
111
112 public GelfLayout(final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType,
113 final int compressionThreshold) {
114 super(StandardCharsets.UTF_8);
115 this.host = host;
116 this.additionalFields = additionalFields;
117 this.compressionType = compressionType;
118 this.compressionThreshold = compressionThreshold;
119 }
120
121 @PluginFactory
122 public static GelfLayout createLayout(
123
124 @PluginAttribute("host") final String host,
125 @PluginElement("AdditionalField") final KeyValuePair[] additionalFields,
126 @PluginAttribute(value = "compressionType",
127 defaultString = "GZIP") final CompressionType compressionType,
128 @PluginAttribute(value = "compressionThreshold",
129 defaultInt= COMPRESSION_THRESHOLD) final int compressionThreshold) {
130
131 return new GelfLayout(host, additionalFields, compressionType, compressionThreshold);
132 }
133
134
135
136
137 static int formatLevel(final Level level) {
138 return Severity.getSeverity(level).getCode();
139 }
140
141 static String formatThrowable(final Throwable throwable) {
142
143 final StringWriter sw = new StringWriter(2048);
144 final PrintWriter pw = new PrintWriter(sw);
145 throwable.printStackTrace(pw);
146 pw.flush();
147 return sw.toString();
148 }
149
150 static String formatTimestamp(final long timeMillis) {
151 return new BigDecimal(timeMillis).divide(TIME_DIVISOR).toPlainString();
152 }
153
154 private byte[] compress(final byte[] bytes) {
155 try {
156 final ByteArrayOutputStream baos = new ByteArrayOutputStream(compressionThreshold / 8);
157 try (final DeflaterOutputStream stream = compressionType.createDeflaterOutputStream(baos)) {
158 if (stream == null) {
159 return bytes;
160 }
161 stream.write(bytes);
162 stream.finish();
163 }
164 return baos.toByteArray();
165 } catch (final IOException e) {
166 StatusLogger.getLogger().error(e);
167 return bytes;
168 }
169 }
170
171 @Override
172 public Map<String, String> getContentFormat() {
173 return Collections.emptyMap();
174 }
175
176 @Override
177 public String getContentType() {
178 return JsonLayout.CONTENT_TYPE + "; charset=" + this.getCharset();
179 }
180
181 @Override
182 public byte[] toByteArray(final LogEvent event) {
183 final byte[] bytes = getBytes(toSerializable(event));
184 return bytes.length > compressionThreshold ? compress(bytes) : bytes;
185 }
186
187 @Override
188 public String toSerializable(final LogEvent event) {
189 final StringBuilder builder = getStringBuilder();
190 final JsonStringEncoder jsonEncoder = JsonStringEncoder.getInstance();
191 builder.append('{');
192 builder.append("\"version\":\"1.1\",");
193 builder.append("\"host\":\"").append(jsonEncoder.quoteAsString(toNullSafeString(host))).append(QC);
194 builder.append("\"timestamp\":").append(formatTimestamp(event.getTimeMillis())).append(C);
195 builder.append("\"level\":").append(formatLevel(event.getLevel())).append(C);
196 if (event.getThreadName() != null) {
197 builder.append("\"_thread\":\"").append(jsonEncoder.quoteAsString(event.getThreadName())).append(QC);
198 }
199 if (event.getLoggerName() != null) {
200 builder.append("\"_logger\":\"").append(jsonEncoder.quoteAsString(event.getLoggerName())).append(QC);
201 }
202
203 for (final KeyValuePair additionalField : additionalFields) {
204 builder.append(QU).append(jsonEncoder.quoteAsString(additionalField.getKey())).append("\":\"")
205 .append(jsonEncoder.quoteAsString(toNullSafeString(additionalField.getValue()))).append(QC);
206 }
207 for (final Map.Entry<String, String> entry : event.getContextMap().entrySet()) {
208 builder.append(QU).append(jsonEncoder.quoteAsString(entry.getKey())).append("\":\"")
209 .append(jsonEncoder.quoteAsString(toNullSafeString(entry.getValue()))).append(QC);
210 }
211 if (event.getThrown() != null) {
212 builder.append("\"full_message\":\"").append(jsonEncoder.quoteAsString(formatThrowable(event.getThrown())))
213 .append(QC);
214 }
215
216 builder.append("\"short_message\":\"").append(jsonEncoder.quoteAsString(toNullSafeString(event.getMessage().getFormattedMessage())))
217 .append(Q);
218 builder.append('}');
219 return builder.toString();
220 }
221
222 private String toNullSafeString(final String s) {
223 return s == null ? Strings.EMPTY : s;
224 }
225 }