我的编程空间,编程开发者的网络收藏夹
学习永远不晚

SpringBoot使用AOP记录接口操作日志的方法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

SpringBoot使用AOP记录接口操作日志的方法

**前言:**我们项目中可能有这种需求,每个人请求了哪些接口?做了什么事情?参数是什么?重要的接口我们需要记录操作日志以便查找。操作日志和系统日志不一样,操作日志必须要做到简单易懂。所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内容易于理解,如何让操作日志的接入更加简单?我们不可能在每个接口中去一一处理,可以借助Spring提供的AOP能力+自定义注解轻松应对。

一、操作日志简介

日志记录量是很大的,所以只记录关键地方并按期归档,最好是存在如elasticsearch中;如果存在数据库中,分表是不错的选择。

1.1、系统日志和操作日志的区别

系统日志:系统日志主要是为开发排查问题提供依据,一般打印在日志文件中;系统日志的可读性要求没那么高,日志中会包含代码的信息,比如在某个类的某一行打印了一个日志。

操作日志:主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。再比如,客服对工单的处理记录信息。

操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。

比如LogUtil.log(orderNo,“订单创建”,“小明”) :这里解释下为什么记录操作日志的时候都绑定了一个 OrderNo,因为操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。当查询业务的操作日志的时候,会查询针对这个订单的的所有操作,所以代码中加上了 OrderNo,记录操作日志的时候需要记录下操作人,所以传了操作人“小明”进来。

操作日志的记录格式大概分为下面几种:

  • 单纯的文字记录,比如:2021-09-16 10:00 订单创建。
  • 简单的动态的文本记录,比如:2021-09-16 10:00 订单创建,订单号:NO.11089999,其中涉及变量订单号“NO.11089999”。
  • 修改类型的文本,包含修改前和修改后的值,比如:2021-09-16 10:00 用户小明修改了订单的配送地址:从“金灿灿小区”修改到“银盏盏小区” ,其中涉及变量配送的原地址“金灿灿小区”和新地址“银盏盏小区”。
  • 修改表单,一次会修改多个字段。

1.2、操作日志记录实现方式

(1)使用 Canal 监听数据库记录操作日志

Canal是阿里开源的一款基于 MySQL 数据库增量日志解析中间件,提供增量数据订阅和消费的开源组件,通过采用监听数据库 Binlog 的方式,这样可以从底层知道是哪些数据做了修改,然后根据更改的数据记录操作日志。

这种方式的优点是和业务逻辑完全分离。缺点也很明显,局限性太高,只能针对数据库的更改做操作日志记录,如果修改涉及到其他团队的 RPC 的调用,就没办法监听数据库了。举个例子:给用户发送通知,通知服务一般都是公司内部的公共组件,这时候只能在调用 RPC 的时候手工记录发送通知的操作日志了。

(2)高度代码耦合:在业务逻辑中直接调用日志记录接口

通过日志文件的方式记录,这样就可以把日志单独保存在一个文件中,然后通过日志收集可以把日志保存在 Elasticsearch或者数据库中,接下来我们看下如何生成可读的操作日志。

