Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix non-JSON generators (regression in 7.0) #930

Merged
merged 1 commit into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ public abstract class AbstractCompositeJsonFormatter<Event extends DeferredProce
private volatile boolean started;

private ThreadLocalHolder<JsonFormatter> threadLocalJsonFormatter;


public AbstractCompositeJsonFormatter(ContextAware declaredOrigin) {
super(declaredOrigin);
}
Expand Down Expand Up @@ -254,7 +254,25 @@ private JsonFactory createJsonFactory() {
}

private JsonFactory decorateFactory(JsonFactory factory) {
return this.jsonFactoryDecorator.decorate(factory)
JsonFactory factoryToDecorate = factory
/*
* When generators are flushed, don't flush the underlying outputStream.
*
* This allows some streaming optimizations when using an encoder.
*
* The encoder generally determines when the stream should be flushed
* by an 'immediateFlush' property.
*
* The 'immediateFlush' property of the encoder can be set to false
* when the appender performs the flushes at appropriate times
* (such as the end of a batch in the AbstractLogstashTcpSocketAppender).
*
* Set this prior to decorating, because some generators require
* FLUSH_PASSED_TO_STREAM to work properly (e.g. YAML)
*/
.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM);

return this.jsonFactoryDecorator.decorate(factoryToDecorate)
/*
* Jackson buffer recycling works by maintaining a pool of buffers per thread. This
* feature works best when one JsonGenerator is created per thread, typically in J2EE
Expand Down Expand Up @@ -284,32 +302,28 @@ private JsonGenerator createGenerator(OutputStream outputStream) throws IOExcept
}

private JsonGenerator decorateGenerator(JsonGenerator generator) {
return new SimpleObjectJsonGeneratorDelegate(jsonGeneratorDecorator.decorate(generator)
/*
* When generators are flushed, don't flush the underlying outputStream.
*
* This allows some streaming optimizations when using an encoder.
*
* The encoder generally determines when the stream should be flushed
* by an 'immediateFlush' property.
*
* The 'immediateFlush' property of the encoder can be set to false
* when the appender performs the flushes at appropriate times
* (such as the end of a batch in the AbstractLogstashTcpSocketAppender).
*/
.disable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM)

/*
* Don't let the json generator close the underlying outputStream and let the
* encoder managed it.
*/
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET)

/*
* JsonGenerator are reused to serialize multiple log events.
* Change the default root value separator to an empty string instead of a single space.
*/
.setRootValueSeparator(new SerializedString(CoreConstants.EMPTY_STRING)));
JsonGenerator decorated = jsonGeneratorDecorator.decorate(generator)
/*
* Don't let the json generator close the underlying outputStream and let the
* encoder managed it.
*/
.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET);

try {
decorated = decorated
/*
* JsonGenerator are reused to serialize multiple log events.
* Change the default root value separator to an empty string instead of a single space.
*/
.setRootValueSeparator(new SerializedString(CoreConstants.EMPTY_STRING));
} catch (UnsupportedOperationException e) {
/*
* Ignore.
* Some generators do not support setting the rootValueSeparator.
*/
}

return new SimpleObjectJsonGeneratorDelegate(decorated);
}

public JsonFactory getJsonFactory() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import net.logstash.logback.decorate.JsonFactoryDecorator;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
Expand All @@ -35,6 +36,12 @@ public JsonFactory decorate(JsonFactory factory) {
YAMLFactory yamlFactory = new YAMLFactory();
ObjectMapper mapper = new ObjectMapper(yamlFactory);
yamlFactory.setCodec(mapper);
/*
* YAMLGenerator needs to pass the flush to the stream.
* It doesn't maintain an internal buffer like the other generators.
* To see this, look at the .flush() implementations of each of the generator classes.
*/
yamlFactory.enable(JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM);
return yamlFactory;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.logstash.logback.decorate.cbor;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import net.logstash.logback.composite.loggingevent.LoggingEventCompositeJsonFormatter;
import net.logstash.logback.composite.loggingevent.LoggingEventJsonProviders;
import net.logstash.logback.composite.loggingevent.MessageJsonProvider;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.spi.ContextAware;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class CborJsonFactoryDecoratorTest {

@Mock
private ContextAware contextAware;

@Mock
private ILoggingEvent event;

@Test
void test() throws IOException {
CborJsonFactoryDecorator decorator = new CborJsonFactoryDecorator();

LoggingEventJsonProviders providers = new LoggingEventJsonProviders();
providers.addMessage(new MessageJsonProvider());

LoggingEventCompositeJsonFormatter formatter = new LoggingEventCompositeJsonFormatter(contextAware);
formatter.setProviders(providers);
formatter.setJsonFactoryDecorator(decorator);
formatter.start();

when(event.getFormattedMessage()).thenReturn("a message");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
formatter.writeEvent(event, baos);

assertThat(baos.toByteArray()).asBase64Encoded().isEqualTo("v2dtZXNzYWdlaWEgbWVzc2FnZf8=");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.logstash.logback.decorate.smile;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

import net.logstash.logback.composite.loggingevent.LoggingEventCompositeJsonFormatter;
import net.logstash.logback.composite.loggingevent.LoggingEventJsonProviders;
import net.logstash.logback.composite.loggingevent.MessageJsonProvider;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.spi.ContextAware;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class SmileJsonFactoryDecoratorTest {

@Mock
private ContextAware contextAware;

@Mock
private ILoggingEvent event;

@Test
void test() throws IOException {
SmileJsonFactoryDecorator decorator = new SmileJsonFactoryDecorator();

LoggingEventJsonProviders providers = new LoggingEventJsonProviders();
providers.addMessage(new MessageJsonProvider());

LoggingEventCompositeJsonFormatter formatter = new LoggingEventCompositeJsonFormatter(contextAware);
formatter.setProviders(providers);
formatter.setJsonFactoryDecorator(decorator);
formatter.start();

when(event.getFormattedMessage()).thenReturn("a message");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
formatter.writeEvent(event, baos);

assertThat(baos.toByteArray()).asBase64Encoded().isEqualTo("OikKAfqGbWVzc2FnZUhhIG1lc3NhZ2X7");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2013-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.logstash.logback.decorate.yaml;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import net.logstash.logback.composite.loggingevent.LoggingEventCompositeJsonFormatter;
import net.logstash.logback.composite.loggingevent.LoggingEventJsonProviders;
import net.logstash.logback.composite.loggingevent.MessageJsonProvider;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.spi.ContextAware;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

@ExtendWith(MockitoExtension.class)
class YamlJsonFactoryDecoratorTest {

@Mock
private ContextAware contextAware;

@Mock
private ILoggingEvent event;

@Test
void test() throws IOException {
YamlJsonFactoryDecorator decorator = new YamlJsonFactoryDecorator();

LoggingEventJsonProviders providers = new LoggingEventJsonProviders();
providers.addMessage(new MessageJsonProvider());

LoggingEventCompositeJsonFormatter formatter = new LoggingEventCompositeJsonFormatter(contextAware);
formatter.setProviders(providers);
formatter.setJsonFactoryDecorator(decorator);
formatter.start();

when(event.getFormattedMessage()).thenReturn("a message");

ByteArrayOutputStream baos = new ByteArrayOutputStream();
formatter.writeEvent(event, baos);

assertThat(new String(baos.toByteArray(), StandardCharsets.UTF_8)).isEqualTo("---\nmessage: \"a message\"");
}

}