Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixes #1317 convert the audit handler to interceptor for logging requ… #1318

Merged
merged 1 commit into from
Aug 4, 2022
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
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