//操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。
log.info("订单创建")
log.info("订单已经创建,订单编号:{}",?orderNo)
log.info("修改了订单的配送地址:从“{}”修改到“{}”,?"金灿灿小区",?"银盏盏小区")

但是当业务变得复杂后,记录操作日志放在业务代码中会导致业务的逻辑比较繁杂。

(3)采用AOP方式:AOP方式能和业务逻辑解耦

为了解决上面问题,一般采用 AOP 的方式记录日志,让操作日志和业务逻辑解耦,接下来看一个简单的 AOP 日志的例子。

@LogRecord(content="修改了配送地址")
public void modifyAddress(updateDeliveryRequest request){
//?更新派送信息?电话,收件人、地址
doUpdate(request);
}

我们可以在注解的操作日志上记录固定文案,这样业务逻辑和业务代码可以做到解耦,让我们的业务代码变得纯净起来。可能有同学注意到,上面的方式虽然解耦了操作日志的代码,但是记录的文案并不符合我们的预期,文案是静态的,没有包含动态的文案,因为我们需要记录的操作日志是:用户%s修改了订单的配送地址,从“%s”修改到“%s”。接下来,我们介绍一下如何优雅地使用 AOP 生成动态的操作日志。

二、AOP面向切面编程

2.1、AOP简介

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。**这种在运行时,动态地将代码切入到类的指定方法或指定位置上的编程思想就是面向切面的编程。**利用AOP可以将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来作为公共部分,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2.2、AOP作用

日志记录,性能统计,安全控制,事务处理,异常处理等等。

在面向切面编程AOP的思想里面,核心业务和切面通用功能(例如事务处理、日志管理、权限控制等)分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。这种思想有利于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

2.3、AOP相关术语

通知(Advice)

通知描述了切面要完成的工作以及何时执行。比如我们的日志切面需要记录每个接口调用时长,就需要在接口调用前后分别记录当前时间,再取差值。

  • 前置通知(Before):在目标方法调用前调用通知功能;
  • 后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;
  • 返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;
  • 异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;
  • 环绕通知(Around):通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。

切点(Pointcut)

切点定义了通知功能被应用的范围。比如日志切面的应用范围就是所有接口,即所有controller层的接口方法。

切面(Aspect)

切面是通知和切点的结合,定义了何时、何地应用通知功能。

引入(Introduction)

在无需修改现有类的情况下,向现有的类添加新方法或属性。

织入(Weaving)

把切面应用到目标对象并创建新的代理对象的过程。

连接点(JoinPoint)

通知功能被应用的时机。比如接口方法被调用的时候就是日志切面的连接点。

2.4、JointPoint和ProceedingJoinPoint

JointPoint是程序运行过程中可识别的连接点,这个点可以用来作为AOP切入点。JointPoint对象则包含了和切入相关的很多信息,比如切入点的方法,参数、注解、对象和属性等。我们可以通过反射的方式获取这些点的状态和信息,用于追踪tracing和记录logging应用信息。

(1)JointPoint
通过JpointPoint对象可以获取到下面信息

# 返回目标对象,即被代理的对象
Object getTarget();

# 返回切入点的参数
Object[] getArgs();

# 返回切入点的Signature
Signature getSignature();

# 返回切入的类型,比如method-call,field-get等等,感觉不重要?
String getKind();

(2)ProceedingJoinPoint

Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的。

暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。

ProceedingJoinPoint可以获取切入点的信息:

  • 切入点的方法名字及其参数
  • 切入点方法标注的注解对象(通过该对象可以获取注解信息)
  • 切入点目标对象(可以通过反射获取对象的类名,属性和方法名)
//获取切入点方法的名字,getSignature());是获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
String methodName = joinPoint.getSignature().getName()

//获取方法的参数,这里返回的是切入点方法的参数列表
Object[] args = joinPoint.getArgs();

//获取方法上的注解
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
xxxxxx annoObj= method.getAnnotation(xxxxxx.class);
}

//获取切入点所在目标对象
Object targetObj =joinPoint.getTarget();
//可以发挥反射的功能获取关于类的任何信息,例如获取类名如下
String className = joinPoint.getTarget().getClass().getName();

2.5、AOP相关注解

Spring中使用注解创建切面

  • @Aspect:用于定义切面
  • @Before:通知方法会在目标方法调用之前执行
  • @After:通知方法会在目标方法返回或抛出异常后执行
  • @AfterReturning:通知方法会在目标方法返回后执行
  • @AfterThrowing:通知方法会在目标方法抛出异常后执行
  • @Around:通知方法会将目标方法封装起来
  • @Pointcut:定义切点表达式

切点表达式:指定了通知被应用的范围,表达式格式:

execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数)
//com.hs.demo.controller包中所有类的public方法都应用切面里的通知
execution(public * com.hs.demo.controller..(…))
//com.hs.demo.service包及其子包下所有类中的所有方法都应用切面里的通知
execution(* com.hs.demo.service….(…))
//com.hs.demo.service.EmployeeService类中的所有方法都应用切面里的通知
execution(* com.hs.demo.service.EmployeeService.*(…))

(1)@POINTCUT定义切入点,有以下2种方式:

方式一:设置为注解@LogFilter标记的方法,有标记注解的方法触发该AOP,没有标记就没有。

@Aspect
@Component
public class LogFilter1Aspect {
	@Pointcut(value = "@annotation(com.hs.aop.annotation.LogFilter)")
	public void pointCut(){
	
	}
}

自定义注解LogFilter:

@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogFilter1 {
}

对应的Controller方法如下,手动添加@LogFilter注解:

@RestController
public class AopController {
	@RequestMapping("/aop")
	@LogFilter
	public String aop(){
	    System.out.println("这是执行方法");
	    return "success";
	}
}

方式二:采用表达式批量添加切入点,如下方法,表示AopController下的所有public方法都添加LogFilter1切面。

@Pointcut(value = "execution(public * com.train.aop.controller.AopController.*(..))")
public void pointCut(){

}

(2)@Around环绕通知

