Skip to content
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
13 changes: 13 additions & 0 deletions cf-java-logging-support-core/beats/app-logs/docs/fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,19 @@ required: False
A unique identifier that can be used to correlate multiple messages to a request.


==== w3c_traceparent

type: string

example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

required: False

The content of the W3C traceparent header as defined in
https://www.w3.org/TR/trace-context/#traceparent-header.
The traceparent allows correlation of logs to the request.


==== sap_passport

type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@
"index": "not_analyzed",
"type": "string"
},
"w3c_traceparent": {
"doc_values": true,
"index": "not_analyzed",
"type": "string"
},
"written_at": {
"doc_values": true,
"ignore_malformed": true,
Expand Down
9 changes: 9 additions & 0 deletions cf-java-logging-support-core/beats/app-logs/etc/fields.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ ctx:
description: |
A unique identifier that can be used to correlate multiple messages to a request.

- name: "w3c_traceparent"
type: string
required: false
example: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
description: |
The content of the W3C traceparent header as defined in
https://www.w3.org/TR/trace-context/#traceparent-header.
The traceparent allows correlation of logs to the request.

- name: "sap_passport"
type: string
required: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,19 @@ required: False
A unique identifier that can be used to correlate multiple messages to a request.


==== w3c_traceparent

type: string

example: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01

required: False

The content of the W3C traceparent header as defined in
https://www.w3.org/TR/trace-context/#traceparent-header.
The traceparent allows correlation of logs to the request.


==== sap_passport

type: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ ctx:
description: |
A unique identifier that can be used to correlate multiple messages to a request.

- name: "w3c_traceparent"
type: string
required: false
example: "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
description: |
The content of the W3C traceparent header as defined in
https://www.w3.org/TR/trace-context/#traceparent-header.
The traceparent allows correlation of logs to the request.

- name: "sap_passport"
type: string
required: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@
"index": "not_analyzed",
"type": "string"
},
"w3c_traceparent": {
"doc_values": true,
"index": "not_analyzed",
"type": "string"
},
"written_at": {
"doc_values": true,
"ignore_malformed": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public interface Fields {
public String WRITTEN_TS = "written_ts";
public String CORRELATION_ID = "correlation_id";
public String REQUEST_ID = "request_id";
public String W3C_TRACEPARENT = "w3c_traceparent";
public String SAP_PASSPORT = "sap_passport";
public String SAP_PASSPORT_ACTION = "sap_passport_Action";
public String SAP_PASSPORT_ACTIONTYPE = "sap_passport_ActionType";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public enum HttpHeaders implements HttpHeader {
X_VCAP_REQUEST_ID("x-vcap-request-id", Fields.REQUEST_ID, true), //
CORRELATION_ID("X-CorrelationID", Fields.CORRELATION_ID, true,
X_VCAP_REQUEST_ID), //
W3C_TRACEPARENT("traceparent", Fields.W3C_TRACEPARENT, true),
SAP_PASSPORT("sap-passport", Fields.SAP_PASSPORT, true), //
TENANT_ID("tenantid", Fields.TENANT_ID, true); //

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void resetLogContext() {

@Test
public void hasCorrectNumberOfTypes() throws Exception {
assertThat(HttpHeaders.values().length, is(equalTo(19)));
assertThat(HttpHeaders.values().length, is(equalTo(20)));
}

@Test
Expand All @@ -34,6 +34,7 @@ public void hasCorrectNames() throws Exception {
assertThat(HttpHeaders.CORRELATION_ID.getName(), is("X-CorrelationID"));
assertThat(HttpHeaders.REFERER.getName(), is("referer"));
assertThat(HttpHeaders.TENANT_ID.getName(), is("tenantid"));
assertThat(HttpHeaders.W3C_TRACEPARENT.getName(), is("traceparent"));
assertThat(HttpHeaders.X_CUSTOM_HOST.getName(), is("x-custom-host"));
assertThat(HttpHeaders.X_FORWARDED_FOR.getName(), is("x-forwarded-for"));
assertThat(HttpHeaders.X_FORWARDED_HOST.getName(), is("x-forwarded-host"));
Expand All @@ -56,6 +57,7 @@ public void hasCorrectFields() throws Exception {
assertThat(HttpHeaders.CORRELATION_ID.getField(), is(Fields.CORRELATION_ID));
assertThat(HttpHeaders.REFERER.getField(), is(nullValue()));
assertThat(HttpHeaders.TENANT_ID.getField(), is(Fields.TENANT_ID));
assertThat(HttpHeaders.W3C_TRACEPARENT.getField(), is(Fields.W3C_TRACEPARENT));
assertThat(HttpHeaders.X_CUSTOM_HOST.getField(), is(Fields.X_CUSTOM_HOST));
assertThat(HttpHeaders.X_FORWARDED_FOR.getField(), is(Fields.X_FORWARDED_FOR));
assertThat(HttpHeaders.X_FORWARDED_HOST.getField(), is(Fields.X_FORWARDED_HOST));
Expand Down Expand Up @@ -93,6 +95,7 @@ public void hasCorrectAliases() throws Exception {
assertThat(HttpHeaders.CORRELATION_ID.getAliases(), containsInAnyOrder(HttpHeaders.X_VCAP_REQUEST_ID));
assertThat(HttpHeaders.REFERER.getAliases(), is(empty()));
assertThat(HttpHeaders.TENANT_ID.getAliases(), is(empty()));
assertThat(HttpHeaders.W3C_TRACEPARENT.getAliases(), is(empty()));
assertThat(HttpHeaders.X_CUSTOM_HOST.getAliases(), is(empty()));
assertThat(HttpHeaders.X_FORWARDED_FOR.getAliases(), is(empty()));
assertThat(HttpHeaders.X_FORWARDED_HOST.getAliases(), is(empty()));
Expand All @@ -111,7 +114,8 @@ public void hasCorrectAliases() throws Exception {
@Test
public void propagatesCorrectHeaders() throws Exception {
assertThat(HttpHeaders.propagated(), containsInAnyOrder(HttpHeaders.CORRELATION_ID, HttpHeaders.SAP_PASSPORT,
HttpHeaders.TENANT_ID, HttpHeaders.X_VCAP_REQUEST_ID));
HttpHeaders.TENANT_ID, HttpHeaders.W3C_TRACEPARENT,
HttpHeaders.X_VCAP_REQUEST_ID));
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.sap.hcp.cf.logging.servlet.filter;

import static com.sap.hcp.cf.logging.common.customfields.CustomField.customField;
import static com.sap.hcp.cf.logging.common.request.HttpHeaders.W3C_TRACEPARENT;
import static java.util.Optional.ofNullable;

import java.util.UUID;
import java.util.function.Predicate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand All @@ -25,13 +28,19 @@ public class CorrelationIdFilter extends AbstractLoggingFilter {

private static final Logger LOG = LoggerFactory.getLogger(CorrelationIdFilter.class);
private HttpHeader correlationHeader;
private HttpHeader traceparentHeader;

public CorrelationIdFilter() {
this(HttpHeaders.CORRELATION_ID);
}

public CorrelationIdFilter(HttpHeader correlationHeader) {
this(correlationHeader, W3C_TRACEPARENT);
}

public CorrelationIdFilter(HttpHeader correlationHeader, HttpHeader traceparentHeader) {
this.correlationHeader = correlationHeader;
this.traceparentHeader = traceparentHeader;
}

@Override
Expand All @@ -43,7 +52,10 @@ protected void beforeFilter(HttpServletRequest request, HttpServletResponse resp

private String determineCorrelationId(HttpServletRequest request) {
String correlationId = HttpHeaderUtilities.getHeaderValue(request, correlationHeader);
if (correlationId == null || correlationId.isEmpty() || correlationId.equals(Defaults.UNKNOWN)) {
if (isBlankOrDefault(correlationId)) {
correlationId = getCorrelationIdFromTraceparent(request);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this works, because correlationId is nullable, so the parsing can fail, and still work as expected.

}
if (isBlankOrDefault(correlationId)) {
correlationId = String.valueOf(UUID.randomUUID());
// add correlation-id as custom field, since it is added to MDC only
// in the next step
Expand All @@ -53,6 +65,25 @@ private String determineCorrelationId(HttpServletRequest request) {
return correlationId;
}

private boolean isBlankOrDefault(String value) {
return value == null || value.isEmpty() || value.equals(Defaults.UNKNOWN);
}

private String getCorrelationIdFromTraceparent(HttpServletRequest request) {
String traceparent = HttpHeaderUtilities.getHeaderValue(request, traceparentHeader);
return ofNullable(traceparent).filter(not(this::isBlankOrDefault)).map(this::parseTraceparent).orElse(
null);
}

private <T> Predicate<T> not(Predicate<T> p) {
return p.negate();
}

private String parseTraceparent(String value) {
String[] tokens = value.split("-");
return tokens.length >= 2 ? tokens[1] : null;
}

private void addCorrelationIdHeader(HttpServletResponse response, String correlationId) {
if (!response.isCommitted() && response.getHeader(correlationHeader.getName()) == null) {
response.setHeader(correlationHeader.getName(), correlationId);
Expand All @@ -63,5 +94,4 @@ private void addCorrelationIdHeader(HttpServletResponse response, String correla
protected void cleanup(HttpServletRequest request, HttpServletResponse response) {
LogContext.remove(correlationHeader.getField());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
@RunWith(MockitoJUnitRunner.class)
public class CorrelationIdFilterTest {

private static String KNOWN_CORRELATION_ID = UUID.randomUUID().toString();
private static final String KNOWN_CORRELATION_ID = UUID.randomUUID().toString();
private static final String KNOWN_TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736";
private static final String KNOWN_TRACEPARENT = "00-" + KNOWN_TRACE_ID + "-00f067aa0ba902b7-01";

@Mock
private HttpServletRequest request;
Expand Down Expand Up @@ -108,13 +110,40 @@ public void doesNotOverwriteCorrelationIdInResponse() throws Exception {
}

@Test
public void usesCustomHeader() throws Exception {
HttpHeader myHeader = new HttpTestHeader("my-header", "my-field", null, false);
when(request.getHeader("my-header")).thenReturn(KNOWN_CORRELATION_ID);
public void usesCustomCorrelationIdHeader() throws Exception {
HttpHeader myCorrelationIdHeader = new HttpTestHeader("my-correlationId-header", "my-correlationId-field", null,
false);
HttpHeader myTraceparentHeader = new HttpTestHeader("my-traceparent-header", "my-traceparent-field", null,
false);
when(request.getHeader("my-correlationId-header")).thenReturn(KNOWN_CORRELATION_ID);
when(request.getHeader("my-traceparent-header")).thenReturn(KNOWN_TRACEPARENT);

new CorrelationIdFilter(myCorrelationIdHeader, myTraceparentHeader).doFilter(request, response, chain);

assertThat(mdcExtractor.getField("my-correlationId-field"), is(equalTo(KNOWN_CORRELATION_ID)));
verify(response).setHeader("my-correlationId-header", KNOWN_CORRELATION_ID);
}

@Test
public void usesCustomTraceparentHeader() throws Exception {
HttpHeader myCorrelationIdHeader = new HttpTestHeader("my-correlationId-header", "my-correlationId-field", null,
false);
HttpHeader myTraceparentHeader = new HttpTestHeader("my-traceparent-header", "my-traceparent-field", null,
false);
when(request.getHeader("my-traceparent-header")).thenReturn(KNOWN_TRACEPARENT);

new CorrelationIdFilter(myHeader).doFilter(request, response, chain);
new CorrelationIdFilter(myCorrelationIdHeader, myTraceparentHeader).doFilter(request, response, chain);

assertThat(mdcExtractor.getField("my-correlationId-field"), is(equalTo(KNOWN_TRACE_ID)));
verify(response).setHeader("my-correlationId-header", KNOWN_TRACE_ID);
}

@Test
public void usesTraceparentIfCorrelationIdHeaderNotPresent() throws Exception {
when(request.getHeader(HttpHeaders.W3C_TRACEPARENT.getName())).thenReturn(KNOWN_TRACEPARENT);

new CorrelationIdFilter().doFilter(request, response, chain);

assertThat(mdcExtractor.getField("my-field"), is(equalTo(KNOWN_CORRELATION_ID)));
verify(response).setHeader("my-header", KNOWN_CORRELATION_ID);
assertThat(getExtractedCorrelationId(), is(equalTo(KNOWN_TRACE_ID)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,19 @@ public void logsSapPassportFromRequestHeader() throws Exception {
}
}

@Test
public void logsW3cTraceparentFromRequestHeader() throws Exception {
String traceparent = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
HttpGet get = createRequestWithHeader(HttpHeaders.W3C_TRACEPARENT.getName(), traceparent);
try (CloseableHttpResponse response = client.execute(get)) {
assertThat("Application log without traceparent.", getRequestMessage(), hasEntry(Fields.W3C_TRACEPARENT,
traceparent));
assertThat("Request log without traceparent.", getRequestLog(), hasEntry(Fields.W3C_TRACEPARENT,
traceparent));
}

}

@Test
public void writesCorrelationIdFromHeadersAsResponseHeader() throws Exception {
String correlationId = UUID.randomUUID().toString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class RequestLoggingFilterTest {
private static final String REQUEST_ID = "1234-56-7890-xxx";
private static final String CORRELATION_ID = "xxx-56-7890-xxx";
private static final String TENANT_ID = "tenant1";
private static final String W3C_TRACEPARENT = "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01";
private static final String REQUEST = "/foobar";
private static final String QUERY_STRING = "baz=bla";
private static final String FULL_REQUEST = REQUEST + "?" + QUERY_STRING;
Expand Down Expand Up @@ -211,6 +212,14 @@ public void testExplicitCorrelationId() throws IOException, ServletException {
assertThat(getField(Fields.TENANT_ID), is(nullValue()));
}

@Test
public void testExplicitW3cTraceparent() throws IOException, ServletException {
mockGetHeader(HttpHeaders.W3C_TRACEPARENT, W3C_TRACEPARENT);
FilterChain mockFilterChain = mock(FilterChain.class);
new RequestLoggingFilter().doFilter(mockReq, mockResp, mockFilterChain);
assertThat(getField(Fields.W3C_TRACEPARENT), is(W3C_TRACEPARENT));
}

@Test
public void testExplicitTenantId() throws IOException, ServletException {
mockGetHeader(HttpHeaders.TENANT_ID, TENANT_ID);
Expand Down