1 /**
2  * Licensed under the Apache License, Version 2.0 (the "License");
3  * you may not use this file except in compliance with the License.
4  * You may obtain a copy of the License at
5  *
6  *      http://www.apache.org/licenses/LICENSE-2.0
7  *
8  * Unless required by applicable law or agreed to in writing, software
9  * distributed under the License is distributed on an "AS IS" BASIS,
10  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11  * See the License for the specific language governing permissions and
12  * limitations under the License.
13  */

14 package net.logstash.logback.composite;
15
16 import java.io.IOException;
17 import java.io.OutputStream;
18 import java.io.Writer;
19 import java.lang.ref.SoftReference;
20 import java.util.ServiceConfigurationError;
21
22 import net.logstash.logback.decorate.JsonFactoryDecorator;
23 import net.logstash.logback.decorate.JsonGeneratorDecorator;
24 import net.logstash.logback.decorate.NullJsonFactoryDecorator;
25 import net.logstash.logback.decorate.NullJsonGeneratorDecorator;
26 import ch.qos.logback.access.spi.IAccessEvent;
27 import ch.qos.logback.classic.spi.ILoggingEvent;
28 import ch.qos.logback.core.spi.ContextAware;
29 import ch.qos.logback.core.spi.ContextAwareBase;
30 import ch.qos.logback.core.spi.DeferredProcessingAware;
31 import ch.qos.logback.core.spi.LifeCycle;
32
33 import com.fasterxml.jackson.core.JsonEncoding;
34 import com.fasterxml.jackson.core.JsonFactory;
35 import com.fasterxml.jackson.core.JsonGenerator;
36 import com.fasterxml.jackson.core.io.SegmentedStringWriter;
37 import com.fasterxml.jackson.core.util.BufferRecycler;
38 import com.fasterxml.jackson.core.util.ByteArrayBuilder;
39 import com.fasterxml.jackson.databind.ObjectMapper;
40 import com.fasterxml.jackson.databind.SerializationFeature;
41
42 /**
43  * Formats logstash Events as JSON using {@link JsonProvider}s.
44  * <p>
45  * 
46  * The {@link CompositeJsonFormatter} starts the JSON object ('{'),
47  * then delegates writing the contents of the object to the {@link JsonProvider}s,
48  * and then ends the JSON object ('}').
49  *
50  * @param <Event> type of event ({@link ILoggingEvent} or {@link IAccessEvent}).
51  */

