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 #12106

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 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
3b43bea
regex matching added and NullPointerException fixed when passing with…
shnrndk Apr 12, 2023
ca44923
Added log level off comparison to the filter.
shnrndk Apr 12, 2023
cf75c98
Get log details of all apis not returning resource methods and paths …
shnrndk Apr 18, 2023
bac6cf5
removing regex usage for finding the resource path
chamikasudusinghe Jun 12, 2023
c1dfcf2
including apilevel logging in the same logic
chamikasudusinghe Jun 13, 2023
bf26843
limit execution of redundant code
chamikasudusinghe Jun 14, 2023
97315bb
reset resource object for every invocation
chamikasudusinghe Jun 16, 2023
13f8260
refactored the implementation using util methods
chamikasudusinghe Jun 20, 2023
e599fcc
move constant from logshandler to restconstants
chamikasudusinghe Jun 21, 2023
7e7e6a2
Api identification added to getMatchingLogLevel method.
chamikasudusinghe Jun 22, 2023
b34c138
Unit test failures fixed by adding mock to the Utils.getAcceptableRes…
shnrndk Jul 17, 2023
d14d56d
logging queries changed to not return revisioned apis.
shnrndk Jul 18, 2023
e5dda07
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 @@ -494,6 +494,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"),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

404 for 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 @@ -46,7 +46,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 int RETRIEVAL_RETRIES = 15;
Expand All @@ -61,7 +61,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"));
Copy link
Contributor

@chamilaadhi chamilaadhi Nov 15, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use constants and refer. There are many places referring to string like context, resourcePath, etc

properties.put("resourceMethod", resourceMethod);
properties.put("resourcePath", resourcePath);
logProperties.put(properties, apiLoggerObject.getString("logLevel"));
}
if (log.isDebugEnabled()) {
log.debug("Response : " + responseString);
Expand All @@ -71,11 +81,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.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better not to use wild card imports. Importing specific class improves readability


/**
* 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use constants previously defined

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