Skip to content

Commit

Permalink
Formatted AccessLog (#80)
Browse files Browse the repository at this point in the history
- Fixes Access Log not showing when error is thrown
- Support for pattern based access log
  • Loading branch information
kingster authored Aug 6, 2024
1 parent 8fa71d4 commit f4d4801
Show file tree
Hide file tree
Showing 19 changed files with 427 additions and 274 deletions.
4 changes: 3 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.java]
trim_trailing_whitespace = true


4 changes: 2 additions & 2 deletions .github/workflows/java.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ jobs:
matrix:
version: [8, 17, 21]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: set up JDK ${{ matrix.version }}
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
java-version: ${{ matrix.version }}
distribution: 'zulu'
Expand Down
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ dependencies {
implementation libraries.grpc_stub
implementation libraries.grpc_services
implementation libraries.lombok
implementation 'org.apache.commons:commons-text:1.12.0'


testImplementation libraries.junit4
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.flipkart.gjex.core.context;


import lombok.Builder;
import lombok.SneakyThrows;
import org.apache.commons.text.StringSubstitutor;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
* Represents the context for access logs, encapsulating details such as client IP, resource path,
* content length, response time, response status, and headers. This class provides functionality
* to format these details into a string using a template.
*/
@Builder
public class AccessLogContext {
String clientIp;
String resourcePath;
Integer contentLength;
Long responseTime;
Integer responseStatus;
@Builder.Default
Map<String,String> headers = new HashMap<>();


/**
* Retrieves a map of field names to their values for the current instance.
* This includes the fields of the class and the headers map, as well as the current thread name.
*
* @return A map containing field names and their corresponding values.
* @throws IllegalAccessException if the field is not accessible.
*/
@SneakyThrows
private Map<String,Object> getValueMap() {
Map<String, Object> params = new HashMap<>();
for (Field field : this.getClass().getDeclaredFields()) {
field.setAccessible(true);
params.put(field.getName(), field.get(this));
}
for (Map.Entry<String, String> entry : headers.entrySet()) {
params.put("headers." + entry.getKey(), entry.getValue());
}
params.put("thread", Thread.currentThread().getName());
return params;
}

/**
* Formats the access log context into a string based on the provided template.
* The template can include placeholders for the field names, which will be replaced
* with their corresponding values.
*
* @param templateFormat The template string containing placeholders for field names.
* @return The formatted string with placeholders replaced by field values.
*/
public String format(final String templateFormat) {
return StringSubstitutor.replace(templateFormat, getValueMap(), "{", "}");
}
}
54 changes: 33 additions & 21 deletions core/src/main/java/com/flipkart/gjex/core/filter/Filter.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,41 @@
*
* @author regu.b
*/
public abstract class Filter<Req, Res, M> {
public abstract class Filter<Req, Res, M> {

/** Lifecycle methods for cleaning up resources used by this Filter*/
public void destroy(){}
/**
* Lifecycle methods for cleaning up resources used by this Filter
*/
public void destroy() {}

/**
* Call-back to decorate or inspect the Request body/message. This Filter cannot fail processing of the Request body and hence there is no support for indicating failure.
* This method should be viewed almost like a proxy for the Request body.
* @param req the Request body/message
* @param requestParams prams
*/
public void doProcessRequest(Req req, RequestParams<M> requestParams){}
/**
* Call-back to decorate or inspect the Request body/message. This Filter cannot fail processing of the Request body and hence there is no support for indicating failure.
* This method should be viewed almost like a proxy for the Request body.
*
* @param req the Request body/message
* @param requestParams prams
*/
public void doProcessRequest(Req req, RequestParams<M> requestParams) {}

/**
* Call-back to decorate or inspect the Response headers. Implementations may use this method to set additional headers in the response.
* @param responseHeaders the Response Headers
*/
public void doProcessResponseHeaders(M responseHeaders) {}
/**
* Call-back to decorate or inspect the Response headers. Implementations may use this method to set additional headers in the response.
*
* @param responseHeaders the Response Headers
*/
public void doProcessResponseHeaders(M responseHeaders) {}

/**
* Call-back to decorate or inspect the Response body/message. This Filter cannot fail processing of the Response body and hence there is no support for indicating failure.
* This method should be viewed almost like a proxy for the Response body.
* @param response the Response body/message
*/
public void doProcessResponse(Res response) {}
/**
* Call-back to decorate or inspect the Response body/message. This Filter cannot fail processing of the Response body and hence there is no support for indicating failure.
* This method should be viewed almost like a proxy for the Response body.
*
* @param response the Response body/message
*/
public void doProcessResponse(Res response) {}

/**
* Call-back to handle exceptions that occur during the processing of the request or response.
*
* @param e The exception that occurred.
*/
public void doHandleException(Exception e) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@
*/
package com.flipkart.gjex.core.filter.grpc;

import com.flipkart.gjex.core.context.AccessLogContext;
import com.flipkart.gjex.core.filter.RequestParams;
import com.flipkart.gjex.core.logging.Logging;
import com.google.protobuf.GeneratedMessageV3;
import io.grpc.Metadata;
import lombok.Getter;
import lombok.Setter;
import io.grpc.Status;
import org.slf4j.Logger;

import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Implements a gRPC filter for logging access to gRPC services. This filter captures and logs
* essential details such as the start time of the request, the client IP, the resource path,
Expand All @@ -34,67 +38,106 @@
*
* @param <R> The request type extending {@link GeneratedMessageV3}, representing the gRPC request message.
* @param <S> The response type extending {@link GeneratedMessageV3}, representing the gRPC response message.
*
* @author ajay.jalgaonkar
*/
public class AccessLogGrpcFilter<R extends GeneratedMessageV3, S extends GeneratedMessageV3>
extends GrpcFilter<R,S> implements Logging {

// The start time of the request processing.
@Getter
@Setter
protected long startTime;

// Parameters of the request being processed, including client IP and resource path.
protected RequestParams<Metadata> requestParams;

// Logger instance for logging access log messages.
protected static Logger logger = Logging.loggerWithName("ACCESS-LOG");

/**
* Processes the incoming gRPC request by initializing the start time and storing the request parameters.
*
* @param req The incoming gRPC request message.
* @param requestParamsInput Parameters of the request, including client IP and any additional metadata.
*/
@Override
public void doProcessRequest(R req, RequestParams<Metadata> requestParamsInput) {
startTime = System.currentTimeMillis();
requestParams = requestParamsInput;
}

/**
* Placeholder method for processing response headers. Currently does not perform any operations.
*
* @param responseHeaders The metadata associated with the gRPC response.
*/
@Override
public void doProcessResponseHeaders(Metadata responseHeaders) {}

/**
* Processes the outgoing gRPC response by logging relevant request and response details.
* Logs the client IP, requested resource path, size of the response message, and the time taken to process the request.
*
* @param response The outgoing gRPC response message.
*/
@Override
public void doProcessResponse(S response) {
String size = "-";
if (response != null){
size = String.valueOf(response.getSerializedSize());
extends GrpcFilter<R, S> implements Logging {

// The start time of the request processing.
private long startTime;

// AccessLogContext of the request being processed
private AccessLogContext.AccessLogContextBuilder accessLogContextBuilder;

// The format string for the access log message.
private static String format;

// Logger instance for logging access log messages.
private static final Logger logger = Logging.loggerWithName("ACCESS-LOG");

public AccessLogGrpcFilter() {

}

/**
* Sets the format string for the access log message.
*
* @param format The format string to be used for logging.
*/
public static void setFormat(String format) {
AccessLogGrpcFilter.format = format;
}

/**
* Processes the incoming gRPC request by initializing the start time and storing the request parameters.
*
* @param req The incoming gRPC request message.
* @param requestParamsInput Parameters of the request, including client IP and any additional metadata.
*/
@Override
public void doProcessRequest(R req, RequestParams<Metadata> requestParamsInput) {
startTime = System.currentTimeMillis();
accessLogContextBuilder = AccessLogContext.builder()
.clientIp(requestParamsInput.getClientIp())
.resourcePath(requestParamsInput.getResourcePath());

Map<String, String> headers = requestParamsInput.getMetadata().keys().stream()
.collect(Collectors.toMap(key -> key, key ->
Optional.ofNullable(requestParamsInput.getMetadata().get(Metadata.Key.of(key, Metadata.ASCII_STRING_MARSHALLER))).orElse("")
));

accessLogContextBuilder.headers(headers);
}

/**
* Placeholder method for processing response headers. Currently does not perform any operations.
*
* @param responseHeaders The metadata associated with the gRPC response.
*/
@Override
public void doProcessResponseHeaders(Metadata responseHeaders) {
}

/**
* Processes the outgoing gRPC response by logging relevant request and response details.
* Logs the client IP, requested resource path, size of the response message, and the time taken to process the request.
*
* @param response The outgoing gRPC response message.
*/
@Override
public void doProcessResponse(S response) {
accessLogContextBuilder
.contentLength(response.getSerializedSize())
.responseTime(System.currentTimeMillis() - startTime)
.responseStatus(Status.Code.OK.value())
.build();
logger.info(accessLogContextBuilder.build().format(format));
}

/**
* Handles exceptions that occur during the processing of the request or response.
* Logs the exception details along with the request and response context.
*
* @param e The exception that occurred.
*/
@Override
public void doHandleException(Exception e) {
accessLogContextBuilder
.contentLength(0)
.responseTime(System.currentTimeMillis() - startTime)
.responseStatus(Status.Code.INTERNAL.value())
.build();
logger.info(accessLogContextBuilder.build().format(format));
}

/**
* Provides an instance of this filter. This method facilitates the creation of new instances of the
* AccessLogGrpcFilter for each gRPC call, ensuring thread safety and isolation of request data.
*
* @return A new instance of {@link AccessLogGrpcFilter}.
*/
@Override
public GrpcFilter<R, S> getInstance() {
return new AccessLogGrpcFilter<>();
}
logger.info("{} {} {} {}", requestParams.getClientIp(), requestParams.getResourcePath(),
size, System.currentTimeMillis()-startTime);
}

/**
* Provides an instance of this filter. This method facilitates the creation of new instances of the
* AccessLogGrpcFilter for each gRPC call, ensuring thread safety and isolation of request data.
*
* @return A new instance of {@link AccessLogGrpcFilter}.
*/
@Override
public GrpcFilter<R,S> getInstance(){
return new AccessLogGrpcFilter<>();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,23 @@
package com.flipkart.gjex.core.filter.grpc;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
* A gRPC Filter Config for processing filters
*
* @author ajay.jalgaonkar
*/

@Data
@NoArgsConstructor
@Getter
@Setter
public class GrpcFilterConfig {
@JsonProperty("enableAccessLogs")
private boolean enableAccessLogs = true;
@JsonProperty("enableAccessLogs")
private boolean enableAccessLogs = true;

@JsonProperty("accessLogFormat")
private String accessLogFormat = "{clientIp} {resourcePath} {contentLength} {responseStatus} {responseTime}";
}
Loading

0 comments on commit f4d4801

Please sign in to comment.