@Around集成了@Before、@AfterReturing、@AfterThrowing、@After四大通知。需要注意的是,他和其他四大通知注解最大的不同是需要手动进行接口内方法的反射后才能执行接口中的方法,换言之,@Around其实就是一个动态代理。

   
	public void aroundPringLog(ProceedingJoinPoint pjp)
   {
      //拿到目标方法的方法签名
       Signature signature = pjp.getSignature();
      //获取方法名
      String name = signature.getName();

	  try {
			//@Before
			System.out.println("【环绕前置通知】【"+name+"方法开始】");
            //这句相当于method.invoke(obj,args),通过反射来执行接口中的方法
			proceed = pjp.proceed();
			//@AfterReturning
			System.out.println("【环绕返回通知】【"+name+"方法返回,返回值:"+proceed+"】");
		} catch (Exception e) {
			//@AfterThrowing
			System.out.println("【环绕异常通知】【"+name+"方法异常,异常信息:"+e+"】");
		}
        finally{
			//@After
			System.out.println("【环绕后置通知】【"+name+"方法结束】");
		}
	}

proceed = pjp.proceed(args)这条语句其实就是method.invoke,以前手写版的动态代理,也是method.invoke执行了,jdk才会利用反射进行动态代理的操作,在Spring的环绕通知里面,只有这条语句执行了,spring才会去切入到目标方法中。

为什么说环绕通知就是一个动态代理呢?

proceed = pjp.proceed(args)这条语句就是动态代理的开始,当我们把这条语句用try-catch包围起来的时候,在这条语句前面写的信息,就相当于前置通知,在它后面写的就相当于返回通知,在catch里面写的就相当于异常通知,在finally里写的就相当于后置通知。

三、AOP切面实现接口日志记录

通过AOP面向切面编程技术,实现操作日志记录功能以便进行信息监控和信息统计,不侵入业务代码逻辑,提高代码的可重用性。

3.1、引入AOP依赖

<dependency>
<groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.2、创建日志信息封装类WebLog

用于封装需要记录的日志信息,包括操作的描述、时间、消耗时间、url、请求参数和返回结果等信息。

@Data
public class WebLog {
    
    private String description;

    
    private String username;

    
    private Long startTime;

    
    private Integer spendTime;

    
    private String basePath;

    
    private String uri;

    
    private String url;

    
    private String method;

    
    private String ip;

    
    private Object parameter;

    
    private Object result;

}

3.3、创建切面类WebLogAspect

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 

@Aspect
@Component
@Order(1)
@Slf4j
public class WebLogAspect {
 
    //定义切点表达式,指定通知功能被应用的范围
    @Pointcut("execution(public * com.hs.notice.controller.*.*(..))")
    public void webLog() {
    }
 
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }
 
         
    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }
 
    //通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为
    //ProceedingJoinPoint切入点可以获取切入点方法上的名字、参数、注解和对象
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable 
    {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息
        WebLog webLog = new WebLog();

        //前面是前置通知,后面是后置通知
        Object result = joinPoint.proceed();

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        long endTime = System.currentTimeMillis();
        String urlStr = request.getRequestURL().toString();
        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        webLog.setIp(request.getRemoteUser());
        webLog.setMethod(request.getMethod());
        webLog.setParameter(getParameter(method, joinPoint.getArgs()));
        webLog.setResult(result);
        webLog.setSpendTime((int) (endTime - startTime));
        webLog.setStartTime(startTime);
        webLog.setUri(request.getRequestURI());
        webLog.setUrl(request.getRequestURL().toString());
        log.info("{}", JSONUtil.parse(webLog));
        return result;
    }
 
    
    private Object getParameter(Method method, Object[] args) 
   {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
}

3.4、调用接口进行测试

随便访问一个com.hs.notice.controller包下的接口,可以看到WebLogAspect输出的日志信息:

2022-01-07 10:43:33.732  INFO 11400 --- [nio-8086-exec-7] com.cernet.notice.util.WebLogAspect: 
{"result":{"code":200,"data":{"appName":"Tiktok","isSend":1,"id":1,"sendAT":1640596730378,
"tilte":"【测试数据】","receiverEmail":"110@qq.com","content":"测试"},"message":"请求成功"},
"basePath":"http://localhost:8086","method":"GET","startTime":1641523413722,
"uri":"/api/email-notices/1","url":"http://localhost:8086/api/email-notices/1","spendTime":6}

四、AOP切面+自定义注解实现接口日志记录

4.1、自定义日志注解

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperatorLog 
{
 // 操作
 String operate();

 // 模块
 String module();
}

4.2、定义拦截操作日志的切面

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import com.elon.springbootdemo.manager.OperatorLog;
import com.elon.springbootdemo.model.ResponseModel;


@Aspect
@Component
public class OperatorLogApsect {
    
    private static Logger logger = LogManager.getLogger(OperatorLogApsect.class);
    
    @Pointcut("@annotation(com.hs.springbootdemo.aop.OperatorLog)")
    public void operatorLog() {
        
    }
    