52 public abstract class CompositeJsonFormatter<Event extends DeferredProcessingAware>
53         extends ContextAwareBase implements LifeCycle {
54
55     /**
56      * This <code>ThreadLocal</code> contains a {@link java.lang.ref.SoftReference}
57      * to a {@link BufferRecycler} used to provide a low-cost
58      * buffer recycling between writer instances.
59      */

60     private final ThreadLocal<SoftReference<BufferRecycler>> recycler = new ThreadLocal<SoftReference<BufferRecycler>>() {
61         protected SoftReference<BufferRecycler> initialValue() {
62             final BufferRecycler bufferRecycler = new BufferRecycler();
63             return new SoftReference<BufferRecycler>(bufferRecycler);
64         }
65     };
66     
67     /**
68      * Used to create the necessary {@link JsonGenerator}s for generating JSON.
69      */

70     private JsonFactory jsonFactory;
71
72     /**
73      * Decorates the {@link #jsonFactory}.
74      * Allows customization of the {@link #jsonFactory}. 
75      */

76     private JsonFactoryDecorator jsonFactoryDecorator = new NullJsonFactoryDecorator();
77     
78     /**
79      * Decorates the generators generated by the {@link #jsonFactory}. 
80      * Allows customization of the generators. 
81      */

82     private JsonGeneratorDecorator jsonGeneratorDecorator = new NullJsonGeneratorDecorator();
83     
84     /**
85      * The providers that are used to populate the output JSON object.
86      */

87     private JsonProviders<Event> jsonProviders = new JsonProviders<Event>();
88     
89     private JsonEncoding encoding = JsonEncoding.UTF8;
90
91     private boolean findAndRegisterJacksonModules = true;
92     
93     private volatile boolean started;
94     
95     public CompositeJsonFormatter(ContextAware declaredOrigin) {
96         super(declaredOrigin);
97     }
98
99     @Override
100     public void start() {
101         if (jsonProviders.getProviders().isEmpty()) {
102             addError("No providers configured");
103         }
104         jsonFactory = createJsonFactory();
105         jsonProviders.setJsonFactory(jsonFactory);
106         jsonProviders.setContext(context);
107         jsonProviders.start();
108         started = true;
109     }
110     
111     @Override
112     public void stop() {
113         jsonProviders.stop();
114         started = false;
115     }
116     
117     @Override
118     public boolean isStarted() {
119         return started;
120     }
121
122     private JsonFactory createJsonFactory() {
123         ObjectMapper objectMapper = new ObjectMapper()
124                 /*
125                  * Assume empty beans are ok.
126                  */

127                 .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
128
129         if (findAndRegisterJacksonModules) {
130             try {
131                 objectMapper.findAndRegisterModules();
132             } catch (ServiceConfigurationError serviceConfigurationError) {
133                 addError("Error occurred while dynamically loading jackson modules", serviceConfigurationError);
134             }
135         }
136
137         JsonFactory jsonFactory = objectMapper
138                 .getFactory()
139                 /*
140                  * When generators are flushed, don't flush the underlying outputStream.
141                  *
142                  * This allows some streaming optimizations when using an encoder.
143                  *
144                  * The encoder generally determines when the stream should be flushed
145                  * by an 'immediateFlush' property.
146                  *
147                  * The 'immediateFlush' property of the encoder can be set to false
148                  * when the appender performs the flushes at appropriate times
149                  * (such as the end of a batch in the AbstractLogstashTcpSocketAppender).
150                  */

151                 .disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM);
152
153         return this.jsonFactoryDecorator.decorate(jsonFactory);
154     }
155
156     public byte[] writeEventAsBytes(Event event) throws IOException {
157         ByteArrayBuilder outputStream = new ByteArrayBuilder(getBufferRecycler());
158
159         try {
160             writeEventToOutputStream(event, outputStream);
161             outputStream.flush();
162             return outputStream.toByteArray();
163         } finally {
164             outputStream.release();
165         }
166     }
167
168     public void writeEventToOutputStream(Event event, OutputStream outputStream) throws IOException {
169         try (JsonGenerator generator = createGenerator(outputStream)) {
170             writeEventToGenerator(generator, event);
171         }
172         /*
173          * Do not flush the outputStream.
174          * 
175          * Allow something higher in the stack (e.g. the encoder/appender)
176          * to determine appropriate times to flush.
177          */

178     }
179
180     public String writeEventAsString(Event event) throws IOException {
181         SegmentedStringWriter writer = new SegmentedStringWriter(getBufferRecycler());
182
183         try (JsonGenerator generator = createGenerator(writer)) {
184             writeEventToGenerator(generator, event);
185             writer.flush();
186             return writer.getAndClear();
187         }
188     }
189
190     protected void writeEventToGenerator(JsonGenerator generator, Event event) throws IOException {
191         if (!isStarted()) {
192             throw new IllegalStateException("Encoding attempted before starting.");
193         }
194         generator.writeStartObject();
195         jsonProviders.writeTo(generator, event);
196         generator.writeEndObject();
197         generator.flush();
198     }
199     
200     protected void prepareForDeferredProcessing(Event event) {
201         event.prepareForDeferredProcessing();
202         jsonProviders.prepareForDeferredProcessing(event);
203     }
204
205     private JsonGenerator createGenerator(OutputStream outputStream) throws IOException {
206         return this.jsonGeneratorDecorator.decorate(jsonFactory.createGenerator(outputStream, encoding));
207     }
208
209     private JsonGenerator createGenerator(Writer writer) throws IOException {
210         return this.jsonGeneratorDecorator.decorate(jsonFactory.createGenerator(writer));
211     }
212
213     private BufferRecycler getBufferRecycler() {
214         SoftReference<BufferRecycler> bufferRecyclerReference = recycler.get();
215         BufferRecycler bufferRecycler = bufferRecyclerReference.get();
216         if (bufferRecycler == null) {
217             recycler.remove();
218             return getBufferRecycler();
219         }
220         return bufferRecycler;
221     }
222     
223     public JsonFactory getJsonFactory() {
224         return jsonFactory;
225     }
226
227     public JsonFactoryDecorator getJsonFactoryDecorator() {
228         return jsonFactoryDecorator;
229     }
230
231     public void setJsonFactoryDecorator(JsonFactoryDecorator jsonFactoryDecorator) {
232         this.jsonFactoryDecorator = jsonFactoryDecorator;
233     }
234
235     public JsonGeneratorDecorator getJsonGeneratorDecorator() {
236         return jsonGeneratorDecorator;
237     }
238
239     public void setJsonGeneratorDecorator(JsonGeneratorDecorator jsonGeneratorDecorator) {
240         this.jsonGeneratorDecorator = jsonGeneratorDecorator;
241     }
242     
243     public JsonProviders<Event> getProviders() {
244         return jsonProviders;
245     }
246     
247     public String getEncoding() {
248         return encoding.getJavaName();
249     }
250     
251     public void setEncoding(String encodingName) {
252         for (JsonEncoding encoding: JsonEncoding.values()) {
253             if (encoding.getJavaName().equals(encodingName) || encoding.name().equals(encodingName)) {
254                 this.encoding = encoding;
255                 return;
256             }
257         }
258         throw new IllegalArgumentException("Unknown encoding " + encodingName);
259     }
260     
261     public void setProviders(JsonProviders<Event> jsonProviders) {
262         this.jsonProviders = jsonProviders;
263     }
264
265     public boolean isFindAndRegisterJacksonModules() {
266         return findAndRegisterJacksonModules;
267     }
268
269     public void setFindAndRegisterJacksonModules(boolean findAndRegisterJacksonModules) {
270         this.findAndRegisterJacksonModules = findAndRegisterJacksonModules;
271     }
272 }
273