Skip to content

Commit

Permalink
Added chunking support
Browse files Browse the repository at this point in the history
Fixes #142
  • Loading branch information
Willi Schönborn committed Mar 20, 2017
1 parent 5b1585e commit 6aa5d33
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 13 deletions.
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ a JSON response body will **not** be escaped and represented as a string:

#### Writing

Writing defines where formatted requests and responses are written to. Logbook comes with two implementations: Logger and Stream.
Writing defines where formatted requests and responses are written to. Logbook comes with three implementations:
Logger, Stream and Chunking.

##### Logger

Expand All @@ -267,10 +268,18 @@ Logbook logbook = Logbook.builder()

An alternative implementation is to log requests and responses to a `PrintStream`, e.g. `System.out` or `System.err`. This is usually a bad choice for running in production, but can sometimes be useful for short-term local development and/or investigation.

```java
```

##### Chunking

The `ChunkingHttpLogWriter` will split long messages into smaller chunks and will write them individually while delegating to another writer:

```java
Logbook logbook = Logbook.builder()
.writer(new StreamHttpLogWriter(System.err))
.writer(new ChunkingHttpLogWriter(1000, new DefaultHttpLogWriter()))
.build();

```

### Servlet
Expand Down Expand Up @@ -381,6 +390,7 @@ The following tables show the available configuration:
| `logbook.obfuscate.parameters` | List of parameter names that need obfuscation | `[access_token]` |
| `logbook.write.category` | Changes the category of the [`DefaultHttpLogWriter`](#logger) | `org.zalando.logbook.Logbook` |
| `logbook.write.level` | Changes the level of the [`DefaultHttpLogWriter`](#logger) | `TRACE` |
| `logbook.write.chunk-size` | Splits log lines into smaller chunks. | `0` (disabled) |

##### Example configuration

Expand All @@ -401,6 +411,7 @@ logbook:
write:
category: http.wire-log
level: INFO
chunk-size: 1000
```
## Known Issues
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.zalando.logbook;

import org.zalando.logbook.DefaultLogbook.SimpleCorrelation;
import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation;

import java.io.IOException;
import java.util.regex.Pattern;

public final class ChunkingHttpLogWriter implements HttpLogWriter {

private final Pattern pattern;
private final HttpLogWriter writer;

public ChunkingHttpLogWriter(final int size, final HttpLogWriter writer) {
this.pattern = Pattern.compile("(?<=\\G.{" + size + "})");
this.writer = writer;
}

@Override
public boolean isActive(final RawHttpRequest request) throws IOException {
return writer.isActive(request);
}

@Override
public void writeRequest(final Precorrelation<String> precorrelation) throws IOException {
for (final String part : split(precorrelation.getRequest())) {
writer.writeRequest(new SimplePrecorrelation<>(precorrelation.getId(), part));
}
}

@Override
public void writeResponse(final Correlation<String, String> correlation) throws IOException {
for (final String part : split(correlation.getResponse())) {
writer.writeResponse(new SimpleCorrelation<>(correlation.getId(), correlation.getRequest(), part));
}
}

private String[] split(final String s) {
return pattern.split(s);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.zalando.logbook;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.runners.MockitoJUnitRunner;
import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation;

import java.io.IOException;
import java.util.List;

import static java.util.stream.Collectors.toList;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;

@RunWith(MockitoJUnitRunner.class)
public final class ChunkingHttpLogWriterTest {

private final HttpLogWriter delegate = mock(HttpLogWriter.class);
private final HttpLogWriter unit = new ChunkingHttpLogWriter(10, delegate);

@Captor
private ArgumentCaptor<Precorrelation<String>> requestCaptor;

@Captor
private ArgumentCaptor<Correlation<String, String>> responseCaptor;

@Test
public void shouldDelegateActive() throws IOException {
final RawHttpRequest request = mock(RawHttpRequest.class);
assertThat(unit.isActive(request), is(false));
}

@Test
public void shouldWriteSingleRequestIfLengthNotExceeded() throws IOException {
final List<String> precorrelation = captureRequest("Hello");
assertThat(precorrelation, contains("Hello"));
}

@Test
public void shouldWriteRequestInChunksIfLengthExceeded() throws IOException {
final List<String> precorrelation = captureRequest("Lorem ipsum dolor sit amet, consectetur adipiscing elit");
assertThat(precorrelation,
contains("Lorem ipsu", "m dolor si", "t amet, co", "nsectetur ", "adipiscing", " elit"));
}

private List<String> captureRequest(final String request) throws IOException {
unit.writeRequest(new SimplePrecorrelation<>("id", request));

verify(delegate, atLeastOnce()).writeRequest(requestCaptor.capture());

return requestCaptor.getAllValues().stream()
.map(Precorrelation::getRequest)
.collect(toList());
}

@Test
public void shouldWriteSingleResponseIfLengthNotExceeded() throws IOException {
final List<String> precorrelation = captureResponse("Hello");
assertThat(precorrelation, contains("Hello"));

}

@Test
public void shouldWriteResponseInChunksIfLengthExceeded() throws IOException {
final List<String> precorrelation = captureResponse("Lorem ipsum dolor sit amet, consectetur adipiscing elit");
assertThat(precorrelation,
contains("Lorem ipsu", "m dolor si", "t amet, co", "nsectetur ", "adipiscing", " elit"));
}

private List<String> captureResponse(final String response) throws IOException {
unit.writeResponse(new DefaultLogbook.SimpleCorrelation<>("id", "", response));

verify(delegate, atLeastOnce()).writeResponse(responseCaptor.capture());

return responseCaptor.getAllValues().stream()
.map(Correlation::getResponse)
.collect(toList());
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.core.Ordered;
import org.zalando.logbook.BodyFilter;
import org.zalando.logbook.BodyFilters;
import org.zalando.logbook.ChunkingHttpLogWriter;
import org.zalando.logbook.Conditions;
import org.zalando.logbook.DefaultHttpLogFormatter;
import org.zalando.logbook.DefaultHttpLogWriter;
Expand All @@ -42,7 +43,6 @@

import javax.servlet.Filter;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;

import static java.util.stream.Collectors.toList;
Expand Down Expand Up @@ -195,18 +195,18 @@ public HttpLogFormatter jsonFormatter(
@Bean
@ConditionalOnMissingBean(HttpLogWriter.class)
public HttpLogWriter writer(final Logger httpLogger) {
final Level level = properties.getWrite().getLevel();
final LogbookProperties.Write write = properties.getWrite();
final Level level = write.getLevel();
final int size = write.getChunkSize();

return level == null ?
new DefaultHttpLogWriter(httpLogger) :
new DefaultHttpLogWriter(httpLogger, level);
final HttpLogWriter writer = new DefaultHttpLogWriter(httpLogger, level);
return size > 0 ? new ChunkingHttpLogWriter(size, writer) : writer;
}

@Bean
@ConditionalOnMissingBean(name = "httpLogger")
public Logger httpLogger() {
final String category = properties.getWrite().getCategory();
return LoggerFactory.getLogger(Optional.ofNullable(category).orElseGet(Logbook.class::getName));
return LoggerFactory.getLogger(properties.getWrite().getCategory());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.zalando.logbook.DefaultHttpLogWriter.Level;
import org.zalando.logbook.Logbook;

import javax.annotation.Nullable;
import java.util.ArrayList;
Expand Down Expand Up @@ -43,10 +44,10 @@ public List<String> getParameters() {

public static class Write {

private String category;
private Level level;
private String category = Logbook.class.getName();
private Level level = Level.TRACE;
private int chunkSize = 0;

@Nullable
public String getCategory() {
return category;
}
Expand All @@ -55,7 +56,6 @@ public void setCategory(final String category) {
this.category = category;
}

@Nullable
public Level getLevel() {
return level;
}
Expand All @@ -64,6 +64,14 @@ public void setLevel(final Level level) {
this.level = level;
}

public int getChunkSize() {
return chunkSize;
}

public void setChunkSize(final int chunkSize) {
this.chunkSize = chunkSize;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.zalando.logbook.spring;

import org.junit.Test;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import org.zalando.logbook.ChunkingHttpLogWriter;
import org.zalando.logbook.HttpLogWriter;

import java.io.IOException;

import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature;
import static org.junit.Assert.assertThat;

@TestPropertySource(properties = "logbook.write.chunk-size = 100")
public final class WriteChunkingTest extends AbstractTest {

@Autowired
private HttpLogWriter writer;

@Test
public void shouldUseChunkingWriter() throws IOException {
assertThat(writer, is(instanceOf(ChunkingHttpLogWriter.class)));
}

}

0 comments on commit 6aa5d33

Please sign in to comment.