    @SuppressWarnings("rawtypes")
    @AfterReturning(returning="result", pointcut="operatorLog()&&@annotation(log)")
    public void afterReturn(JoinPoint joinPoint, ResponseModel result, OperatorLog log) {
        
        
        StringBuilder sb = new StringBuilder();
        sb.append("模块:").append(log.module());
        sb.append("|操作:").append(log.operate());
        sb.append("|接口名称:").append(joinPoint.getSignature().getName());
        sb.append("|错误码:").append(result.getRetCode());
        sb.append("|错误信息:").append(result.getErrorMsg());
        logger.info(sb.toString());
    }
}

4.3、在接口上增加操作日志注解

@RequestMapping(value="/v1/query-user", method=RequestMethod.GET)
@OperatorLog(operate="查询用户", module="用户管理")
public ResponseModel queryUser(@RequestParam(value="user", required=true) String user)?
{
log.info("[INFO]user info:" + user);
log.warn("[WARN]user info:" + user);
log.error("[ERROR]user info:" + user);
return ResponseModel.success(null);
}

4.4、调用接口,可以看到输出的日志信息

在这里插入图片描述

参考链接:

美团技术团队—如何优雅地记录操作日志?

SpringAOP中的ProceedingJoinPoint使用,配合注解的方式

使用(Annotation)自定义注解做日志记录

Spring 过滤器 拦截器 AOP区别

SpringBoot 拦截器解析

使用拦截器(intercept)和AOP写操作日志-springboot

到此这篇关于SpringBoot使用AOP记录接口操作日志的文章就介绍到这了,更多相关SpringBoot AOP记录接口内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

SpringBoot使用AOP记录接口操作日志的方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

SpringBoot使用AOP记录接口操作日志的方法

日志记录量是很大的,所以只记录关键地方并按期归档,最好是存在如elasticsearch中,如果存在数据库中,分表是不错的选择,这篇文章主要介绍了SpringBoot使用AOP记录接口操作日志的方法,需要的朋友可以参考下
2022-11-13

利用spring AOP记录用户操作日志的方法示例

前言最近项目已经开发完成,但发现需要加用户操作日志,如果返回去加也不太现实,所以使用springAOP来完成比较合适。下面来一起看看详细的介绍:注解工具类:@Retention(RetentionPolicy.RUNTIME)@Target
2023-05-31

Django记录操作日志与LogEntry的使用方法是什么

这篇文章主要介绍“Django记录操作日志与LogEntry的使用方法是什么”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Django记录操作日志与LogEntry的使用方法是什么”文章能帮助大家解
2023-06-26

springboot项目如何使用切面记录用户操作日志

这篇文章主要介绍了springboot项目如何使用切面记录用户操作日志,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2022-11-13

怎么使用Aop的方式实现自动日志记录

本篇内容介绍了“怎么使用Aop的方式实现自动日志记录”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!使用Aop的方式实现自动日志记录自动日志记
2023-06-30

MySQL 使用触发器记录用户的操作日志问题

使用 MySQL 触发器可以记录哪些用户、什么时间对数据表进行了增、删、改操作。如果执行删除操作,则记录删除之前的数据记录;如果执行更新操作,记录更新之前的数据记录,这篇文章主要介绍了MySQL 使用触发器记录用户的操作日志,需要的朋友可以参考下
2022-12-08

MySQL 使用触发器记录用户的操作日志问题

目录mysql 使用触发器记录用户的操作日志一、创建用户数据表(emp)和保存操作日志的表(emp_log)二、为 emp 表创建触发器1、创建触发器 trigger_after_insert_emp2、创建触发器 trigger_afte
2022-12-05

如何使用PHP进行日志记录?(PHP实现日志记录的方法有哪些?)

日志记录是应用程序开发中用于记录事件、错误和调试信息的至关重要的技术。PHP提供了多种日志记录方法,每种方法都有其独特的优势和劣势。文件日志记录:简单易用,但缺乏格式化和中央日志管理。数据库日志记录:提供高级格式化和查询功能,但需要数据库服务器。Syslog日志记录:将日志信息发送到系统日志守护程序,可以将日志信息集中到一个位置。第三方库:使用第三方库简化日志记录过程,提供丰富的功能,如格式化、过滤和远程日志记录。选择最合适的日志记录方法取决于应用程序的需求。对于简单的情况,文件日志记录可能足够;对于需要
如何使用PHP进行日志记录?(PHP实现日志记录的方法有哪些?)
2024-04-02

使用Spring MVC拦截器实现日志记录的方法

最近在研究Spring MVC拦截器,那么今天也算个学习笔记吧!有需要了解使用Spring MVC拦截器实现日志记录的朋友可参考。希望此文章对各位有所帮助。1. 定义一个类实现HandlerInterceptor,比如: public
2023-05-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录