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

Resource level filter logging feature added #12086

Closed
wants to merge 41 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e1b423e
ResourcePath and ResourceMethod added to loggingApiInput Schema
shnrndk Mar 13, 2023
d57654e
Moved log_level field from AM_API to AM_API_URL_MAPPING.
shnrndk Mar 17, 2023
1de9212
log level field added to am_api also to work with both tables.
shnrndk Mar 23, 2023
d69f1b1
changed the get apis related api logging to support with the two tables.
shnrndk Mar 24, 2023
2fadd3d
Unit test testGetApiLogging testcase failure fixed.
shnrndk Mar 27, 2023
d3f2e0b
/api-logging-configs GET rest api changed to support the resourceMeth…
shnrndk Mar 30, 2023
2c446ab
getAPILogLevel() method changed to compare the log level from api lev…
shnrndk Mar 30, 2023
7eb9ec2
logResourceProperties inner class changed to properties hash map.
shnrndk Apr 3, 2023
b71e155
Changed RETRIEVE_ALL_PER_API_RESOURCE_LOGGING_SQL sql query to return…
shnrndk Apr 3, 2023
a29e40d
resourceMethod and resourcePath added APIEvent class
shnrndk Apr 3, 2023
2529c77
resourceMethod and resourcePath added APIEvent class
shnrndk Apr 3, 2023
8f19598
Merge remote-tracking branch 'origin/ResoureLevelFilterLogging' into …
shnrndk Apr 3, 2023
dacb9dd
Fixed some bugs in the logic.
shnrndk Apr 5, 2023
557a1d5
Log properties multiple key adding bug fixed
shnrndk Apr 7, 2023
391d5ea
regex matching added to resource path checking
shnrndk Apr 8, 2023
5f63a96
Code reformatted
shnrndk Apr 8, 2023
fc24c50
Merge branch 'master' into ResourceLevelLogfilteringCopy
shnrndk Apr 10, 2023
93ac087
Added validation to check api resource is valid or not when adding th…
shnrndk Apr 11, 2023
24ade92
If condition logic simplified
shnrndk Apr 12, 2023
8e7f197
NullPointerException fixed when passing without resourcePath and reso…
shnrndk Apr 12, 2023
4f0eac8
Null pointer issue fixed when the resourcePath is null.
shnrndk Apr 12, 2023
ec5a893
resourcePath regex matching changed to support - and alphabets and no…
shnrndk Apr 12, 2023
62c4bdc
added log level off comparison to the filter
shnrndk Apr 12, 2023
44c75bf
Regex pattern match updated
shnrndk Apr 17, 2023
0eca0a3
Array index out of bound error fixed.
shnrndk Apr 17, 2023
529c806
Get log details of all apis not returning resource methods and paths …
shnrndk Apr 18, 2023
6057715
removing regex usage for finding the resource path
chamikasudusinghe Jun 12, 2023
d33301b
fix for cors handler to process different method types
chamikasudusinghe Jun 12, 2023
ba941b4
including apilevel logging in the same logic
chamikasudusinghe Jun 13, 2023
c8fcc63
limit execution of redundant code
chamikasudusinghe Jun 14, 2023
3552d93
reset resource object for every invocation
chamikasudusinghe Jun 16, 2023
f02bad8
refactored the implementation using util methods
chamikasudusinghe Jun 20, 2023
16008d2
fixed formatting
chamikasudusinghe Jun 20, 2023
91eff3c
move constant from logshandler to restconstants
chamikasudusinghe Jun 21, 2023
9d09635
using synapse util to find the api
chamikasudusinghe Jun 22, 2023
a0cfc05
Api identification added to getMatchingLogLevel method.
shnrndk Jul 17, 2023
4f921fc
Unit test failures fixed by adding mock to the Utils.getAcceptableRes…
shnrndk Jul 17, 2023
0b7f837
formatted logging related sql scripts
shnrndk Jul 18, 2023
c824e59
logging queries changed to not return revisioned apis.
shnrndk Jul 19, 2023
23a7904
Merge branch 'wso2:master' into resource_lvl_logging
shnrndk Jul 19, 2023
de3deca
Added match identified api context with the log properties
shnrndk Jul 25, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,7 @@ public enum ExceptionCodes implements ErrorHandler {
LOGGING_API_NOT_FOUND(901400, "Requested Resource Not Found", 404, "Request API Not Found for context: %s"),
LOGGING_API_INCORRECT_LOG_LEVEL(901401, "Bad Request", 400, "Log level should be either OFF, BASIC, STANDARD or FULL"),
LOGGING_API_MISSING_DATA(901402, "Missing data", 400, "API context or log level is missing"),
LOGGING_API_RESOURCE_NOT_FOUND(901403, "Requested Resource Not Found", 400, "Requested API Resource Not Found"),
CORRELATION_CONFIG_BAD_REQUEST(902020, "Bad Request", 400, "Request body can not have empty elements"),
CORRELATION_CONFIG_BAD_REQUEST_INVALID_NAME(902021, "Bad Request", 400, "Request body contains invalid correlation component name"),
//Service Catalog related error codes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@
public class APILoggingImpl {
private static final String PER_API_LOGGING_PERMISSION_PATH = "/permission/protected/configure/logging";
private static final String INVALID_LOGGING_PERMISSION = "Invalid logging permission";
private static final String INCORRECT_LOGGING_PER_API_RESOURCE_REQUEST = "Resource Method and Resource Path both " +
"should included";
private static final String REQUIRED_API_RESOURCE_IS_NOT_AVAILABLE = "Requested resource is not available";
private final ApiMgtDAO apiMgtDAO = ApiMgtDAO.getInstance();

public void addUpdateAPILogger(String tenantId, String apiId, String logLevel) throws APIManagementException {
public void addUpdateAPILogger(String tenantId, String apiId, String logLevel, String resourceMethod,
String resourcePath) throws APIManagementException {
if (apiMgtDAO.getAPIInfoByUUID(apiId) == null) {
throw new APIManagementException("API not found.",
ExceptionCodes.from(ExceptionCodes.API_NOT_FOUND, apiId));
Expand All @@ -49,13 +53,29 @@ public void addUpdateAPILogger(String tenantId, String apiId, String logLevel) t
throw new APIManagementException(INVALID_LOGGING_PERMISSION,
ExceptionCodes.from(ExceptionCodes.INVALID_PERMISSION));
}
LoggingMgtDAO.getInstance().addAPILogger(tenantId, apiId, logLevel);
publishLogAPIData(tenantId, apiId, logLevel);
if (resourceMethod != null && resourcePath != null) {
boolean isAPIResourceExists = LoggingMgtDAO.getInstance().checkAPILoggerPerResourceAvailable(tenantId,
apiId, resourceMethod.toUpperCase(), resourcePath);
if (isAPIResourceExists) {
LoggingMgtDAO.getInstance().addAPILoggerPerResource(tenantId, apiId, logLevel,
resourceMethod.toUpperCase(), resourcePath);
} else {
throw new APIManagementException(REQUIRED_API_RESOURCE_IS_NOT_AVAILABLE,
ExceptionCodes.from(ExceptionCodes.LOGGING_API_RESOURCE_NOT_FOUND));
}
} else if (resourceMethod == null && resourcePath == null) {
LoggingMgtDAO.getInstance().addAPILogger(tenantId, apiId, logLevel);
} else {
throw new APIManagementException(INCORRECT_LOGGING_PER_API_RESOURCE_REQUEST,
ExceptionCodes.from(ExceptionCodes.LOGGING_API_MISSING_DATA));
}

publishLogAPIData(tenantId, apiId, logLevel, resourceMethod, resourcePath);
}

private void publishLogAPIData(String tenantId, String apiId, String logLevel) throws APIManagementException {
private void publishLogAPIData(String tenantId, String apiId, String logLevel, String resourceMethod, String resourcePath) throws APIManagementException {
APIEvent apiEvent = new APIEvent(apiId, logLevel, APIConstants.EventType.UDATE_API_LOG_LEVEL.name(),
apiMgtDAO.getAPIContext(apiId));
apiMgtDAO.getAPIContext(apiId), resourceMethod, resourcePath, tenantId);
APIUtil.sendNotification(apiEvent, APIConstants.NotifierType.API.name());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
*/
public class APILoggerManager {
private static final Log log = LogFactory.getLog(APILoggerManager.class);
private static final Map<String, String> logProperties = new HashMap<>();
private Map<Map<String, String>, String> logProperties = new HashMap<>();
private static final APILoggerManager apiLoggerManager = new APILoggerManager();
private final EventHubConfigurationDto eventHubConfigurationDto;
public static final String UTF8 = "UTF-8";
Expand All @@ -58,7 +58,17 @@ public void initializeAPILoggerList() {
JSONArray apiLogArray = responseJson.getJSONArray("apis");
for (int i = 0; i < apiLogArray.length(); i++) {
JSONObject apiLoggerObject = apiLogArray.getJSONObject(i);
logProperties.put(apiLoggerObject.getString("context"), apiLoggerObject.getString("logLevel"));
String resourceMethod = null;
String resourcePath = null;
if(!apiLoggerObject.isNull("resourceMethod") && !apiLoggerObject.isNull("resourcePath") ){
resourceMethod = apiLoggerObject.getString("resourceMethod");
resourcePath = apiLoggerObject.getString("resourcePath");
}
Map<String, String> properties = new HashMap<>();
properties.put("context", apiLoggerObject.getString("context"));
properties.put("resourceMethod", resourceMethod);
properties.put("resourcePath", resourcePath);
logProperties.put(properties, apiLoggerObject.getString("logLevel"));
}
if (log.isDebugEnabled()) {
log.debug("Response : " + responseString);
Expand All @@ -68,11 +78,15 @@ public void initializeAPILoggerList() {
}
}

public void updateLoggerMap(String apiContext, String logLevel) {
logProperties.put(apiContext, logLevel);
public void updateLoggerMap(String apiContext, String logLevel, String resourceMethod, String resourcePath) {
Map<String, String> properties = new HashMap<>();
properties.put("context", apiContext);
properties.put("resourcePath", resourcePath);
properties.put("resourceMethod", resourceMethod);
logProperties.put(properties, logLevel);
}

public Map<String, String> getPerAPILoggerList() {
public Map<Map<String, String>, String> getPerAPILoggerList() {
return logProperties;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@

package org.wso2.carbon.apimgt.gateway.handlers;

import org.apache.axis2.Constants;
import org.apache.http.HttpHeaders;
import org.apache.synapse.MessageContext;
import org.apache.synapse.api.API;
import org.apache.synapse.api.ApiUtils;
import org.apache.synapse.api.Resource;
import org.apache.synapse.api.dispatch.DispatcherHelper;
import org.apache.synapse.api.dispatch.RESTDispatcher;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.wso2.carbon.apimgt.gateway.APIMgtGatewayConstants;
import org.wso2.carbon.apimgt.impl.APIConstants;

import java.util.Map;
import java.util.*;

/**
* Provides util methods for the LogsHandler
Expand Down Expand Up @@ -91,7 +97,7 @@ protected static String getElectedResource(org.apache.synapse.MessageContext mes
return (String) messageContext.getProperty("API_ELECTED_RESOURCE");
}

protected static String getResourceCacheKey(org.apache.synapse.MessageContext messageContext){
protected static String getResourceCacheKey(org.apache.synapse.MessageContext messageContext) {
return (String) messageContext.getProperty("API_RESOURCE_CACHE_KEY");
}

Expand Down Expand Up @@ -119,16 +125,99 @@ protected static String getTransportInURL(org.apache.synapse.MessageContext mess
return transportInURL.substring(1);
}

protected static String getMatchingLogLevel(MessageContext ctx, Map<String, String> logProperties) {
String apiCtx = LogUtils.getTransportInURL(ctx);
for (Map.Entry<String, String> entry : logProperties.entrySet()) {
String key = entry.getKey().substring(1);
if (apiCtx.startsWith(key + "/") || apiCtx.equals(key)) {
ctx.setProperty(LogsHandler.LOG_LEVEL, entry.getValue());
ctx.setProperty("API_TO", apiCtx);
return entry.getValue();
protected static String getMatchingLogLevel(MessageContext messageContext,
Map<Map<String, String>, String> logProperties) {
//initializing variables to store resource level logging
String apiLogLevel = null;
String resourceLogLevel = null;
String resourcePath = null;
String resourceMethod = null;
Resource selectedResource = null;
//obtain the selected API by context and path
API selectedApi = null;
Collection<API> apiSet = messageContext.getEnvironment().getSynapseConfiguration().getAPIs();
//identify the api
for (API api : apiSet) {
if (ApiUtils.identifyApi(api, messageContext)) {
selectedApi = api;
break;
}
}
return null;
String apiContext = ((Axis2MessageContext) messageContext).getAxis2MessageContext()
.getProperty("TransportInURL").toString();
String httpMethod = (String) ((Axis2MessageContext) messageContext).getAxis2MessageContext()
.getProperty(Constants.Configuration.HTTP_METHOD);

if (selectedApi != null) {
Utils.setSubRequestPath(selectedApi, messageContext);
//iterating through all the existing resources to match with the requesting method
Map<String, Resource> resourcesMap = selectedApi.getResourcesMap();
Set<Resource> acceptableResources = ApiUtils
.getAcceptableResources(resourcesMap, messageContext);
if (!acceptableResources.isEmpty()) {
for (RESTDispatcher dispatcher : ApiUtils.getDispatchers()) {
selectedResource = dispatcher.findResource(messageContext, acceptableResources);
if (selectedResource != null) {
DispatcherHelper helper = selectedResource.getDispatcherHelper();
for (Map.Entry<Map<String, String>, String> entry : logProperties.entrySet()) {
Map<String, String> key = entry.getKey();
//if resource path is empty, proceeding with API level logs
if (selectedApi.getContext().equals(key.get("context"))) {
if (key.get("resourcePath") == null && key.get("resourceMethod") == null) {
apiLogLevel = entry.getValue();
//matching the methods first and then the resource path
} else if (httpMethod.equals(key.get("resourceMethod"))) {
if (helper.getString().equals(key.get("resourcePath"))) {
resourceLogLevel = entry.getValue();
resourcePath = key.get("resourcePath");
resourceMethod = key.get("resourceMethod");
}
}
}
}
}
}
}
}
boolean isResourceLevelHasHighPriority = false;
if (resourceLogLevel != null) {
switch (resourceLogLevel) {
case APIConstants.LOG_LEVEL_FULL:
isResourceLevelHasHighPriority = true;
break;
case APIConstants.LOG_LEVEL_STANDARD:
if (apiLogLevel != null && (apiLogLevel.equals(APIConstants.LOG_LEVEL_BASIC)
|| apiLogLevel.equals(APIConstants.LOG_LEVEL_OFF))) {
isResourceLevelHasHighPriority = true;
break;
} else {
break;
}
case APIConstants.LOG_LEVEL_BASIC:
if (apiLogLevel == null || apiLogLevel.equals(APIConstants.LOG_LEVEL_OFF)) {
isResourceLevelHasHighPriority = true;
} else {
break;
}
}
if (isResourceLevelHasHighPriority || apiLogLevel == null) {
messageContext.setProperty(LogsHandler.LOG_LEVEL, resourceLogLevel);
messageContext.setProperty(LogsHandler.RESOURCE_PATH, resourcePath);
messageContext.setProperty(LogsHandler.RESOURCE_METHOD, resourceMethod);
messageContext.setProperty("API_TO", apiContext);
return resourceLogLevel;
} else {
messageContext.setProperty(LogsHandler.LOG_LEVEL, apiLogLevel);
messageContext.setProperty("API_TO", apiContext);
return apiLogLevel;
}
} else if (apiLogLevel != null) {
messageContext.setProperty(LogsHandler.LOG_LEVEL, apiLogLevel);
messageContext.setProperty("API_TO", apiContext);
return apiLogLevel;
} else {
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ public class LogsHandler extends AbstractSynapseHandler {
private static final String UUID_HEADER = "UUID_HEADER";
private static final String CORRELATION_ID_HEADER = "CORRELATION_ID_HEADER";
protected static final String LOG_LEVEL = "LOG_LEVEL";

protected static final String RESOURCE_PATH = "RESOURCE_PATH";
protected static final String RESOURCE_METHOD = "RESOURCE_METHOD";
private static final String REQUEST_BODY_SIZE_ERROR = "Error occurred while building the message to calculate" +
" the response body size";
private static final String REQUEST_EVENT_PUBLICATION_ERROR = "Cannot publish request event. ";
Expand Down Expand Up @@ -278,7 +279,7 @@ private long buildResponseMessage(org.apache.synapse.MessageContext messageConte
*
* @param map Map containing API context and logLevel
*/
public static Map<String, String> syncAPILogData(Map<String, Object> map) {
public static Map<Map<String, String>, String> syncAPILogData(Map<String, Object> map) {
String apictx = (String) map.get("context");
String logLevel = (String) map.get("value");
log.debug("Log level for " + apictx + " is changed to " + logLevel);
Expand All @@ -288,7 +289,7 @@ public static String getLogData(String context) {
return APILoggerManager.getInstance().getPerAPILoggerList().get(context);
}

public static Map<String, String> getLogData() {
public static Map<Map<String, String>, String> getLogData() {
return APILoggerManager.getInstance().getPerAPILoggerList();
}

Expand All @@ -299,10 +300,11 @@ public static Map<String, String> getLogData() {
* @return log level of the API or null if not
*/
private String getAPILogLevel(MessageContext ctx) {
Map<String, String> logProperties = APILoggerManager.getInstance().getPerAPILoggerList();
Map<Map<String, String>, String> logProperties = APILoggerManager.getInstance().getPerAPILoggerList();
// if the logging API data holder is empty or null return null
if (!logProperties.isEmpty()) {
return LogUtils.getMatchingLogLevel(ctx, logProperties);
//return LogUtils.getMatchingLogLevel(ctx, logProperties);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.apache.synapse.SynapseConstants;
import org.apache.synapse.api.API;
import org.apache.synapse.api.ApiUtils;
import org.apache.synapse.api.Resource;
import org.apache.synapse.commons.json.JsonUtil;
import org.apache.synapse.config.xml.rest.VersionStrategyFactory;
import org.apache.synapse.core.axis2.Axis2MessageContext;
Expand Down Expand Up @@ -676,4 +677,65 @@ public static X509Certificate convertCertificateToX509Certificate(Certificate ce
}
return null;
}

/**
* Using the api context to match API path to get the invoked API from an API Collection.
*
* @param messageContext MessageContext
* @return selected API based on the API path
*/
public static API getAPIByContext(MessageContext messageContext) {
API selectedApi = null;
//getting the API collection from the synapse configuration to find the invoked API
Collection<API> apiSet = messageContext.getEnvironment().getSynapseConfiguration().getAPIs();
List<API> duplicateApiSet = new ArrayList<>(apiSet);
//obtaining required parameters to execute findResource method
String requestPath = ApiUtils.getFullRequestPath(messageContext);
for (API api : duplicateApiSet) {
if (ApiUtils.matchApiPath(requestPath, api.getContext())) {
selectedApi = api;
break;
}
}
return selectedApi;
}

/**
* Select acceptable resources from the set of all resources based on requesting methods.
*
* @return set of acceptable resources
*/
public static Set<Resource> getAcceptableResources(Resource[] allAPIResources,
String httpMethod, String corsRequestMethod) {
Set<Resource> acceptableResources = new LinkedHashSet<>();
for (Resource resource : allAPIResources) {
//If the requesting method is OPTIONS or if the Resource contains the requesting method
String [] resourceMethods = resource.getMethods();
if ((RESTConstants.METHOD_OPTIONS.equals(httpMethod) && resourceMethods != null
&& Arrays.asList(resourceMethods).contains(corsRequestMethod))
|| (resourceMethods != null && Arrays.asList(resourceMethods).contains(httpMethod))) {
acceptableResources.add(resource);
}
}
return acceptableResources;
}

/**
* Obtain the selected resource from the message context for CORSRequestHandler.
*
* @return selected resource
*/
public static Resource getSelectedResource(MessageContext messageContext,
String httpMethod, String corsRequestMethod) {
Resource selectedResource = null;
Resource resource = (Resource) messageContext.getProperty(RESTConstants.SELECTED_RESOURCE);
String [] resourceMethods = resource.getMethods();
if ((RESTConstants.METHOD_OPTIONS.equals(httpMethod) && resourceMethods != null
&& Arrays.asList(resourceMethods).contains(corsRequestMethod))
|| (resourceMethods != null && Arrays.asList(resourceMethods).contains(httpMethod))) {
selectedResource = resource;
}
return selectedResource;
}

}
Loading
Loading