2727
2828import java .io .IOException ;
2929import java .util .ArrayList ;
30+ import java .util .Collections ;
31+ import java .util .HashMap ;
32+ import java .util .LinkedHashSet ;
3033import java .util .List ;
34+ import java .util .Map ;
35+ import java .util .Set ;
3136import java .util .UUID ;
3237import java .util .stream .Collectors ;
3338
34-
35- /*
36- * A HTTP service for log ingestion to be executed by BlockingTaskExecutor.
37- */
3839@ Blocking
3940public class LogHTTPService {
4041 private static final int SERIALIZATION_OVERHEAD = 1024 ;
@@ -45,6 +46,21 @@ public class LogHTTPService {
4546 public static final String PAYLOAD_SIZE = "payloadSize" ;
4647 public static final String REQUEST_PROCESS_DURATION = "requestProcessDuration" ;
4748
49+ static final Set <String > SENSITIVE_HEADERS = Set .of (
50+ "authorization" ,
51+ "proxy-authorization" ,
52+ "cookie" ,
53+ "set-cookie" ,
54+ "www-authenticate" ,
55+ "proxy-authenticate" ,
56+ "x-api-key" ,
57+ "x-csrf-token" ,
58+ "x-xsrf-token" ,
59+ "x-auth-token" ,
60+ "x-amz-security-token" ,
61+ "x-amz-credential"
62+ );
63+
4864 private static final Logger LOG = LoggerFactory .getLogger (LogHTTPService .class );
4965
5066 // TODO: support other data-types as request body, e.g. json_lines, msgpack
@@ -60,16 +76,19 @@ public class LogHTTPService {
6076 private final Timer requestProcessDuration ;
6177 private Integer bufferMaxRequestLength ;
6278 private Integer bufferOptimalRequestLength ;
79+ private final List <String > metadataHeaders ;
6380
6481 public LogHTTPService (final int bufferWriteTimeoutInMillis ,
6582 final Buffer <Record <Log >> buffer ,
6683 final PluginMetrics pluginMetrics ,
67- final InputCodec codec ) {
84+ final InputCodec codec ,
85+ final List <String > metadataHeaders ) {
6886 this .buffer = buffer ;
6987 this .bufferWriteTimeoutInMillis = bufferWriteTimeoutInMillis ;
7088 this .bufferMaxRequestLength = buffer .getMaxRequestSize ().isPresent () ? buffer .getMaxRequestSize ().get (): null ;
7189 this .bufferOptimalRequestLength = buffer .getOptimalRequestSize ().isPresent () ? buffer .getOptimalRequestSize ().get (): null ;
7290 this .codec = codec ;
91+ this .metadataHeaders = metadataHeaders ;
7392 requestsReceivedCounter = pluginMetrics .counter (REQUESTS_RECEIVED );
7493 successRequestsCounter = pluginMetrics .counter (SUCCESS_REQUESTS );
7594 requestsOverOptimalSizeCounter = pluginMetrics .counter (REQUESTS_OVER_OPTIMAL_SIZE );
@@ -78,6 +97,13 @@ public LogHTTPService(final int bufferWriteTimeoutInMillis,
7897 requestProcessDuration = pluginMetrics .timer (REQUEST_PROCESS_DURATION );
7998 }
8099
100+ public LogHTTPService (final int bufferWriteTimeoutInMillis ,
101+ final Buffer <Record <Log >> buffer ,
102+ final PluginMetrics pluginMetrics ,
103+ final InputCodec codec ) {
104+ this (bufferWriteTimeoutInMillis , buffer , pluginMetrics , codec , null );
105+ }
106+
81107 @ Post
82108 public HttpResponse doPost (final ServiceRequestContext serviceRequestContext , final AggregatedHttpRequest aggregatedHttpRequest ) throws Exception {
83109 requestsReceivedCounter .increment ();
@@ -92,6 +118,7 @@ public HttpResponse doPost(final ServiceRequestContext serviceRequestContext, fi
92118
93119 HttpResponse processRequest (final AggregatedHttpRequest aggregatedHttpRequest ) throws Exception {
94120 final HttpData content = aggregatedHttpRequest .content ();
121+ final Map <String , Object > extractedHeaders = extractHeaders (aggregatedHttpRequest );
95122
96123 if (buffer .isByteBuffer ()) {
97124 if (bufferMaxRequestLength != null && bufferOptimalRequestLength != null && content .array ().length > bufferOptimalRequestLength ) {
@@ -124,6 +151,13 @@ HttpResponse processRequest(final AggregatedHttpRequest aggregatedHttpRequest) t
124151 LOG .error ("Failed to parse the request of size {} using specified input codec {} due to: {}" , content .length (), codec .getClass (), e .getMessage ());
125152 throw new IOException ("Bad request data format. " , e .getCause ());
126153 }
154+ if (!extractedHeaders .isEmpty ()) {
155+ for (Record <Log > record : records ) {
156+ for (Map .Entry <String , Object > entry : extractedHeaders .entrySet ()) {
157+ record .getData ().getMetadata ().setAttribute (entry .getKey (), entry .getValue ());
158+ }
159+ }
160+ }
127161 } else {
128162
129163 try {
@@ -135,7 +169,7 @@ HttpResponse processRequest(final AggregatedHttpRequest aggregatedHttpRequest) t
135169
136170 records .addAll (
137171 jsonList .stream ()
138- .map (this :: buildRecordLog )
172+ .map (json -> buildRecordLog ( json , extractedHeaders ) )
139173 .collect (Collectors .toList ())
140174 );
141175 }
@@ -152,6 +186,35 @@ HttpResponse processRequest(final AggregatedHttpRequest aggregatedHttpRequest) t
152186 return HttpResponse .of (HttpStatus .OK );
153187 }
154188
189+ private Map <String , Object > extractHeaders (final AggregatedHttpRequest aggregatedHttpRequest ) {
190+ if (metadataHeaders == null || metadataHeaders .isEmpty ()) {
191+ return Collections .emptyMap ();
192+ }
193+
194+ final boolean includeAll = metadataHeaders .size () == 1 && "*" .equals (metadataHeaders .get (0 ));
195+ final Set <String > headerNames = includeAll
196+ ? aggregatedHttpRequest .headers ().names ().stream ().map (Object ::toString ).collect (Collectors .toSet ())
197+ : metadataHeaders .stream ().map (String ::toLowerCase ).collect (Collectors .toCollection (LinkedHashSet ::new ));
198+
199+ final Map <String , Object > headers = new HashMap <>();
200+ for (String headerName : headerNames ) {
201+ if (isSensitiveHeader (headerName )) {
202+ LOG .warn ("Skipping sensitive header '{}' from metadata_headers configuration" , headerName );
203+ continue ;
204+ }
205+ List <String > values = aggregatedHttpRequest .headers ().getAll (headerName );
206+ if (!values .isEmpty ()) {
207+ headers .put (headerName , values .size () == 1 ? values .get (0 ) : new ArrayList <>(values ));
208+ }
209+ }
210+
211+ return headers ;
212+ }
213+
214+ static boolean isSensitiveHeader (final String headerName ) {
215+ return SENSITIVE_HEADERS .contains (headerName .toLowerCase ());
216+ }
217+
155218 private void writeChunkedBody (final String chunk ) {
156219 final byte [] chunkBytes = chunk .getBytes ();
157220
@@ -171,13 +234,13 @@ private void writeChunkedBody(final String chunk) {
171234 }
172235 }
173236
174- private Record <Log > buildRecordLog (String json ) {
175-
176- final JacksonLog log = JacksonLog .builder ()
237+ private Record <Log > buildRecordLog (final String json , final Map <String , Object > headerAttributes ) {
238+ final JacksonLog .Builder builder = JacksonLog .builder ()
177239 .withData (json )
178- .getThis ()
179- .build ();
180-
181- return new Record <>(log );
240+ .getThis ();
241+ if (!headerAttributes .isEmpty ()) {
242+ builder .withEventMetadataAttributes (headerAttributes );
243+ }
244+ return new Record <>(builder .build ());
182245 }
183246}
0 commit comments