content: /*
   * Copyright 1999-2018 Alibaba Group Holding Ltd.
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.alibaba.nacos.config.server.aspect;
  
  import com.alibaba.nacos.api.config.remote.request.ConfigBatchListenRequest;
  import com.alibaba.nacos.api.remote.request.RequestMeta;
  import com.alibaba.nacos.api.remote.response.Response;
  import com.alibaba.nacos.common.utils.MD5Utils;
  import com.alibaba.nacos.config.server.constant.Constants;
  import com.alibaba.nacos.config.server.model.ConfigRequestInfo;
  import com.alibaba.nacos.config.server.model.form.ConfigForm;
  import com.alibaba.nacos.config.server.model.gray.BetaGrayRule;
  import com.alibaba.nacos.config.server.monitor.MetricsMonitor;
  import com.alibaba.nacos.config.server.service.ConfigCacheService;
  import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest;
  import com.alibaba.nacos.config.server.utils.GroupKey2;
  import com.alibaba.nacos.config.server.utils.LogUtil;
  import com.alibaba.nacos.config.server.utils.RequestUtil;
  import org.aspectj.lang.ProceedingJoinPoint;
  import org.aspectj.lang.annotation.Around;
  import org.aspectj.lang.annotation.Aspect;
  import org.springframework.stereotype.Component;
  
  import java.util.concurrent.TimeUnit;
  import java.util.concurrent.atomic.AtomicLong;
  
  /**
   * Aspect for logging HTTP API and SDK API requests in Nacos.
   *
   * @author Nacos
   */
  @Aspect
  @Component
  public class RequestLogAspect {
      
      private static final String PUBLISH_CONFIG = "execution(* com.alibaba.nacos.config.server.service.ConfigOperationService.publishConfig(..))";
      
      private static final String GET_CONFIG = "execution(* com.alibaba.nacos.config.server.service.query.ConfigQueryChainService.handle(..))";
      
      private static final String DELETE_CONFIG = "execution(* com.alibaba.nacos.config.server.service.ConfigOperationService.deleteConfig(..))";
      
      private static final String CONFIG_CHANGE_LISTEN_RPC =
              "execution(* com.alibaba.nacos.core.remote.RequestHandler.handleRequest(..)) "
                      + " && target(com.alibaba.nacos.config.server.remote.ConfigChangeBatchListenRequestHandler) && args(request,meta)";
      
      /**
       * Intercepts configuration publishing operations, records metrics, and logs client requests.
       */
      @Around(PUBLISH_CONFIG)
      public Object interfacePublishConfig(ProceedingJoinPoint pjp) throws Throwable {
          Object[] args = pjp.getArgs();
          ConfigForm configForm = (ConfigForm) args[0];
          ConfigRequestInfo configRequestInfo = (ConfigRequestInfo) args[1];
          String dataId = configForm.getDataId();
          String group = configForm.getGroup();
          String namespaceId = configForm.getNamespaceId();
          String content = configForm.getContent();
          String requestIp = configRequestInfo.getSrcIp();
          String md5 = content == null ? null : MD5Utils.md5Hex(content, Constants.ENCODE);
          
          MetricsMonitor.getPublishMonitor().incrementAndGet();
          AtomicLong rtHolder = new AtomicLong();
          Object retVal = logClientRequest("publish", pjp, dataId, group, namespaceId, requestIp, md5, rtHolder);
          MetricsMonitor.getWriteConfigRtTimer().record(rtHolder.get(), TimeUnit.MILLISECONDS);
          
          return retVal;
      }
      
      /**
       * Intercepts configuration get operations, records metrics, and logs client requests.
       */
      @Around(GET_CONFIG)
      public Object interfaceGetConfig(ProceedingJoinPoint pjp) throws Throwable {
          Object[] args = pjp.getArgs();
          ConfigQueryChainRequest chainRequest = (ConfigQueryChainRequest) args[0];
          String dataId = chainRequest.getDataId();
          String group = chainRequest.getGroup();
          String tenant = chainRequest.getTenant();
          String requestIp = null;
          if (chainRequest.getAppLabels() != null) {
              requestIp = chainRequest.getAppLabels().getOrDefault(BetaGrayRule.CLIENT_IP_LABEL, null);
          }
          String groupKey = GroupKey2.getKey(dataId, group, tenant);
          String md5 = ConfigCacheService.getContentMd5(groupKey);
          
          MetricsMonitor.getConfigMonitor().incrementAndGet();
          AtomicLong rtHolder = new AtomicLong();
          Object retVal = logClientRequest("get", pjp, dataId, group, tenant, requestIp, md5, rtHolder);
          MetricsMonitor.getReadConfigRtTimer().record(rtHolder.get(), TimeUnit.MILLISECONDS);
          
          return retVal;
      }
      
      /**
       * Deletes a configuration entry and logs the operation.
       */
      @Around(DELETE_CONFIG)
      public Object interfaceRemoveConfig(ProceedingJoinPoint pjp) throws Throwable {
          Object[] args = pjp.getArgs();
          String dataId = (String) args[0];
          String group = (String) args[1];
          String tenant = (String) args[2];
          String clientIp = (String) args[4];
          String groupKey = GroupKey2.getKey(dataId, group, tenant);
          String md5 = ConfigCacheService.getContentMd5(groupKey);
          
          MetricsMonitor.getConfigMonitor().incrementAndGet();
          AtomicLong rtHolder = new AtomicLong();
          Object retVal = logClientRequest("delete", pjp, dataId, group, tenant, clientIp, md5, rtHolder);
          MetricsMonitor.getReadConfigRtTimer().record(rtHolder.get(), TimeUnit.MILLISECONDS);
          
          return retVal;
      }
      
      /**
       * Client api request log rt | status | requestIp | opType | dataId | group | datumId | md5.
       */
      private Object logClientRequest(String requestType, ProceedingJoinPoint pjp, String dataId, String group,
              String tenant, String requestIp, String md5, AtomicLong rtHolder) throws Throwable {
          long startTime = System.currentTimeMillis();
          try {
              Object retVal = pjp.proceed();
              
              long rt = System.currentTimeMillis() - startTime;
              if (rtHolder != null) {
                  rtHolder.set(rt);
              }
              
              LogUtil.CLIENT_LOG.info(
                      "opType: {} | rt: {}ms | status: success | requestIp: {} | dataId: {} | group: {} | tenant: {} | md5: {}",
                      requestType, rt, requestIp, dataId, group, tenant, md5);
              
              return retVal;
              
          } catch (Throwable e) {
              long rt = System.currentTimeMillis() - startTime;
              if (rtHolder != null) {
                  rtHolder.set(rt);
              }
              
              LogUtil.CLIENT_LOG.error(
                      "opType: {} | rt: {}ms | status: failure | requestIp: {} | dataId: {} | group: {} | tenant: {} | md5: {}",
                      requestType, rt, requestIp, dataId, group, tenant, md5);
              
              throw e;
          }
      }
      
      /**
       * Handles configuration change listening requests.
       */
      @Around(CONFIG_CHANGE_LISTEN_RPC)
      public Object interfaceListenConfigRpc(ProceedingJoinPoint pjp, ConfigBatchListenRequest request, RequestMeta meta)
              throws Throwable {
          MetricsMonitor.getConfigMonitor().incrementAndGet();
          final String requestIp = meta.getClientIp();
          String appName = request.getHeader(RequestUtil.CLIENT_APPNAME_HEADER);
          final long st = System.currentTimeMillis();
          Response retVal = (Response) pjp.proceed();
          final long rt = System.currentTimeMillis() - st;
          LogUtil.CLIENT_LOG.info(
                  "opType: {} | rt: {}ms | status: {} | requestIp: {} | listenSize: {} | listenOrCancel: {} | appName: {}",
                  "listen", rt, retVal.isSuccess() ? retVal.getResultCode() : retVal.getErrorCode(), requestIp,
                  request.getConfigListenContexts().size(), request.isListen(), appName);
          return retVal;
      }
  }
  