Skip to content

Commit

Permalink
fixes #1317 convert the audit handler to interceptor for logging requ…
Browse files Browse the repository at this point in the history
…est and response bodies (#1318)
  • Loading branch information
stevehu committed Aug 4, 2022
1 parent 5cb0ff9 commit 986c279
Show file tree
Hide file tree
Showing 13 changed files with 264 additions and 85 deletions.
30 changes: 22 additions & 8 deletions audit/src/main/java/com/networknt/audit/AuditConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,20 @@
class AuditConfig {
private static final Logger logger = LoggerFactory.getLogger(AuditConfig.class);

public static final String REQUEST_BODY = "requestBody";
public static final String RESPONSE_BODY = "responseBody";

private static final String HEADERS = "headers";
private static final String AUDIT = "audit";
private static final String STATUS_CODE = "statusCode";
private static final String RESPONSE_TIME = "responseTime";
private static final String AUDIT_ON_ERROR = "auditOnError";
private static final String IS_LOG_LEVEL_ERROR = "logLevelIsError";
private static final String IS_MASK_ENABLED = "mask";
private static final String LOG_LEVEL_IS_ERROR = "logLevelIsError";
private static final String MASK = "mask";
private static final String TIMESTAMP_FORMAT = "timestampFormat";

private static final String ENABLED = "enabled";

private Map<String, Object> mappedConfig;
public static final String CONFIG_NAME = "audit";
private List<String> headerList;
Expand All @@ -53,9 +59,11 @@ class AuditConfig {
private boolean statusCode;
private boolean responseTime;
private boolean auditOnError;
private boolean isMaskEnabled;
private boolean mask;
private String timestampFormat;

private boolean enabled;

private AuditConfig() {
this(CONFIG_NAME);
}
Expand Down Expand Up @@ -101,10 +109,12 @@ public boolean isAuditOnError() {
return auditOnError;
}

public boolean isMaskEnabled() {
return isMaskEnabled;
public boolean isMask() {
return mask;
}

public boolean isEnabled() { return enabled; }

public boolean isResponseTime() {
return responseTime;
}
Expand Down Expand Up @@ -134,7 +144,7 @@ Config getConfig() {
}

private void setLogLevel() {
Object object = getMappedConfig().get(IS_LOG_LEVEL_ERROR);
Object object = getMappedConfig().get(LOG_LEVEL_IS_ERROR);
auditFunc = (object != null && (Boolean) object) ?
LoggerFactory.getLogger(Constants.AUDIT_LOGGER)::error : LoggerFactory.getLogger(Constants.AUDIT_LOGGER)::info;
}
Expand Down Expand Up @@ -197,9 +207,13 @@ private void setConfigData() {
if(object != null && (Boolean) object) {
auditOnError = true;
}
object = getMappedConfig().get(IS_MASK_ENABLED);
object = getMappedConfig().get(MASK);
if(object != null && (Boolean) object) {
mask = true;
}
object = getMappedConfig().get(ENABLED);
if(object != null && (Boolean) object) {
isMaskEnabled = true;

}
timestampFormat = (String)getMappedConfig().get(TIMESTAMP_FORMAT);
}
Expand Down
98 changes: 51 additions & 47 deletions audit/src/main/java/com/networknt/audit/AuditHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
* responseTime
*
* Created by steve on 17/09/16.
* This handler is replaced by the AuditInterceptor for logging request body and response body.
*/
public class AuditHandler implements MiddlewareHandler {
static final Logger logger = LoggerFactory.getLogger(AuditHandler.class);
Expand All @@ -86,10 +87,10 @@ public class AuditHandler implements MiddlewareHandler {
static final String REQUEST_COOKIES_KEY = "requestCookies";
static final String STATUS_KEY = "status";
static final String SERVER_CONFIG = "server";
static final String SERVICEID_KEY = "serviceId";
static final String SERVICE_ID_KEY = "serviceId";
static final String INVALID_CONFIG_VALUE_CODE = "ERR10060";

private AuditConfig auditConfig;
private AuditConfig config;

private volatile HttpHandler next;

Expand All @@ -99,12 +100,12 @@ public class AuditHandler implements MiddlewareHandler {

public AuditHandler() {
if (logger.isInfoEnabled()) logger.info("AuditHandler is loaded.");
auditConfig = AuditConfig.load();
config = AuditConfig.load();
Map<String, Object> serverConfig = Config.getInstance().getJsonMapConfigNoCache(SERVER_CONFIG);
if (serverConfig != null) {
serviceId = (String) serverConfig.get(SERVICEID_KEY);
serviceId = (String) serverConfig.get(SERVICE_ID_KEY);
}
String timestampFormat = auditConfig.getTimestampFormat();
String timestampFormat = config.getTimestampFormat();
if (!StringUtils.isBlank(timestampFormat)) {
try {
DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(timestampFormat)
Expand All @@ -126,35 +127,36 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
auditMap.put(TIMESTAMP, DATE_TIME_FORMATTER == null ? System.currentTimeMillis() : DATE_TIME_FORMATTER.format(Instant.now()));

// dump audit info fields according to config
boolean needAuditData = auditInfo != null && auditConfig.hasAuditList();
boolean needAuditData = auditInfo != null && config.hasAuditList();
if (needAuditData) {
auditFields(auditInfo, auditMap);
}

// dump request header, request body, path parameters, query parameters and request cookies according to config
auditRequest(exchange, auditMap, auditConfig);
auditRequest(exchange, auditMap, config);

// dump serviceId from server.yml
if (auditConfig.hasAuditList() && auditConfig.getAuditList().contains(SERVICEID_KEY)) {
if (config.hasAuditList() && config.getAuditList().contains(SERVICE_ID_KEY)) {
auditServiceId(auditMap);
}

if (auditConfig.isStatusCode() || auditConfig.isResponseTime()) {
if (config.isStatusCode() || config.isResponseTime()) {
exchange.addExchangeCompleteListener((exchange1, nextListener) -> {
if (auditConfig.isStatusCode()) {
// response status code and response time.
if (config.isStatusCode()) {
auditMap.put(STATUS_CODE, exchange1.getStatusCode());
}
if (auditConfig.isResponseTime()) {
if (config.isResponseTime()) {
auditMap.put(RESPONSE_TIME, System.currentTimeMillis() - start);
}
// add additional fields accumulated during the microservice execution
// according to the config
Map<String, Object> auditInfo1 = exchange.getAttachment(AttachmentConstants.AUDIT_INFO);
if (auditInfo1 != null) {
if (auditConfig.getAuditList() != null && auditConfig.getAuditList().size() > 0) {
for (String name : auditConfig.getAuditList()) {
if (config.getAuditList() != null && config.getAuditList().size() > 0) {
for (String name : config.getAuditList()) {
if (name.equals(RESPONSE_BODY_KEY)) {
auditResponseOnError(exchange, auditMap);
auditResponseBody(exchange, auditMap);
}
auditMap.putIfAbsent(name, auditInfo1.get(name));
}
Expand All @@ -163,11 +165,11 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {

try {
// audit entries only is it is an error, if auditOnError flag is set
if (auditConfig.isAuditOnError()) {
if (config.isAuditOnError()) {
if (exchange1.getStatusCode() >= 400)
auditConfig.getAuditFunc().accept(Config.getInstance().getMapper().writeValueAsString(auditMap));
config.getAuditFunc().accept(Config.getInstance().getMapper().writeValueAsString(auditMap));
} else {
auditConfig.getAuditFunc().accept(Config.getInstance().getMapper().writeValueAsString(auditMap));
config.getAuditFunc().accept(Config.getInstance().getMapper().writeValueAsString(auditMap));
}
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
Expand All @@ -176,15 +178,15 @@ public void handleRequest(final HttpServerExchange exchange) throws Exception {
nextListener.proceed();
});
} else {
auditConfig.getAuditFunc().accept(auditConfig.getConfig().getMapper().writeValueAsString(auditMap));
config.getAuditFunc().accept(config.getConfig().getMapper().writeValueAsString(auditMap));
}
next(exchange);
}

private void auditHeader(HttpServerExchange exchange, Map<String, Object> auditMap) {
for (String name : auditConfig.getHeaderList()) {
for (String name : config.getHeaderList()) {
String value = exchange.getRequestHeaders().getFirst(name);
auditMap.put(name, auditConfig.isMaskEnabled() ? Mask.maskRegex(value, "requestHeader", name) : value);
auditMap.put(name, config.isMask() ? Mask.maskRegex(value, "requestHeader", name) : value);
}
}

Expand All @@ -193,21 +195,21 @@ protected void next(HttpServerExchange exchange) throws Exception {
}

private void auditFields(Map<String, Object> auditInfo, Map<String, Object> auditMap) {
for (String name : auditConfig.getAuditList()) {
for (String name : config.getAuditList()) {
Object value = auditInfo.get(name);
boolean needApplyMask = auditConfig.isMaskEnabled() && value instanceof String;
boolean needApplyMask = config.isMask() && value instanceof String;
auditMap.put(name, needApplyMask ? Mask.maskRegex((String) value, MASK_KEY, name) : value);
}
}

private void auditRequest(HttpServerExchange exchange, Map<String, Object> auditMap, AuditConfig auditConfig) {
if (auditConfig.hasHeaderList()) {
private void auditRequest(HttpServerExchange exchange, Map<String, Object> auditMap, AuditConfig config) {
if (config.hasHeaderList()) {
auditHeader(exchange, auditMap);
}
if (!auditConfig.hasAuditList()) {
if (!config.hasAuditList()) {
return;
}
for (String key : auditConfig.getAuditList()) {
for (String key : config.getAuditList()) {
switch (key) {
case REQUEST_BODY_KEY:
auditRequestBody(exchange, auditMap);
Expand Down Expand Up @@ -239,22 +241,24 @@ private void auditRequestBody(HttpServerExchange exchange, Map<String, Object> a
}
// Mask requestBody json string if mask enabled
if (requestBodyString != null) {
auditMap.put(REQUEST_BODY_KEY, auditConfig.isMaskEnabled() ? Mask.maskJson(requestBodyString, REQUEST_BODY_KEY) : requestBodyString);
auditMap.put(REQUEST_BODY_KEY, config.isMask() ? Mask.maskJson(requestBodyString, REQUEST_BODY_KEY) : requestBodyString);
}
}

// Audit response body only if auditOnError is enabled
private void auditResponseOnError(HttpServerExchange exchange, Map<String, Object> auditMap) {
if (!auditOnError) {
return;
}
String responseBodyString = null;
Map<String, Object> auditInfo = exchange.getAttachment(AttachmentConstants.AUDIT_INFO);
if (auditInfo != null && auditInfo.get(STATUS_KEY) != null) {
responseBodyString = auditInfo.get(STATUS_KEY).toString();
// Audit response body
private void auditResponseBody(HttpServerExchange exchange, Map<String, Object> auditMap) {
String responseBodyString = exchange.getAttachment(AttachmentConstants.RESPONSE_BODY_STRING);
if(responseBodyString == null && exchange.getAttachment(AttachmentConstants.RESPONSE_BODY) != null) {
// try to convert the response body to JSON if possible. Fallback to String().
try {
responseBodyString = Config.getInstance().getMapper().writeValueAsString(exchange.getAttachment(AttachmentConstants.RESPONSE_BODY));
} catch (JsonProcessingException e) {
responseBodyString = exchange.getAttachment(AttachmentConstants.RESPONSE_BODY).toString();
}
}
if (responseBodyString != null) {
auditMap.put(RESPONSE_BODY_KEY, auditConfig.isMaskEnabled() ? Mask.maskJson(responseBodyString, RESPONSE_BODY_KEY) : responseBodyString);
// mask the response body json string if mask is enabled.
if(responseBodyString != null) {
auditMap.put(RESPONSE_BODY_KEY, config.isMask() ? Mask.maskJson(responseBodyString, RESPONSE_BODY_KEY) : responseBodyString);
}
}

Expand All @@ -265,7 +269,7 @@ private void auditQueryParameters(HttpServerExchange exchange, Map<String, Objec
if (queryParameters != null && queryParameters.size() > 0) {
for (String query : queryParameters.keySet()) {
String value = queryParameters.get(query).toString();
String mask = auditConfig.isMaskEnabled() ? Mask.maskRegex(value, QUERY_PARAMETERS_KEY, query) : value;
String mask = config.isMask() ? Mask.maskRegex(value, QUERY_PARAMETERS_KEY, query) : value;
res.put(query, mask);
}
auditMap.put(QUERY_PARAMETERS_KEY, res.toString());
Expand All @@ -278,7 +282,7 @@ private void auditPathParameters(HttpServerExchange exchange, Map<String, Object
if (pathParameters != null && pathParameters.size() > 0) {
for (String name : pathParameters.keySet()) {
String value = pathParameters.get(name).toString();
String mask = auditConfig.isMaskEnabled() ? Mask.maskRegex(value, PATH_PARAMETERS_KEY, name) : value;
String mask = config.isMask() ? Mask.maskRegex(value, PATH_PARAMETERS_KEY, name) : value;
res.put(name, mask);
}
auditMap.put(PATH_PARAMETERS_KEY, res.toString());
Expand All @@ -291,7 +295,7 @@ private void auditRequestCookies(HttpServerExchange exchange, Map<String, Object
if (cookieMap != null && cookieMap.size() > 0) {
for (String name : cookieMap.keySet()) {
String cookieString = cookieMap.get(name).getValue();
String mask = auditConfig.isMaskEnabled() ? Mask.maskRegex(cookieString, REQUEST_COOKIES_KEY, name) : cookieString;
String mask = config.isMask() ? Mask.maskRegex(cookieString, REQUEST_COOKIES_KEY, name) : cookieString;
res.put(name, mask);
}
auditMap.put(REQUEST_COOKIES_KEY, res.toString());
Expand All @@ -300,7 +304,7 @@ private void auditRequestCookies(HttpServerExchange exchange, Map<String, Object

private void auditServiceId(Map<String, Object> auditMap) {
if (!StringUtils.isBlank(serviceId)) {
auditMap.put(SERVICEID_KEY, serviceId);
auditMap.put(SERVICE_ID_KEY, serviceId);
}
}

Expand All @@ -318,21 +322,21 @@ public MiddlewareHandler setNext(final HttpHandler next) {

@Override
public boolean isEnabled() {
Object object = auditConfig.getMappedConfig().get(ENABLED);
Object object = config.getMappedConfig().get(ENABLED);
return object != null && (Boolean) object;
}

@Override
public void register() {
ModuleRegistry.registerModule(AuditHandler.class.getName(), auditConfig.getMappedConfig(), null);
ModuleRegistry.registerModule(AuditHandler.class.getName(), config.getMappedConfig(), null);
}

@Override
public void reload() {
if (auditConfig==null) {
auditConfig = AuditConfig.load();
if (config==null) {
config = AuditConfig.load();
} else {
auditConfig.reload();
config.reload();
}
}
}
4 changes: 0 additions & 4 deletions audit/src/main/resources/config/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ responseTime: ${audit.responseTime:true}

# when auditOnError is true:
# - it will only log when status code >= 400
# - response body will be only logged when auditOnError is true
# - status detail will be only logged when auditOnError is true
# when auditOnError is false:
# - it will log on every request
# - no response body will be logged.
# - no status detail will be logged.
# log level is controlled by logLevel
auditOnError: ${audit.auditOnError:false}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public void shouldLoadEmptyConfig() {
Assert.assertTrue(config.isStatusCode());
Assert.assertTrue(config.isResponseTime());
Assert.assertFalse(config.isAuditOnError());
Assert.assertFalse(config.isMaskEnabled());
Assert.assertFalse(config.isMask());
Assert.assertNotNull(config.getTimestampFormat());
}

Expand Down
10 changes: 10 additions & 0 deletions body/src/main/java/com/networknt/body/BodyConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,13 @@ public class BodyConfig {
public static final String CONFIG_NAME = "body";
private static final String ENABLED = "enabled";
private static final String CACHE_REQUEST_BODY = "cacheRequestBody";

private static final String CACHE_RESPONSE_BODY = "cacheResponseBody";
private static final String APPLIED_PATH_PREFIXES = "appliedPathPrefixes";

boolean enabled;
boolean cacheRequestBody;
boolean cacheResponseBody;
List<String> appliedPathPrefixes;

private Config config;
Expand Down Expand Up @@ -86,6 +89,9 @@ public boolean isEnabled() {
public boolean isCacheRequestBody() {
return cacheRequestBody;
}
public boolean isCacheResponseBody() {
return cacheResponseBody;
}

public List<String> getAppliedPathPrefixes() {
return appliedPathPrefixes;
Expand All @@ -100,6 +106,10 @@ private void setConfigData() {
if (object != null && (Boolean) object) {
cacheRequestBody = (Boolean)object;
}
object = mappedConfig.get(CACHE_RESPONSE_BODY);
if (object != null && (Boolean) object) {
cacheResponseBody = (Boolean)object;
}
}

private void setConfigList() {
Expand Down
2 changes: 1 addition & 1 deletion body/src/main/java/com/networknt/body/BodyHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public class BodyHandler implements MiddlewareHandler {
static final Logger logger = LoggerFactory.getLogger(BodyHandler.class);
static final String CONTENT_TYPE_MISMATCH = "ERR10015";

// request body will be parse during validation and it is attached to the exchange, in JSON,
// request body will be parsed during validation and it is attached to the exchange, in JSON,
// it could be a map or list. So treat it as Object in the attachment.
public static final AttachmentKey<Object> REQUEST_BODY = AttachmentConstants.REQUEST_BODY;

Expand Down
Loading

0 comments on commit 986c279

Please sign in to comment.