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