精华内容
下载资源
问答
  • 如何实现操作操作日志记录

    万次阅读 多人点赞 2018-12-26 11:16:45
    如何实现操作操作日志记录 为什么要记录操作日志? 项目中的业务需求,需要针对用户的一些业务操作做操作记录, 也就是标题中的操场日志记录,最近做的项目也有这个需求, 我也是第一次写,相信有很多开发者也有遇到这个...

    如何实现操作操作日志记录

    为什么要记录操作日志?

    项目中的业务需求,需要针对用户的一些业务操作做操作记录,
    也就是标题中的操场日志记录,最近做的项目也有这个需求,
    我也是第一次写,相信有很多开发者也有遇到这个需求的,所以
    在这里做一个简单的记录,只是提供一个思路参考,代码什么的
    其实是次要的!
    

    业务需求如下,记录用户的重要操作,记录除查询外,如增加,修改,和删除等操作

    在这里插入图片描述

    实现思路

    首先我肯定是用aop了,在后面的使用发现,apo的实现适合大部分
    的单表操作,但是多表更改,例如先加后改是没法实现的,所以我决定
    提供两种实现方式,另外一种使用service函数调用来解决了
    

    表设计

    CREATE TABLE operation_log (
    `id` INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `name` VARCHAR(128) NULL DEFAULT NULL COMMENT '操作业务名',
    `table_name` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作表名',
    `table_id` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作表id',
    `type` VARCHAR(8) NULL DEFAULT NULL COMMENT '操作类型,(添加ADD,删除DELETE,修改UPDATE)' ,
    `operator_id` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作人id',
    `operator_name` VARCHAR(16) NULL DEFAULT NULL COMMENT '操作人名',
    `operation_time` TIMESTAMP NULL DEFAULT NULL COMMENT '操作时间'
    )ENGINE INNODB CHARSET utf8 COMMENT '用户操作日志记录表';
    	
    CREATE TABLE operation_log_detail (
    `id` INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT '主键id',
    `operation_log_id` INT(10) NULL DEFAULT NULL COMMENT '操作日志id',
    `clm_name` VARCHAR(16) NULL DEFAULT NULL COMMENT '字段名',
    `clm_comment` VARCHAR(128) NULL DEFAULT NULL COMMENT '字段描述',
    `old_string` VARCHAR(128) NULL DEFAULT NULL COMMENT '旧值',
    `new_string` VARCHAR(128) NULL DEFAULT NULL COMMENT '新值'
    )ENGINE INNODB CHARSET utf8 COMMENT '操作日志详情表';
    

    AOP实现

    0目标: 在业务代码函数上使用注解,通过注解实现执行时的环形切面,在切面前,切面后,做数据的变更记录操作
    开始:
    1创建注解
    package com.csp.operationlog.aspect.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import com.csp.operationlog.aspect.enums.OperationType;
    
    /**
     * 用来标注需要进行操作日志的服务函数上
     * @author taoken
     */
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface OperationLog {
    	/** 业务名 */
    	String name();
    	/** 表名 */
    	String table();
    	/** id 在函数的字段名 */
    	int idRef() default -1; 
    	/** 需要记录的字段 */
    	String[] cloum() default {};
    	/** 操作类型 */
    	OperationType type();
    	/** 操作人 id 在函数的字段名*/
    	int operatorIdRef();
    	/** 操作人名称 在函数的字段名 */
    	int operatorNameRef();
    }
    由于使用了一个枚举下面提供一个枚举,作用是分辨操作类型
    package com.csp.operationlog.aspect.enums;
    public enum OperationType {
    	ADD,
    	UPDATE,
    	DELETE;
    
    	public String getType() {
    		if (this.equals(ADD)) {
    			return "ADD";
    		}
    		if (this.equals(UPDATE)) {
    			return "UPDATE";
    		}
    		if (this.equals(DELETE)) {
    			return "DELETE";
    		}
    		return null;
    	};
    }
    2使用注解,只是提前看看使用效果
        @OperationLog(name = "更新账户",type = OperationType.UPDATE,operatorIdRef = 0,operatorNameRef = 1,idRef = 2,table = "account")
        public void updateAccount(String operatorId,String operatorName,Integer accountId){
            Account account = new Account();
            account.setId(accountId);
            account.setAccount(1100);
            accountMapper.updateAccount(account);
        }
    3下面开始实现切面
    package com.csp.operationlog.aspect.aop;
    
    import java.sql.Timestamp;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.TransactionCallbackWithoutResult;
    import org.springframework.transaction.support.TransactionTemplate;
    
    import com.csp.operationlog.aspect.enums.OperationType;
    import com.csp.operationlog.dto.ColumnComment;
    import com.csp.operationlog.mapper.OperationLogDetailMapper;
    import com.csp.operationlog.mapper.OperationLogMapper;
    import com.csp.operationlog.model.OperationLog;
    import com.csp.operationlog.model.OperationLogDetail;
    
    @Aspect
    @Component
    public class OperationLogAop {
    	@Autowired
    	private OperationLogMapper operationLogMapper;
    	@Autowired
    	private OperationLogDetailMapper operationLogDetailMapper;
    	@Autowired
    	private TransactionTemplate txTemplate;
    
    	@Around(value = "@annotation(operationlog)")
    	public void logAround(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) throws Throwable {
    		OperationType type = operationlog.type();
    		if (OperationType.UPDATE.equals(type)) {
    			update(p, operationlog);
    		}
    		if (OperationType.ADD.equals(type)) {
    			add(p, operationlog);
    		}
    		if (OperationType.DELETE.equals(type)) {
    			delete(p, operationlog);
    		}
    	}
    	
    	public void delete(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) {
    		txTemplate.execute(new TransactionCallbackWithoutResult() {
    			@Override
    			protected void doInTransactionWithoutResult(TransactionStatus status) {
    				StringBuilder sql = new StringBuilder();
    				OperationType type = operationlog.type();
    				Object[] args = p.getArgs();
    				String logName = operationlog.name();
    				String logTable = operationlog.table();
    				if (operationlog.idRef()==-1) {
    					throw new RuntimeException();
    				}
    				String id = args[operationlog.idRef()].toString();
    				String[] cloum = operationlog.cloum();
    				String operatorId = args[operationlog.operatorIdRef()].toString();
    				String operatorName = args[operationlog.operatorNameRef()].toString();
    
    				Map<String, Object> columnCommentMap = new HashMap<String, Object>();
    				List<ColumnComment> columnCommentList = operationLogMapper.selectColumnCommentByTable(logTable);
    
    				for (ColumnComment cc : columnCommentList) {
    					columnCommentMap.put(cc.getColumn(), cc.getComment());
    				}
    				if (cloum.length == 0) {
    					Set<String> keySet = columnCommentMap.keySet();
    					List<String> list = new ArrayList<String>();
    					for (String o : keySet) {
    						list.add(o.toString());
    					}
    					cloum = list.toArray(new String[list.size()]);
    				}
    				sql.append("SELECT ");
    				for (int i = 0; i < cloum.length; i++) {
    					if (i == 0) {
    						sql.append("`" + cloum[i] + "` ");
    					} else {
    						sql.append(",`" + cloum[i] + "` ");
    					}
    				}
    				sql.append(" FROM " + logTable + " WHERE id=" + id);
    				Map<String, Object> oldMap = operationLogMapper.selectAnyTalbe(sql.toString());
    
    				try {
    					p.proceed();
    				} catch (Throwable e) {
    					throw new RuntimeException(e);
    				}
    
    				if (oldMap!=null) {
    					OperationLog op = new OperationLog();
    					op.setName(logName);
    					op.setTableName(logTable);
    					op.setTableId(id);
    					op.setType(type.getType());
    					op.setOperatorId(operatorId);
    					op.setOperatorName(operatorName);
    					op.setOperationTime(new Timestamp(System.currentTimeMillis()));
    					operationLogMapper.insertOperationLog(op);
    					List<OperationLogDetail> opds = new ArrayList<OperationLogDetail>();
    					for (String clm : cloum) {
    						Object oldclm = oldMap.get(clm);
    						OperationLogDetail opd = new OperationLogDetail();
    						opd.setOldString(oldclm.toString());
    						opd.setNewString("");
    						opd.setClmName(clm);
    						opd.setClmComment(columnCommentMap.get(clm).toString());
    						opd.setOperationLogId(op.getId());
    						opds.add(opd);
    					}
    					if (!opds.isEmpty()) {
    						operationLogDetailMapper.insertOperationLogDetail(opds);
    					}
    				}
    			}
    		});
    	}
    
    	private void add(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) {
    		txTemplate.execute(new TransactionCallbackWithoutResult() {
    			@Override
    			protected void doInTransactionWithoutResult(TransactionStatus status) {
    				StringBuilder sql = new StringBuilder();
    				OperationType type = operationlog.type();
    				Object[] args = p.getArgs();
    				String logName = operationlog.name();
    				String logTable = operationlog.table();
    				String[] cloum = operationlog.cloum();
    				String operatorId = args[operationlog.operatorIdRef()].toString();
    				String operatorName = args[operationlog.operatorNameRef()].toString();
    
    				Map<String, Object> columnCommentMap = new HashMap<String, Object>();
    				List<ColumnComment> columnCommentList = operationLogMapper.selectColumnCommentByTable(logTable);
    				
    				for (ColumnComment cc : columnCommentList) {
    					columnCommentMap.put(cc.getColumn(), cc.getComment());
    				}
    				if (cloum.length == 0) {
    					Set<String> keySet = columnCommentMap.keySet();
    					List<String> list = new ArrayList<String>();
    					for (String o : keySet) {
    						list.add(o.toString());
    					}
    					cloum = list.toArray(new String[list.size()]);
    				}
    				sql.append("SELECT ");
    				for (int i = 0; i < cloum.length; i++) {
    					if (i == 0) {
    						sql.append("`" + cloum[i] + "` ");
    					} else {
    						sql.append(",`" + cloum[i] + "` ");
    					}
    				}
    				sql.append(" FROM " + logTable + " ORDER BY id DESC LIMIT 1");
    				Map<String, Object> oldMap = operationLogMapper.selectAnyTalbe(sql.toString());
    				try {
    					p.proceed();
    				} catch (Throwable e) {
    					throw new RuntimeException(e);
    				}
    				Map<String, Object> newMap = operationLogMapper.selectAnyTalbe(sql.toString());
    				if ((oldMap==null)||(!oldMap.get("id").toString().equals(newMap.get("id").toString()))) {
    					
    					OperationLog op = new OperationLog();
    					op.setName(logName);
    					op.setTableName(logTable);
    					op.setTableId("");
    					op.setType(type.getType());
    					op.setOperatorId(operatorId);
    					op.setOperatorName(operatorName);
    					op.setOperationTime(new Timestamp(System.currentTimeMillis()));
    					operationLogMapper.insertOperationLog(op);
    					List<OperationLogDetail> opds = new ArrayList<OperationLogDetail>();
    					for (String clm : cloum) {
    						Object oldclm = "";
    						Object newclm = newMap.get(clm);
    						OperationLogDetail opd = new OperationLogDetail();
    						opd.setOldString(oldclm.toString());
    						opd.setNewString(newclm.toString());
    						opd.setClmName(clm);
    						opd.setClmComment(columnCommentMap.get(clm).toString());
    						opd.setOperationLogId(op.getId());
    						opds.add(opd);
    					}
    					if (!opds.isEmpty()) {
    						operationLogDetailMapper.insertOperationLogDetail(opds);
    					}
    					
    				}
    			}
    		});
    	}
    
    	public void update(final ProceedingJoinPoint p,final com.csp.operationlog.aspect.annotation.OperationLog operationlog) {
    		txTemplate.execute(new TransactionCallbackWithoutResult() {
    			@Override
    			protected void doInTransactionWithoutResult(TransactionStatus status) {
    				StringBuilder sql = new StringBuilder();
    				OperationType type = operationlog.type();
    				Object[] args = p.getArgs();
    				String logName = operationlog.name();
    				String logTable = operationlog.table();
    				if (operationlog.idRef()==-1) {
    					throw new RuntimeException();
    				}
    				String id = args[operationlog.idRef()].toString();
    				String[] cloum = operationlog.cloum();
    				String operatorId = args[operationlog.operatorIdRef()].toString();
    				String operatorName = args[operationlog.operatorNameRef()].toString();
    
    				Map<String, Object> columnCommentMap = new HashMap<String, Object>();
    				List<ColumnComment> columnCommentList = operationLogMapper.selectColumnCommentByTable(logTable);
    				
    				for (ColumnComment cc : columnCommentList) {
    					columnCommentMap.put(cc.getColumn(), cc.getComment());
    				}
    				if (cloum.length == 0) {
    					Set<String> keySet = columnCommentMap.keySet();
    					List<String> list = new ArrayList<String>();
    					for (String o : keySet) {
    						list.add(o.toString());
    					}
    					cloum = list.toArray(new String[list.size()]);
    				}
    				sql.append("SELECT ");
    				for (int i = 0; i < cloum.length; i++) {
    					if (i == 0) {
    						sql.append("`" + cloum[i] + "` ");
    					} else {
    						sql.append(",`" + cloum[i] + "` ");
    					}
    				}
    				sql.append(" FROM " + logTable + " WHERE id=" + id);
    				Map<String, Object> oldMap = operationLogMapper.selectAnyTalbe(sql.toString());
    
    				try {
    					p.proceed();
    				} catch (Throwable e) {
    					throw new RuntimeException(e);
    				}
    
    				Map<String, Object> newMap = operationLogMapper.selectAnyTalbe(sql.toString());
    				if (oldMap!=null&&newMap!=null) {
    					OperationLog op = new OperationLog();
    					op.setName(logName);
    					op.setTableName(logTable);
    					op.setTableId(id);
    					op.setType(type.getType());
    					op.setOperatorId(operatorId);
    					op.setOperatorName(operatorName);
    					op.setOperationTime(new Timestamp(System.currentTimeMillis()));
    					operationLogMapper.insertOperationLog(op);
    					List<OperationLogDetail> opds = new ArrayList<OperationLogDetail>();
    					for (String clm : cloum) {
    						Object oldclm = oldMap.get(clm);
    						Object newclm = newMap.get(clm);
    						OperationLogDetail opd = new OperationLogDetail();
    						opd.setOldString(oldclm.toString());
    						opd.setNewString(newclm.toString());
    						opd.setClmName(clm);
    						opd.setClmComment(columnCommentMap.get(clm).toString());
    						opd.setOperationLogId(op.getId());
    						opds.add(opd);
    					}
    					if (!opds.isEmpty()) {
    						operationLogDetailMapper.insertOperationLogDetail(opds);
    					}
    				}
    			}
    		});
    	}
    }
    4 可以看出上面实现中用到了表对应的实体类,以及操作数据库的持久层mapper,还有一个数据对象
    我们提供一下,这里简单说明一下,我用的是mybatis,最后提供pom.xml
    package com.csp.operationlog.model;
    import java.sql.Timestamp;
    /**
     * 操作日志主信息模型
     * @author taoken
     */
    public class OperationLog {
    	/** 主键id */
    	private String id;
    	/** 操作业务名 */
    	private String name;
    	/** 操作表名 */
    	private String tableName;
    	/** 操作表id */
    	private String tableId;
    	/** 操作类型,(添加ADD,删除DELETE,修改UPDATE)' */
    	private String type;
    	/** 操作人id */
    	private String operatorId;
    	/** 操作人名 */
    	private String operatorName;
    	/** 操作时间 */
    	private Timestamp operationTime;
    	public String getId() {
    		return id;
    	}
    	public void setId(String id) {
    		this.id = id;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public String getTableName() {
    		return tableName;
    	}
    	public void setTableName(String tableName) {
    		this.tableName = tableName;
    	}
    	public String getTableId() {
    		return tableId;
    	}
    	public void setTableId(String tableId) {
    		this.tableId = tableId;
    	}
    	public String getType() {
    		return type;
    	}
    	public void setType(String type) {
    		this.type = type;
    	}
    	public String getOperatorId() {
    		return operatorId;
    	}
    	public void setOperatorId(String operatorId) {
    		this.operatorId = operatorId;
    	}
    	public String getOperatorName() {
    		return operatorName;
    	}
    	public void setOperatorName(String operatorName) {
    		this.operatorName = operatorName;
    	}
    	public Timestamp getOperationTime() {
    		return operationTime;
    	}
    	public void setOperationTime(Timestamp operationTime) {
    		this.operationTime = operationTime;
    	}
    }
    
    package com.csp.operationlog.model;
    /**
     * 操作日志详情模型
     * @author taoken
     */
    public class OperationLogDetail {
    	/** 主键id */
    	private String id;
    	/** 操作日志id */
    	private String operationLogId;
    	/** 字段名 */
    	private String clmName;
    	/** 字段描述 */
    	private String clmComment;
    	/** 旧值 */
    	private String oldString;
    	/** 新值 */
    	private String newString;
    	
    	public String getId() {
    		return id;
    	}
    	public void setId(String id) {
    		this.id = id;
    	}
    	public String getOperationLogId() {
    		return operationLogId;
    	}
    	public void setOperationLogId(String operationLogId) {
    		this.operationLogId = operationLogId;
    	}
    	public String getClmName() {
    		return clmName;
    	}
    	public void setClmName(String clmName) {
    		this.clmName = clmName;
    	}
    	public String getClmComment() {
    		return clmComment;
    	}
    	public void setClmComment(String clmComment) {
    		this.clmComment = clmComment;
    	}
    	public String getOldString() {
    		return oldString;
    	}
    	public void setOldString(String oldString) {
    		this.oldString = oldString;
    	}
    	public String getNewString() {
    		return newString;
    	}
    	public void setNewString(String newString) {
    		this.newString = newString;
    	}
    }
    
    package com.csp.operationlog.mapper;
    import java.util.List;
    import java.util.Map;
    import org.apache.ibatis.annotations.InsertProvider;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Param;
    import com.csp.operationlog.model.OperationLogDetail;
    /**
     * 操作日志详情持久层
     * @author taoken
     */
    @Mapper
    public interface OperationLogDetailMapper {
    	public static class OperationLogDetailMapperProvider{
    		public String insertOperationLogDetailSQL(Map<String,List<OperationLogDetail>> map) {
    			List<OperationLogDetail> ops = map.get("ops");
    			StringBuilder sqlBuid = new StringBuilder("INSERT INTO operation_log_detail (operation_log_id,clm_name,clm_comment,old_string,new_string) VALUES ");
    			for (int i = 0; i < ops.size(); i++) {
    				OperationLogDetail o = ops.get(i);
    				if (i==0) {
    					sqlBuid.append(" ('"+o.getOperationLogId()+"','"+o.getClmName()+"','"+o.getClmComment()+"','"+o.getOldString()+"','"+o.getNewString()+"') ");
    				}else {
    					sqlBuid.append(" ,('"+o.getOperationLogId()+"','"+o.getClmName()+"','"+o.getClmComment()+"','"+o.getOldString()+"','"+o.getNewString()+"') ");
    				}
    			}
    			return sqlBuid.toString();
    		}
    	}
    	//批量添加操作详情
    	@InsertProvider( type=OperationLogDetailMapperProvider.class, method="insertOperationLogDetailSQL" )
    	public void insertOperationLogDetail(@Param("ops")List<OperationLogDetail> operationLogDetails);
    }
    
    package com.csp.operationlog.mapper;
    import java.util.List;
    import java.util.Map;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    import org.apache.ibatis.annotations.Options;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.Select;
    import org.apache.ibatis.annotations.SelectProvider;
    import com.csp.operationlog.dto.ColumnComment;
    import com.csp.operationlog.model.OperationLog;
    
    /**
     * 操作日志持久层
     * @author taoken
     */
    @Mapper
    public interface OperationLogMapper {
    	public static class OperationLogMapperProvider{
    		public String selectAnyTalbeSQL(Map<String,String> map) {
    			return map.get("sql");
    		}
    	}
    	//添加操作日志
    	@Insert("INSERT INTO operation_log (name,table_name,table_id,type,operator_id,operator_name,operation_time) VALUES (#{p.name},#{p.tableName},#{p.tableId},#{p.type},#{p.operatorId},#{p.operatorName},#{p.operationTime});")
    	@Options(useGeneratedKeys=true,keyColumn="id",keyProperty="p.id")
    	public void insertOperationLog(@Param("p")OperationLog operationLog);
    	
    	//查询任意sql
    	@SelectProvider(type=OperationLogMapperProvider.class,method="selectAnyTalbeSQL")
    	public Map<String,Object> selectAnyTalbe(@Param("sql")String sql);
    	
    	//查询任意表的字段与备注
    	@Select("SELECT COLUMN_NAME `column`,column_comment `comment` FROM INFORMATION_SCHEMA.Columns WHERE table_name=#{table}")
    	public List<ColumnComment> selectColumnCommentByTable(@Param("table")String tableName);
    }
    
    package com.csp.operationlog.dto;
    public class ColumnComment {
    	private String column;
    	private String comment;
    	public String getColumn() {
    		return column;
    	}
    	public void setColumn(String column) {
    		this.column = column;
    	}
    	public String getComment() {
    		return comment;
    	}
    	public void setComment(String comment) {
    		this.comment = comment;
    	}
    }
    
    package com.csp.operationlog.util;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    public class HumpUtil {
    	public static final char UNDERLINE = '_';
    	/**
    	 * (userId:user_id)
    	 * @param param
    	 * @return
    	 */
    	public static String camelToUnderline(String param) {
    		if (param == null || "".equals(param.trim())) {
    			return "";
    		}
    		int len = param.length();
    		StringBuilder sb = new StringBuilder(len);
    		for (int i = 0; i < len; i++) {
    			char c = param.charAt(i);
    			if (Character.isUpperCase(c)) {
    				sb.append(UNDERLINE);
    				sb.append(Character.toLowerCase(c));
    			} else {
    				sb.append(c);
    			}
    		}
    		return sb.toString();
    	}
    
    	/**
    	 * (user_id:userId)
    	 * @param param
    	 * @return
    	 */
    	public static String underlineToCamel(String param) {
    		if (param == null || "".equals(param.trim())) {
    			return "";
    		}
    		StringBuilder sb = new StringBuilder(param);
    		Matcher mc = Pattern.compile(UNDERLINE + "").matcher(param);
    		int i = 0;
    		while (mc.find()) {
    			int position = mc.end() - (i++);
    			String.valueOf(Character.toUpperCase(sb.charAt(position)));
    			sb.replace(position - 1, position + 1,
    					sb.substring(position, position + 1).toUpperCase());
    		}
    		return sb.toString();
    	}
    }
    
    package com.csp.operationlog.util;
    import java.beans.PropertyDescriptor;
    import java.util.HashMap;
    import java.util.Map;
    import org.springframework.beans.BeanWrapper;
    import org.springframework.beans.BeanWrapperImpl;
    public class ToMapUtil {
    	@SuppressWarnings({ "unchecked"})
    	public static <T> Map<String, Object> toMap(T bean) {
    		if (bean instanceof Map) {
    			return (Map<String, Object>)bean;
    		}
    		BeanWrapper beanWrapper = new BeanWrapperImpl(bean);
    		Map<String, Object> map = new HashMap<String, Object>();
    		PropertyDescriptor[] pds = beanWrapper.getPropertyDescriptors();
    		for (PropertyDescriptor pd : pds) {
    			if (!"class".equals(pd.getName())) {
    				map.put(pd.getName(),
    						beanWrapper.getPropertyValue(pd.getName()));
    			}
    		}
    		return map;
    	}
    }
    
    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    
    	<groupId>com.csp.service</groupId>
    	<artifactId>service-operationlog</artifactId>
    	<version>1.0</version>
    	<packaging>jar</packaging>
    
    	<name>service-operationlog</name>
    	<description>service-operationlog project for Spring Boot</description>
    	
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.5.9.RELEASE</version>
    		<relativePath />
    	</parent>
    
    	<dependencies>
    		<!-- aop -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-aop</artifactId>
    		</dependency>
    		<!-- mybatis -->
    		<dependency>
    			<groupId>org.mybatis.spring.boot</groupId>
    			<artifactId>mybatis-spring-boot-starter</artifactId>
    			<version>1.3.2</version>
    		</dependency>
    		<!-- mysql -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    		</dependency>
    		<!--long3-->
    		<dependency>
    			<groupId>org.apache.commons</groupId>
    			<artifactId>commons-lang3</artifactId>
    			<version>3.3.2</version>
    		</dependency>
    	</dependencies>
    </project>
    

    开始测试

    创建springboot的测试demo项目

    1启动相关配置与启动类,这里模拟我们的真实项目
    pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>1.5.9.RELEASE</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.example</groupId>
    	<artifactId>demo</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>demo</name>
    	<description>Demo project for Spring Boot</description>
    
    	<properties>
    		<java.version>1.8</java.version>
    	</properties>
    
    	<dependencies>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    		<dependency>
    			<groupId>com.csp.service</groupId>
    			<artifactId>service-operationlog</artifactId>
    			<version>1.0</version>
    		</dependency>
    		<!-- mybatis -->
    		<dependency>
    			<groupId>org.mybatis.spring.boot</groupId>
    			<artifactId>mybatis-spring-boot-starter</artifactId>
    			<version>1.3.2</version>
    		</dependency>
    		<!-- mysql -->
    		<dependency>
    			<groupId>mysql</groupId>
    			<artifactId>mysql-connector-java</artifactId>
    		</dependency>
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    </project>
    
    package com.example.demo;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.ComponentScan;
    @SpringBootApplication
    @ComponentScan(basePackages = {"com.csp.**","com.example.**"})//这里是项目对SpringBean注入的扫描,前面是对operationlog项目中bean的扫描,后面是demo项目的bean的扫描
    @MapperScan({"com.csp.operationlog.mapper","com.example.demo.**.mapper"})//这里com.csp.**是扫描我的operationlog项目的mapper,而com.example.**扫描的是我的demo项目的mapper
    public class DemoApplication {
    	public static void main(String[] args) {
    		SpringApplication.run(DemoApplication.class, args);
    	}
    }
    
    2 我们使用一个账户表,用来测试操作账户,看看是否能够实现日志记录
    表创建:
    CREATE TABLE `account` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',
      `account` int(10) DEFAULT NULL COMMENT '账户',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
    
    2 创建对应实体与mapper
    package com.example.demo.domain;
    import java.io.Serializable;
    public class Account implements Cloneable, Serializable {
    	private static final long serialVersionUID = 1L;
    	
    	private Integer id;
        private Integer account;
    
        public Account clone() {
            try {
                Account proto = (Account) super.clone();
                return proto;
            }catch (CloneNotSupportedException e){
                return null;
            }
        }
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public Integer getAccount() {
            return account;
        }
        public void setAccount(Integer account) {
            this.account = account;
        }
    }
    
    package com.example.demo.mapper;
    import com.example.demo.domain.Account;
    import org.apache.ibatis.annotations.*;
    @Mapper
    public interface AccountMapper {
        @Insert("INSERT INTO account (account) VALUES (#{a.account})")
        public void insertAccount(@Param("a") Account a);
        @Update("UPDATE account SET account=#{a.account} WHERE id=#{a.id}")
        public void updateAccount(@Param("a") Account a);
        @Select("DELETE FROM account WHERE id=#{id}")
        public void deleteAccountById(@Param("id") Integer id);
        @Select("SELECT id,account FROM account WHERE id=#{id}")
        public Account selectAccountById(@Param("id")Integer id);
    }
    
    3 后面是具体的业务代码
    package com.example.demo.service;
    import javax.annotation.Resource;
    import org.springframework.stereotype.Service;
    import com.csp.operationlog.aspect.annotation.OperationLog;
    import com.csp.operationlog.aspect.enums.OperationType;
    import com.csp.operationlog.service.OperationLogService;
    import com.example.demo.domain.Account;
    import com.example.demo.mapper.AccountMapper;
    @Service
    public class AccountService {
        @Resource
        AccountMapper accountMapper;
        @Resource
        OperationLogService operationLogService;
    
        //采用注解方式,实现操作日志的记录,适用于大多数简单服务,不涉及代码中多表更改的业务
        @OperationLog(name = "添加账户",type = OperationType.ADD,operatorIdRef = 0,operatorNameRef = 1,table = "account")
        public void addAccount(String operatorId,String operatorName){
            Account account = new Account();
            account.setAccount(181);
            accountMapper.insertAccount(account);
        }
    
        @OperationLog(name = "更新账户",type = OperationType.UPDATE,operatorIdRef = 0,operatorNameRef = 1,idRef = 2,table = "account")
        public void updateAccount(String operatorId,String operatorName,Integer accountId){
            Account account = new Account();
            account.setId(accountId);
            account.setAccount(1100);
            accountMapper.updateAccount(account);
        }
    
        @OperationLog(name = "删除账户",type = OperationType.DELETE,operatorIdRef = 0,operatorNameRef = 1,idRef = 2,table = "account")
        public void deleteAccount(String operatorId,String operatorName,Integer accountId){
            accountMapper.deleteAccountById(accountId);
        }
    
        //使用服务调用的方式,实现操作日志的记录,为了在注解无法解决业务代码中对多个表操作时的应对方法
        public void addAcccount2(){
            Account account=new Account();
            account.setAccount(181);
            accountMapper.insertAccount(account);
            operationLogService.logForAdd("添加账户","account","1","liutao",account);
        }
    
        public void updateAccount2(){
            Account account = accountMapper.selectAccountById(1);
            if (account!=null){
                Account accountOld = account.clone();
                account.setId(1);
                account.setAccount(0);
                accountMapper.updateAccount(account);
                operationLogService.logForUpd("更新账户","account",account.getId().toString(),"1","liutao",accountOld,account);
            }
        }
    
        public void deleteAccount2(){
            Account account = accountMapper.selectAccountById(1);
            if (account!=null){
                accountMapper.deleteAccountById(1);
                operationLogService.logForDel("删除账户","account",account.getId().toString(),"1","liutao",account);
            }
        }
    }
    4 为了方便测试,我们写几个controller进行测试,有页面调用,模拟实际业务操作
    package com.example.demo.controller;
    import javax.annotation.Resource;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import com.example.demo.service.AccountService;
    @Controller
    public class AccountControler {
        @Resource
        private AccountService accountService;
        //采用注解方式
        @RequestMapping("/t1")
        @ResponseBody
        public String addTest(){
            accountService.addAccount("1","liutao");
            return "ok";
        }
        @RequestMapping("/t2")
        @ResponseBody
        public String updateTest(){
            accountService.updateAccount("1","liutao",1);
            return "ok";
        }
        @RequestMapping("/t3")
        @ResponseBody
        public String deleteTest(){
            accountService.deleteAccount("1","liutao",1);
            return "ok";
        }
    
        //采用服务调用
        @RequestMapping("/s1")
        @ResponseBody
        public String addTest2(){
            accountService.addAcccount2();
            return "ok";
        }
        @RequestMapping("/s2")
        @ResponseBody
        public String updateTest2() {
            accountService.updateAccount2();
            return "ok";
        }
        @RequestMapping("/s3")
        @ResponseBody
        public String deleteTest2(){
            accountService.deleteAccount2();
            return "ok";
        }
    }
    
    配置文件application.yml
    spring:
      datasource:
        driver-class-name: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/demo?characterEncoding=utf-8&useSSL=false
        username: root
        password: root
    

    备注

    有了上面的基本实现,和测试demo,应该可以基本实现日志的记录,
    对于相关细节,我都放到备注中,
    1 apo实现,需要再建立在mysql事务级别在可重复读级别上(一般默认就是哈!)
    2 服务调用实现,可以异步处理啦,如果有为了效率可以再我备注的地方实现即可,自己选择mq实现就行了
    3 只能保证基本实现了,质量不保证,主要是提供思路和实现逻辑,有了思路,自己可以写的
    4 已经有了记录,具体的表查询的业务代码就可以自己查表了哈
    最后项目代码放到github: https://github.com/taopanwoaini/operation-log 自行下载吧,路过几点点个星,谢谢

    展开全文
  • 业务系统日志记录规范总结

    千次阅读 2019-08-28 16:01:09
    业务系统日志记录规范 注意 应用中应该充满了日志记录信息,日志甚至比逻辑代码还要多; 集成 seluth ,开启消息链路;不开启日志上传,不集成 zipkin; 应该避免日志记录过程中出现异常,比如 log.debug(requst...

    业务系统日志记录规范

    注意

    1. 应用中应该充满了日志记录信息,日志甚至比逻辑代码还要多;
    2. 集成 seluth ,开启消息链路;不开启日志上传,不集成 zipkin;
    3. 应该避免日志记录过程中出现异常,比如 log.debug(requst.getid) ,这条日志记录之前一定要判断 request 是否为空;日志记录中使用的信息一定时稳定的,提前准备好的,最好不是专门为此次日志记录专门准备的;
    4. 日志应只记录标识性信息,具体信息从具体存储里取;
    5. 日志应该同时支持人和计算机都可以读;人可读的意思的,要成句子;计算机可读的意思是要有明确的分割符,可以支持正则等工具抽取其中有意义的信息;
    6. service 或 manager 层内有 if…else 或者 switch 这样的分支时,要在分支的首行打印日志,用来确定进入了哪个分支;
    7. 对于 trace / debug / info 级别的日志输出,必须进行日志级别的开关判断。warn 和 error 不用开关判断;

    日志级别 trace 和 debug

    测试环境要实现的目标是,不需要重新完整的调试程序,可以直接定位到出问题的 service 内的逻辑分支,最多在进行一次小范围的测试;
    要实现这个目标,需要记录 service 入参出参,debug级别的日志,只在非生产环境中使用;

    日志级别 info

    1. info级别的日志只在 service 层和 manager 层存在,体现业务逻辑运行的路径,工具类等的不要记录;
    2. Service 方法中对于系统/业务状态的变更处记录
    3. 主要逻辑中的分步骤记录

    日志级别 warn

    1. 有容错机制的时候出现的错误情况,有异常,但是你有 Plan B;
    2. 业务异常的记录,比如:当接口抛出业务异常时,应该记录此异常;
    3. warn 日志级别来记录用户输入参数错误的情况,避免用户投诉时,可以追溯信息;

    日志级别 error

    error 级别只记录系统逻辑出错、异常或者重要的错误信息。
    异常在程序中,一般会沿着调用链路,层层上抛,直到service层,在最终处理异常的地方记录log.error(e);log.error和往外抛异常,不应该同时出现;

    错误异常

    错误异常分为程序异常(系统异常)和业务异常;

    程序异常会导致程序不能正常执行;业务异常不会,业务异常的处理属于业务逻辑的一部分;
    ERROR 级别的日志,一出,意味着开发运维人员要介入了,要操作确认一下东西,要维修一些东西了;

    业务系统开发过程中,不需要 log.error 记录异常,让框架和容器(Tomcat等)来做;
    业务异常的需要开发者开发对应的异常处理逻辑,业务异常不是程序异常;比如用户登陆失败;
    业务异常的处理属于正常的业务逻辑,不应该log.error,不重要的可以不log,重要的可以使用log.warn记录,避免用户投诉时,可以追溯信息;

    数据库或者kafka在应用启动时连不上是如何处理的,在应用运行过程中是如何处理的?启动时,依赖的插件有问题,应用直接启动不起来;如果应用已经启动起来了,触发到跟其交互时,抛出异常,程序还是正常执行;

    ExceptionHandler的默认的处理逻辑

    ExceptionHandler的默认的处理逻辑不要吃掉所有的异常,log.error打印出来,这个逻辑主要用来处理程序异常,业务异常都应有对应的处理逻辑;

    @ExceptionHandler(Throwable.class)
    @ResponseBody
    public String handle(Throwable e) {
    	log.error(e.getMessage(),e);
    	//构造返回信息
    	return e.getMessage();
    }
    

    程序异常

    各个组件的异常信息可以各自处理;比如SQLExcption,需要如果SQL 出异常,异常的信息中会有SQL相关的信息,如果直接返回给前端,会造成安全问题,前端对此异常也不关心,而后端开发者关心的信息应该都记录在日志中,以方便分析问题;

    @ExceptionHandler(SQLException.class)
    @ResponseBody
    public String handle(SQLException e) {
    	log.error(e.getMessage(),e);
    	//构造返回信息,记得脱敏
    	return e.getMessage();
    }
    

    各个组件的SDK一般都会有自己的异常体系;可以根据情况直接对顶级异常类,或者典型的异常子类进行处理;
    接入外部组件时,首先分析其SDK的异常体系,编写对应的ExceptionHandler

    org.apache.kafka.common.KafkaException
    org.springframework.kafka.KafkaException
    java.sql.SQLException

    链路追踪

    多个进程间的日志联动
    集中式日志存储系统的存在,让在一个入口处理业务系统的日志成为了可能,产生了高级的用法,链路追踪;
    用户的一个动作触发的在各个系统的所有的执行逻辑,使用一个标识将其联系起来,开发人员分析的时候,可以根据此标识查询所有相关的日志,哪里出问题,一目了然;

    远程调用

    远程调用的plan b,就是熔断降级里面的Plan B;
    外部接口部分,客户端请求参数(REST/WS),调用第三方时的调用参数和调用结果使用info

    如果出异常,调用过程异常或者返回错误码,根据情况选择抛出异常或者启用Plan B;
    抛出异常意味着程序正常流程执行结束,需要处理这个异常,warn记录此异常,然后返回用户结果;
    Plan B意味着程序还可以正常执行下去,warn记录发生了此事件,出现异常转入Plan B;

    远程调用过程中出异常,意味着需要开发人员介入,应该记录 error 级别的异常;
    如果是返回的错误码是非成功执行的错误码,这时候应该根据错误码的级别抛出不同的异常,处理异常的地方根据远程调用的接口的重要程度评估使用不同的日志级别。

    验证日志

    提交代码前,确定通过日志可以看到一个功能的整个执行流程,可以通过日志进行问题定位程序执行的路径;

    参考

    用JAVA日志来写诗
    JAVA - 优雅的记录日志(log4j实战篇)
    Alibaba Java Coding Guidelines
    小白学习如何打日志
    正确的打日志姿势
    Java常用日志框架介绍

    展开全文
  • 今天跟大家分享的就是数据业务级日志记录的2个方案 一、数据业务级日志是什么意思 我这里的意思其实就是业务操作前的数据、业务操作后的数据。意义在于,可以通过对比直观显示业务操作前后字段值变化情况,方便...

            对于日志的记录,大家肯定会考虑使用aop,但是aop能不能记录业务操作前后的数据呢?今天跟大家分享的就是数据业务级日志记录的2个方案

     一、数据业务级日志是什么意思

            我这里的意思其实就是业务操作前的数据、业务操作后的数据。意义在于,可以通过对比直观显示业务操作前后字段值变化情况,方便运维、客服人员查看,同时出现事故可以通过日志追责。

    二、设计思路

           1、使用aop,想办法记录业务操作前后的数据

           2、业务方法调用日志保存方法

    三、使用aop实现记录操作前后业务数据

            aop的集成:

            pom文件引入aop支持

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

           切面类:

    @Aspect
    @Component
    @Slf4j
    public class AppLogAop {
        @Pointcut("execution(public * com.xw.controller..*.*(..))")
        public void Pointcut() {
        }
     
        //前置通知
        @Before("Pointcut()")
        public void beforeMethod(JoinPoint joinPoint){
            log.info("调用了前置通知");
     
        }
     
        //@After: 后置通知
        @After("Pointcut()")
        public void afterMethod(JoinPoint joinPoint){
            log.info("调用了后置通知");
        }
        //@AfterRunning: 返回通知 rsult为返回内容
        @AfterReturning(value="Pointcut()",returning="result")
        public void afterReturningMethod(JoinPoint joinPoint,Object result){
            log.info("调用了返回通知");
        }
        //@AfterThrowing: 异常通知
        @AfterThrowing(value="Pointcut()",throwing="e")
        public void afterReturningMethod(JoinPoint joinPoint, Exception e){
            log.info("调用了异常通知");
        }
     
        //@Around:环绕通知
        @Around("Pointcut()")
        public Object Around(ProceedingJoinPoint pjp) throws Throwable {
            //TODO 切面记录日志:对于数据业务级的日志,缺陷是方法没有执行也会记录(方法里有很多校验)
            /*
            Signature sig = pjp.getSignature();
            MethodSignature msig = null;
            if (!(sig instanceof MethodSignature)) {
                throw new IllegalArgumentException("该注解只能用于方法");
            }
            msig = (MethodSignature) sig;
            Object target = pjp.getTarget();
            Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
            String methodName = currentMethod.getName();
            
            Object targetName = pjp.getTarget().getClass().getName();
            
            Object[] args = pjp.getArgs();
            
            
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            
            OperateLog operateLog = OperateLogUtil.initOperateLog(methodName,targetName,args,request);
            */
            
            log.info("around执行方法之前");
            Object object = pjp.proceed();
            log.info("around执行方法之后--返回值:" +object);
            return object;
        }

    }

    注意注解标签@Aspect、@Component,然后@Pointcut里注意设置controller包路径,具体的这里不细说。

    主要看我的环绕事件Around,这个事件在Object object = pjp.proceed();前后做文章,在这之前是还没有执行业务方法,这之后是执行了。这里的思路:

    1、取到方法名称、请求路径、controller等信息

    2、定义一个枚举类,设置方法名、controller名、业务中文描述,凡是配置在这个枚举类的就是要记录的,否则就直接放过(更灵活一点的做法就是建表记录,考虑性能问题的化,可以将表记录的信息缓存到redis,避免查询数据库影响接口性能)

    3、方法执行前初始一条日志记录数据,这个里面会根据controller、方法名称获取到对应的service或mapper,从入参获取id,查询业务数据。

    4、方法执行后调用异步方法(使用多线程实现,上期分享讲过),同3一样查询数据(此时应该是已经操作过的数据,其实也可以直接在方法执行前就调用异步方法,查询的数据作为操作之前,用入参替换对应字段的值作为之后的数据,这样性能更优,对接口可以说是没有一点延迟影响)

    5、保存生成的日志记录,这里操作之前、操作之后都使用json字符串记录数据,增加一个className,方便后面可以将json字符串转对象。

    我的日志记录对象:

    @Data
    public class OperateLog {
        /**
         * 业务内部区分id
         */
        private String difSourceId;
        /**
         * 流水号
         */
        private Long operateNo;
        /**
         * 操作类别
         */
        private String operateType;
        /**
         * 数据json串,操作之前
         */
        private String beforeData;
        /**
         * 数据json串,操作之后
         */
        private String afterData;
        /**
         * 请求url
         */
        private String requestUrl;
        /**
         * 方法名称
         */
        private String methodName;
        /**
         * 操作人id
         */
        private Integer userId;
        /**
         * 操作人名称(冗余)
         */
        private String userName;
        /**
         * 记录时间
         */
        @JSONField(format = "yyyy-MM-dd HH:mm:ss")
        private String recordTime;
        
        /**
         * 参数接收,开始时间
         */
        @JSONField(format = "yyyy-MM-dd HH:mm:ss")
        private String startTime;
        /**
         * 参数接收,接收时间
         */
        @JSONField(format = "yyyy-MM-dd HH:mm:ss")
        private String endTime;
        /**
         * 关键字
         */
        private String paramKey;
        /**
         * 请求ip
         */
        private String reqIp;
        /**
         * 对象class名称
         */
        private String entityName;
        /*
         * 操作名称
         */
        private String operateName;
    }

    字段上有注释,相信大家一看就懂。

    四、业务系统调用

         这个就更好解释了,实际就是每个方法里直接调用一个公共方法,传入之前、之后的json字符串、操作中文描述等,当然也是异步方法,不影响接口性能的。这个公共方法应该不用我贴代码大家就可以自己实现。

    总结:

    使用aop会简单点,每个service里的逻辑还是改怎么写就怎么写。但是如果遇到方法里操作多张业务数据表,那这就有点尴尬了。另外呢,方法里面还有参数校验嘛,可能参数校验后就不执行操作了呢?aop是无法感知是否真正执行了吧,也许有人说可以看返回结果啊,但是如果这样,你就不能开启异步方法了,对性能会有影响的,接口的执行时间肯定会边长。因此,我这里最终还是放弃了aop这种方法,使用了封装公共方法,之前批量初始日志,开启异步方法保存日志。

          希望能启发到大家,有更好的思路还原评论指正。

     

     

    展开全文
  • ASP.NET MVC的最佳日志记录

    千次阅读 2019-03-02 13:13:35
    4个日志记录库 log4net Log4net记录文本文件中 Log4net记录到数据库中 NLOG Nlog日志记录在文本文件中 NLog日志记录到数据库中 Serilog Serilog记录到文本文件中 Serilog记录到数据库中 Serilog.Settings....

     

    目录

    介绍

    4个日志记录库

    log4net

    Log4net记录文本文件中

    Log4net记录到数据库中

    NLOG

    Nlog日志记录在文本文件中

    NLog日志记录到数据库中

    Serilog

    Serilog记录到文本文件中

    Serilog记录到数据库中

    Serilog.Settings.AppSettings    

    ELMAH

    ELMAH记录数据库


    在本文中,我们将学习如何为ASP.NET MVC应用程序实现市场上可用的最佳Logging库。

    介绍

    什么是日志记录?

    日志记录是保存日志的行为。日志文件是写入应用程序的所有事件或错误的文件。

    参考:https://en.wikipedia.org/wiki/Log_file

    日志记录是应用程序的重要部分我们尝试构建多少无错误的应用程序,但有些错误是有条件地发生的,为了记录这种错误,我们需要使用一些日志库,例如,由于某些数据库服务器关闭或某些应用程序功能不正常,可能会发生一些错误,如果我们没有适当的日志记录设置,我们将无法知道在客户端功能上不起作用。开发人员有一个众所周知的短语它在我的机器上是正常的。要知道应用程序日志记录中出现的问题很重要。

    https://www.codeproject.com/KB/aspnet/1278018/image001.png

    4个日志记录库

    我们有4个日志记录库,我们将详细了解如何使用ASP.NET MVC应用程序实现它们。

    1. Log4net  (记录在文本文件中+记录在SQL数据库中)

    2. Nlog  (记录在文本文件中+记录在SQL数据库中)

    3. Serilog  (记录在文本文件中+记录在SQL数据库中)

    4. Elmah(记录在SQL数据库中)

    源代码可在Github链接上获得,在本文末尾提供。

    创建ASP.NET应用程序

    让我们首先使用名为“ WebErrorLogging ”4.5 ASP.NET模板创建一个简单的ASP.NET Web应用程序。

    https://www.codeproject.com/KB/aspnet/1278018/image003.png

    在创建应用程序之后,我们将要看到如何实现的第一个库是Log4net

    log4net

    什么是Apache log4net

    Apache log4net库是一个帮助程序员将日志语句输出到各种输出标的工具。

    参考定义:——https://logging.apache.org/log4net/

    NuGet添加项目的引用

    我们将从NuGet包安装log4net

    https://www.codeproject.com/KB/aspnet/1278018/image004.png

    在添加了log4net的引用后,我们将配置它以记录错误。

    我们将在Log4net中看到两种记录方式

    1. 在文本文件中
    2. 在数据库中

    Log4net记录文本文件中

    让我们从日志记录在文本文件中开始。为了在Log4net中执行此操作,我们需要在web.config文件中添加配置。为了在文本文件中记录消息,我们将使用RollingLogFileAppender类。

    RollingLogFileAppender根据大小或日期或两者来滚动日志文件。

    RollingFileAppender构建在FileAppender之上,并且具有与该appender相同的选项。

    web.config文件中添加此配置将开始将记录消息写入文件中。

    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
     </configSections>
    
    <log4net>
        <root>
          <level value="ALL"></level>
          <appender-ref ref="RollingLogFileAppender"></appender-ref>
        </root>
        <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
          <file value="E:\DemoProject\WebErrorLogging\WebErrorLogging\ErrorLog\logfile.txt" />
          <appendToFile value="true" />
          <rollingStyle value="Date" />
          <datePattern value="yyyyMMdd" />
          <maxSizeRollBackups value="10" />
          <maximumFileSize value="1MB" />
          <layout type="log4net.Layout.PatternLayout">
            <conversionPattern 
    
            value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
          </layout>
        </appender>
    </log4net>

    您可以在上面的配置设置中看到,这些设置必须添加到web.config文件的“<configuration>”标记中。

    然后在log4net元素中我们可以看到该标签内的root元素,在root内我们还有2个子元素,一个是level,另一个是appender-ref

    appender-ref:——允许零个或多个元素。允许记录器按名称引用appender

    level:——可选元素,最多允许一个元素。定义此记录器的日志记录级别。此记录器仅接受此级别或更高级别的事件。

    不同的日志记录级别

    • ALL
    • DEBUG
    • INFO
    • WARN
    • ERROR
    • FATAL
    • OFF

    RollingLogFileAppender

    如果你可以看到里面有名称为RollingLogFileAppender的主元素appender,那么就有一个子元素文件,我们可以在其中配置日志记录文件的路径。

    示例:——

    <file value="ErrorLog/log.txt" />

    注意:——我已将路径指定为“ErrorLog / log.txt”,因为我在此应用程序中创建了一个ErrorLog文件夹。

    https://www.codeproject.com/KB/aspnet/1278018/image006.png

    下一个元素是appendToFile

    appendToFile

    如果该值设置为false,则将覆盖该文件,如果将其设置为true,则将附加该文件。

    RollingStyle

    名称

    描述

    Once

    每个程序执行一次滚动文件

    Size

    仅根据文件大小滚动文件

    Date

    仅根据日期滚动文件

    Composite

    根据文件的大小和日期滚动文件

    参考来自:—— https://logging.apache.org/

    maxSizeRollBackups

    如果我们将最大文件大小设置为1MB并将maxSizeRollBackups设置为10 MB,那么根据日期或文件大小,它将只保留最后10MB的文件。

    web.config文件的快照

    我在下面给出了快照,以供参考,在web.config文件中正确添加元素。

    https://www.codeproject.com/KB/aspnet/1278018/image008.jpg

    web.config文件中添加配置后,我们将初始化Log4net

     初始化Log4net

    我们需要调用XmlConfigurator类的configure方法来初始化Log4net

    log4net.Config.XmlConfigurator.Configure();

    https://www.codeproject.com/KB/aspnet/1278018/image009.png

    global.asax中初始化XmlConfigurator方法之后。现在可以演示了,我在此添加了一个名为DefaultController的控制器,其中包含Index操作方法,以显示我们如何记录错误,使用它来调试信息。

    代码片段

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using log4net;
    
    namespace WebErrorLogging.Controllers
    {
        public class DefaultController : Controller
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(DefaultController));
    
            // GET: Default
    
            public ActionResult Index()
            {
                try
                {
                    Log.Debug("Hi I am log4net Debug Level");
                    Log.Info("Hi I am log4net Info Level");
                    Log.Warn("Hi I am log4net Warn Level");
                    throw new NullReferenceException();
                    return View();
                }
                catch (Exception ex)
                {
                    Log.Error("Hi I am log4net Error Level", ex);
                    Log.Fatal("Hi I am log4net Fatal Level", ex);
                    throw;
                }
            }
        }
    }

    添加控制器并获取LogManager类的实例后,我们在各个级别上记录了消息。      

    现在让我们通过运行应用程序来测试它。

    错误日志记录的文件

    https://www.codeproject.com/KB/aspnet/1278018/image010.png

    现在我们已经完成了将应用程序记录到文本文件中,然后我们将异常记录到数据库中。

    Log4net记录到数据库中

    要记录到数据库中,我们需要先在数据库中创建一个表。

    https://www.codeproject.com/KB/aspnet/1278018/image012.png

    用于创建表的脚本

    CREATE TABLE [dbo].[Log](
           [Id] [int] IDENTITY(1,1) NOT NULL,
           [Date] [datetime] NOT NULL,
           [Thread] [varchar](255) NOT NULL,
           [Level] [varchar](50) NOT NULL,
           [Logger] [varchar](255) NOT NULL,
           [Message] [varchar](4000) NOT NULL,
           [Exception] [varchar](2000) NULL
    ) ON [PRIMARY]

    在数据库中创建Log表后,我们将添加新类型的appender,即“log4net.Appender.AdoNetAppender”

    web.config文件中添加此配置将开始将日志消息写入Database表。

    为此,我们需要数据库的连接字符串,我们将记录错误,如果您看到下面的配置,您将看到connectionStrings元素,其中我添加了数据库连接设置,我们将在AdoNetAppender中分配connectionStrings 

    如果在AdoNetAppender中看到commandText元素,您将在其中看到insert语句的脚本。

    我们需要设置的主要内容是appender-ref,这里我们要将消息记录到数据库中,以便我们将参考(ref)设置为AdoNetAppender

    <appender-ref ref="AdoNetAppender"></appender-ref>

    配置设置

    <configuration>
      <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
      </configSections>
      <connectionStrings>
        <add name="ConnectionStringLogging" 
    
        connectionString="data source=SAI-PC\SQLEXPRESS;
        initial catalog=LoggingDatabase;
        integrated security=false;persist security info=True;User ID=sa;Password=Pass$123" 
    
        providerName="System.Data.SqlClient" />
      </connectionStrings>
      <log4net>
        <root>
          <level value="ALL"></level>
          <appender-ref ref="AdoNetAppender"></appender-ref>
        </root>
        <appender name="AdoNetAppender" type="log4net.Appender.AdoNetAppender">
          <bufferSize value="1" />
          <connectionType 
    
          value="System.Data.SqlClient.SqlConnection,System.Data, 
          Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
          <connectionStringName value="ConnectionStringLogging" />
          <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message],[Exception]) 
          VALUES (@log_date, @thread, @log_level, @logger, @message, @exception)" />
          <parameter>
            <parameterName value="@log_date" />
            <dbType value="DateTime" />
            <layout type="log4net.Layout.RawTimeStampLayout" />
          </parameter>
          <parameter>
            <parameterName value="@thread" />
            <dbType value="String" />
            <size value="255" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%thread" />
            </layout>
          </parameter>
          <parameter>
            <parameterName value="@log_level" />
            <dbType value="String" />
            <size value="50" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%level" />
            </layout>
          </parameter>
          <parameter>
            <parameterName value="@logger" />
            <dbType value="String" />
            <size value="255" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%logger" />
            </layout>
          </parameter>
          <parameter>
            <parameterName value="@message" />
            <dbType value="String" />
            <size value="4000" />
            <layout type="log4net.Layout.PatternLayout">
              <conversionPattern value="%message" />
            </layout>
          </parameter>
          <parameter>
            <parameterName value="@exception" />
            <dbType value="String" />
            <size value="2000" />
            <layout type="log4net.Layout.ExceptionLayout" />
          </parameter>
        </appender>
      </log4net>
    </configuration>

    web.config文件的快照

    我在下面给出了快照,以供参考,在web.config文件中正确添加元素。

    https://www.codeproject.com/KB/aspnet/1278018/image014.jpg

    现在我们将运行我们用于在文本文件中记录消息的相同应用程序,但这次我们要将消息记录到数据库中。

    现在让我们通过运行应用程序来测试它。

    错误记录到数据库中

    运行应用程序并访问默认控制器后,消息将记录在数据库中,如下所示。

    https://www.codeproject.com/KB/aspnet/1278018/image015.png

    在完成了解Log4net之后,接下来我们将继续讨论如何使用Nlog来记录消息。

    NLOG

    什么是Nlog

    NLog是一个灵活的免费日志记录平台,适用于各种.NET平台,包括.NET standardNLog可以轻松写入多个目标(数据库,文件,控制台)并即时更改日志记录配置。

    参考来自:—— https://nlog-project.org/

    NuGet添加项目的引用

    我们将从NuGet包安装2个包NLogNLog.Config

    https://www.codeproject.com/KB/aspnet/1278018/image017.png

    添加NLog的引用后,接下来我们将配置它以记录错误。

    我们将看到在NLog中有2种日志记录的方法

    1. 在文本文件中
    2. 在数据库中 

    Nlog日志记录在文本文件中

    级别

    典型用途

    Fatal

    发生了一件坏事应用程序即将关闭

    Error

    有些事情失败了应用程序可能会也可能不会继续工作

    Warn

    出乎意料的事应用程序将继续工作

    Info

    正常行为如邮件发送给用户,更新配置文件等

    Debug

    用于调试执行查询,用户通过身份验证,会话已过期

    Trace

    用于跟踪调试开始方法X,结束方法X.

    参考自:https://github.com/NLog/NLog/wiki/Configuration-file#log-levels

    让我们从在文本文件中的日志记录开始。为了在NLog中执行此操作,我们需要在web.config文件中添加配置以便在文本文件中写入。

    代码片段

    <configSections>
        <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" />
      </configSections>
    
      <nlog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
        <targets>
          <target name="logfile" xsi:type="File"
    
                  maxArchiveFiles="10"
    
                  archiveAboveSize="5242880"
    
                  archiveEvery="Minute"
    
                  archiveNumbering="Sequence"
    
                  fileName="${basedir}/ErrorLog/logfile.txt"
    
                  layout="------${level}(${longdate})${machinename}------${newline}
                          Exception Type:${exception:format=Type} |
                          Exception Message:${exception:format=Message} |
                          Stack Trace:${exception:format=Stack Trace} |
                          Additional Info:${message}${newline}" />
        </targets>
        <rules>
          <logger name="*" minlevel="Trace" writeTo="logfile" />
        </rules>
      </nlog>

    您可以根据需要设置许多选项,您可以根据文件的时间大小存档日志。对于这个例子,我是根据时间和大小来完成的, 因为我已经通过了archiveEvery =“Minute”,对于大小,我提供了archiveAboveSize =“5242880”,大小是以字节5MB =“5,242,880”为单位。

    链接:——https://github.com/nlog/NLog/wiki/File-target

    web.config文件的快照

    我在下面给出了快照,以供参考,在web.config文件中正确添加元素。

    ​​​​​​https://www.codeproject.com/KB/aspnet/1278018/image019.png

    在配置NLog后,接下来我们将在其中添加带有Index操作方法的DefaultController,其接下来用于日志记录,我们将使用LogManager类创建Logger实例。

    代码片段

    public class DefaultController : Controller
        {
            public readonly Logger Logger = NLog.LogManager.GetCurrentClassLogger();
            // GET: Default
            public ActionResult Index()
            {
                try
                {
                    Logger.Debug("Hi I am NLog Debug Level");
                    Logger.Info("Hi I am NLog Info Level");
                    Logger.Warn("Hi I am NLog Warn Level");
                    throw new NullReferenceException();
                    return View();
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, "Hi I am NLog Error Level");
                    Logger.Fatal(ex, "Hi I am NLog Fatal Level");
                    throw;
                }
            }
        }

    在创建Logger实例后,我们将调用各种日志记录级别方法来记录消息。

    现在让我们通过运行应用程序来测试它。

    错误记录的文件

    https://www.codeproject.com/KB/aspnet/1278018/image021.png

    现在我们已经完成了在文本文件中记录消息,接下来在配置文件中进行了一些更改,我们将以简单的步骤将日志记录消息发送到数据库中

    NLog日志记录到数据库中

    要把日志记录到数据库中,我们需要先在数据库中创建一个表。

    https://www.codeproject.com/KB/aspnet/1278018/image023.png

    用于创建表的脚本

    CREATE TABLE [dbo].[NLog](
        [id] [int] IDENTITY(1,1) NOT NULL Primary key,
        [timestamp] [datetime] NOT NULL,
        [level] [varchar](100) NOT NULL,
        [logger] [varchar](1000) NOT NULL,
        [message] [varchar](3600) NOT NULL,
        [Callsite] [varchar](3600) NULL,
        [exception] [varchar](3600) NULL
    )

    完成创建NLog表后,接下来我们将添加配置设置到现有web.config文件以记录到数据库中。

    我们只需要在现有的NLog配置中添加一个具有新名称的新目标。这个目标包含数据库的连接字符串以及SQL插入查询以插入数据库,下一步,我们需要传递包含要与之一起记录的数据的参数,我们需要在rules元素中使用日志级别(“trace”)注册目标(“database”)。     

    用于记录数据库的配置设置

    <target name="database" type="Database" 
    
    connectionString="data source=SAI-PC\SQLEXPRESS;
    initial catalog=LoggingDatabase;
    integrated security=false;
    persist security info=True;
    User ID=sa;Password=Pass$123">
            <commandText> insert into NLog ([TimeStamp],[Level],Logger, [Message], Callsite, Exception) 
            values (@TimeStamp, @Level, @Logger, @Message, @Callsite, @Exception); </commandText>
            <parameter name="@TimeStamp" layout="${date}" />
            <parameter name="@Level" layout="${level}" />
            <parameter name="@Logger" layout="${logger}" />
            <parameter name="@Message" layout="${message}" />
            <parameter name="@Callsite" layout="${callsite}" />
            <parameter name="@Exception" layout="${exception:tostring}" />
            <dbProvider>System.Data.SqlClient</dbProvider>
          </target>
        <rules>
          <logger name="*" minlevel="Trace" writeTo="database" />
        </rules>

    注意: - * - 匹配0个或更多字符。

    添加新目标以记录数据库中的消息后,web.config文件的快照

    https://www.codeproject.com/KB/aspnet/1278018/image024.png

    现在让我们通过运行应用程序来测试它。

    错误记录到数据库中

    运行应用程序并访问默认控制器后,消息将记录在数据库中,如下所示。

    https://www.codeproject.com/KB/aspnet/1278018/image026.png

    在完全理解Nlog后,接下来我们将继续前进,将看看如何使用Serilog Log记录消息。

    Serilog

    什么是Serilog

    Serilog.NET应用程序的诊断日志记录库。它易于设置,具有干净的API,并可在所有最新的.NET平台上运行。虽然它在最简单的应用程序中也很有用,但Serilog对结构化日志记录的支持在检测复杂、分布式和异步应用程序和系统时尤为突出。

    我们将在Serilog中看到两种记录的方法

    1. 在文本文件中
    2. 在数据库中 

    NuGet添加项目的引用

    我们将从NuGet包安装2个软件包“Serilog”“Serilog.Sinks.File”

    Serilog:——Serilog.NET应用程序的日志记录库。

    https://www.codeproject.com/KB/aspnet/1278018/image028.png

    Serilog.Sinks.File:——此包用于将日志记录消息写入文本文件。

    https://www.codeproject.com/KB/aspnet/1278018/image030.png

    添加两个包之后,接下来我们将查看日志事件级别。

    记录事件级别

    Serilog使用级别作为分配日志事件重要性的主要手段。重要性递增的级别为:

    接下来,我编写了一个帮助类,其中我编写了代码来集中事件日志记录。

    名称

    描述

    Verbose

    跟踪信息和调试细节通常,仅在异常情况下开启

    Debug

    内部控制流程和诊断状态转储,以便于查明已识别的问题

    Information

    感兴趣的事件或与外部观察员有关的事件默认启用的最低日志记录级别

    Warning

    可能的问题或服务/功能退化的指标

    Error

    表示应用程序或连接系统中的故障

    Fatal

    导致应用程序完全失败的严重错误

    参考链接:——https://github.com/serilog/serilog/wiki/Writing-Log-Events

    Serilog记录到文本文件中

    让我们开始理解代码。我创建了一个名为helper的静态类,这样就可以在不创建对象的情况下使用它。下一个案例是仅初始化此类一次,这就是为什么我必须使用静态构造函数来初始化它。对于在文本文件中的日志记录,我们已经创建了LoggerConfiguration类的实例,并且对于日志记录错误,我已经将最低级别设置为错误,我们要写文本文件,但在我们要存储它的地方,它将在文件夹右侧,为了保持整洁以维护错误,我已经在该文件夹中创建了一个主文件夹“ ErrorLog ”,我必须根据调试,错误,警告...” 级别创建子文件夹。现在我们需要将消息写入此文件夹,这样我们就需要为它设置路径,我们将使用“ WriteTo.File方法,并以字符串形式将路径传递给它。接下来,我们不能将错误消息记录为单个大文本文件,因为我不会帮助我们轻松跟踪消息记录。另一个原因是,如果文件大小变大,则无法轻松打开该文件以克服这种情况,我们可以使用rollingIntervalrollOnFileSizeLimit属性,在rollingInterval中,我们可以使用RollingInterval参数(无限,年,月,日,小时,分钟)滚动文件,在rollOnFileSizeLimit中,我们可以设置文件大小限制。为此,我们需要以字节为单位设置属性fileSizeLimitBytes并将rollOnFileSizeLimit设置为true。现在,在创建实例后,我根据不同的级别创建了不同的方法,以便我们可以根据需要使用不同的方法。 

    https://www.codeproject.com/KB/aspnet/1278018/image032.png    

    Helper类的代码片段

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using Serilog;
    using Serilog.Events;
    
    namespace WebErrorLogging.Utilities
    {
        public static class Helper
        {
            private static readonly ILogger Errorlog;
            private static readonly ILogger Warninglog;
            private static readonly ILogger Debuglog;
            private static readonly ILogger Verboselog;
            private static readonly ILogger Fatallog;
    
            static Helper()
            {
    
                // 5 MB = 5242880 bytes
    
                Errorlog = new LoggerConfiguration()
                    .MinimumLevel.Error()
                   .WriteTo.File(System.Web.Hosting.HostingEnvironment.MapPath("~/ErrorLog/Error/log.txt"),
                    rollingInterval: RollingInterval.Day,
                    fileSizeLimitBytes: 5242880,
                    rollOnFileSizeLimit: true)
                    .CreateLogger();
    
                Warninglog = new LoggerConfiguration()
                    .MinimumLevel.Warning()
                  .WriteTo.File(System.Web.Hosting.HostingEnvironment.MapPath("~/ErrorLog/Warning/log.txt"),
                        rollingInterval: RollingInterval.Day,
                        fileSizeLimitBytes: 5242880,
                        rollOnFileSizeLimit: true)
                    .CreateLogger();
    
                Debuglog = new LoggerConfiguration()
                    .MinimumLevel.Debug()
                  .WriteTo.File(System.Web.Hosting.HostingEnvironment.MapPath("~/ErrorLog/Debug/log.txt"),
                        rollingInterval: RollingInterval.Day,
                        fileSizeLimitBytes: 5242880,
                        rollOnFileSizeLimit: true)
                    .CreateLogger();
    
                Verboselog = new LoggerConfiguration()
                    .MinimumLevel.Verbose()
                  .WriteTo.File(System.Web.Hosting.HostingEnvironment.MapPath("~/ErrorLog/Verbose/log.txt"),
                        rollingInterval: RollingInterval.Day,
                        fileSizeLimitBytes: 5242880,
                        rollOnFileSizeLimit: true)
                    .CreateLogger();
    
                Fatallog = new LoggerConfiguration()
                    .MinimumLevel.Fatal()
                  .WriteTo.File(System.Web.Hosting.HostingEnvironment.MapPath("~/ErrorLog/Fatal/log.txt"),
                        rollingInterval: RollingInterval.Day,
                        fileSizeLimitBytes: 5242880,
                        rollOnFileSizeLimit: true)
                    .CreateLogger();
    
            }
    
            public static void WriteError(Exception ex, string message)
            {
                //Error - indicating a failure within the application or connected system
                Errorlog.Write(LogEventLevel.Error, ex, message);
            }
    
            public static void WriteWarning(Exception ex, string message)
            {
                //Warning - indicators of possible issues or service / functionality degradation
                Warninglog.Write(LogEventLevel.Warning, ex, message);
            }
    
            public static void WriteDebug(Exception ex, string message)
            {
                //Debug - internal control flow and diagnostic state dumps to facilitate 
                //          pinpointing of recognised problems
                Debuglog.Write(LogEventLevel.Debug, ex, message);
            }
    
            public static void WriteVerbose(Exception ex, string message)
            {
                // Verbose - tracing information and debugging minutiae; 
                //             generally only switched on in unusual situations
                Verboselog.Write(LogEventLevel.Verbose, ex, message);
            }
    
            public static void WriteFatal(Exception ex, string message)
            {
                //Fatal - critical errors causing complete failure of the application
                Fatallog.Write(LogEventLevel.Fatal, ex, message);
            }
    
            public static void WriteInformation(Exception ex, string message)
            {
                //Fatal - critical errors causing complete failure of the application
                Fatallog.Write(LogEventLevel.Fatal, ex, message);
            }
    
        }
    }

    在创建一个helper之后,接下来我们将在DefaultControllerIndex操作方法中调用这个helper类来测试和记录所有类型的消息。

    DefaultController的代码片段 

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using WebErrorLogging.Utilities;
    
    namespace WebErrorLogging.Controllers
    {
        public class DefaultController : Controller
        {
    
            // GET: Default
            public ActionResult Index()
            {
                try
                {
                    Helper.WriteDebug(null, "Debug ");
                    Helper.WriteWarning(null, "Warning ");
                    throw new NotImplementedException();
                }
                catch (Exception e)
                {
                    Helper.WriteError(e, "Error");
                    Helper.WriteFatal(e, "Fatal");
                    Helper.WriteVerbose(e, "Verbose");
                    throw;
                }
    
                return View();
            }
        }
    }

    在默认控制器中将Helper类添加到日志记录消息之后,我们将保存应用程序并对其进行测试。

    最后输出

    使用Serilog在文本文件中记录消息后的最终输出

    https://www.codeproject.com/KB/aspnet/1278018/image033.png

    使用Serilog在文本文件中完成日志消息之后,接下来我们将学习如何使用相同的Serilog将消息记录到SQL数据库中,但是使用另一个额外的NuGet包。

    Serilog记录到数据库中

    在这一部分中,我们将消息记录到SQL数据库中,以便我们将新的NuGet“ Serilog.Sinks.MSSqlServer ”添加到现有解决方案中。

    NuGet添加项目的引用

    https://www.codeproject.com/KB/aspnet/1278018/image034.png

    安装NuGet包之后,现在我们将编写一个类似的帮助类,就像我们在文本文件中记录消息一样,但这次我们将在数据库中记录消息。

    为此,我们首先要创建一个表。

    https://www.codeproject.com/KB/aspnet/1278018/image036.png

    用于创建表的脚本 

    CREATE TABLE [dbo].[Serilogs](
        [Id] [int] IDENTITY(1,1) NOT NULL Primary Key,
        [Message] [nvarchar](max) NULL,
        [MessageTemplate] [nvarchar](max) NULL,
        [Level] [nvarchar](128) NULL,
        [TimeStamp] [datetime] NULL,
        [Exception] [nvarchar](max) NULL,
        [Properties] [xml] NULL)

    在创建表之后,我们将编写一个帮助程序类HelperStoreSqlLog,它将集中记录并在Serilogs表中存储消息。

    在数据库中设置日志记录时,需要关注3个要点。

    1. web.config文件中检查数据库的正确连接字符串
    2. 在数据库中创建Serilogs表。
    3. 要使用WriteTo.MSSqlServer方法在数据库中写入消息和错误,此方法需要2个参数作为输入,一个是连接字符串,另一个是您的表。

    代码片段

    using System;
    using System.Collections.Generic;
    using System.Configuration;
    using System.Linq;
    using System.Web;
    using Serilog;
    using Serilog.Events;
    
    namespace WebErrorLogging.Utilities
    {
        public static class HelperStoreSqlLog
        {
            private static readonly ILogger Errorlog;
            private static readonly ILogger Warninglog;
            private static readonly ILogger Debuglog;
            private static readonly ILogger Verboselog;
            private static readonly ILogger Fatallog;
            private static readonly string ConnectionString = 
             ConfigurationManager.ConnectionStrings["ConnectionStringLogging"].ToString();
    
            static HelperStoreSqlLog()
            {
    
                Errorlog = new LoggerConfiguration()
                    .MinimumLevel.Error()
                    .WriteTo.MSSqlServer(ConnectionString, "Serilogs")
                    .CreateLogger();
    
                Warninglog = new LoggerConfiguration()
                    .MinimumLevel.Warning()
                    .WriteTo.MSSqlServer(ConnectionString, "Serilogs")
                    .CreateLogger();
    
                Debuglog = new LoggerConfiguration()
                    .MinimumLevel.Debug()
                    .WriteTo.MSSqlServer(ConnectionString, "Serilogs")
                    .CreateLogger();
    
                Verboselog = new LoggerConfiguration()
                    .MinimumLevel.Verbose()
                    .WriteTo.MSSqlServer(ConnectionString, "Serilogs")
                    .CreateLogger();
    
                Fatallog = new LoggerConfiguration()
                    .MinimumLevel.Fatal()
                    .WriteTo.MSSqlServer(ConnectionString, "Serilogs")
                    .CreateLogger();
    
            }
    
            public static void WriteError(Exception ex, string message)
            {
                //Error - indicating a failure within the application or connected system
                Errorlog.Write(LogEventLevel.Error, ex, message);
            }
    
            public static void WriteWarning(Exception ex, string message)
            {
                //Warning - indicators of possible issues or service / functionality degradation
                Warninglog.Write(LogEventLevel.Warning, ex, message);
            }
    
            public static void WriteDebug(Exception ex, string message)
            {
                //Debug - internal control flow and diagnostic 
                // state dumps to facilitate pinpointing of recognised problems
                Debuglog.Write(LogEventLevel.Debug, ex, message);
            }
    
            public static void WriteVerbose(Exception ex, string message)
            {
                // Verbose - tracing information and debugging minutiae; 
                //   generally only switched on in unusual situations
                Verboselog.Write(LogEventLevel.Verbose, ex, message);
            }
    
            public static void WriteFatal(Exception ex, string message)
            {
                //Fatal - critical errors causing complete failure of the application
                Fatallog.Write(LogEventLevel.Fatal, ex, message);
            }
    
            public static void WriteInformation(Exception ex, string message)
            {
                //Fatal - critical errors causing complete failure of the application
                Fatallog.Write(LogEventLevel.Fatal, ex, message);
            }
        }
    }

    在创建HelperStoreSqlLog之后,接下来我们将在DefaultControllerIndex操作方法中调用此HelperStoreSqlLog类来测试和记录所有类型的消息。

    代码片段

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using WebErrorLogging.Utilities;
    
    namespace WebErrorLogging.Controllers
    {
        public class DefaultController : Controller
        {
            // GET: Default   
            public ActionResult Index()
            {
                try
                {
                    HelperStoreSqlLog.WriteDebug(null, "Debug ");
                    HelperStoreSqlLog.WriteWarning(null, "Warning ");
                    throw new NotImplementedException();
                }
                catch (Exception e)
                {
                    HelperStoreSqlLog.WriteError(e, "Error");
                    HelperStoreSqlLog.WriteFatal(e, "Fatal");
                    HelperStoreSqlLog.WriteVerbose(e, "Verbose");
                    throw;
                }
                return View();
            }
        }
    }

    在默认控制器中将HelperStoreSqlLog类添加到日志消息后,我们将保存应用程序并对其进行测试。

     最后输出

    使用Serilog在数据库中记录消息后的最终输出

    https://www.codeproject.com/KB/aspnet/1278018/image037.png

    我们已经使用Serilog完成了日志消息,接下来我们将看到一些配置,我们可以使用Serilog中的appsettings来完成。

    Serilog.Settings.AppSettings    

    到目前为止,我们已经完全使用代码配置了Serilog,现在我们将使用appsettings在文本文件中执行相同的日志消息配置。

    https://www.codeproject.com/KB/aspnet/1278018/image038.png

    安装软件包之后,接下来我们将在appsettings中进行配置设置。

    使用Serilog.Settings.AppSettings在文本文件中记录消息的Appsettings 

    <appSettings>
    
        <add key="webpages:Version" value="3.0.0.0" />
        <add key="webpages:Enabled" value="false" />
        <add key="ClientValidationEnabled" value="true" />
        <add key="UnobtrusiveJavaScriptEnabled" value="true" />
    
        <!-- appSettings for Serilog Storing in Text file  -->
        <add key="serilog:minimum-level" value="Verbose" />
        <add key="serilog:using:File" value="Serilog.Sinks.File" />
        <add key="serilog:write-to:File.path" 
    
             value="E:\DemoProject\WebErrorLogging\WebErrorLogging\ErrorLog\Error\log.txt" />
        <add key="serilog:write-to:File.fileSizeLimitBytes" value="1234567" />
        <add key="serilog:write-to:RollingFile.retainedFileCountLimit" value="10" />
        <add key="serilog:write-to:File.rollingInterval" value="Day"/>
        <!-- appSettings for Serilog Storing in Text file  -->
    
      </appSettings>

    appsettings中进行配置后,接下来我们将对代码进行一些更改,使其适用于此配置。

    代码片段

    代码中配置的主要部分是使用“.ReadFrom.AppSettings()”方法从我们配置的appsetting中读取配置。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using Serilog;
    using WebErrorLogging.Utilities;
    
    namespace WebErrorLogging.Controllers
    {
        public class DefaultController : Controller
        {
            private ILogger _errorlog;
            public ActionResult Index()
            {
                try
                {
                    throw new NotImplementedException();
                }
                catch (Exception e)
                {
                    _errorlog = new LoggerConfiguration()
                        .ReadFrom.AppSettings()
                        .CreateLogger();
    
                    _errorlog.Error(e, "DefaultController");
                    throw;
                }
                return View();
            }
        }
    }

    添加Serilog.Settings.AppSettings后,接下来我们将保存应用程序并对其进行测试。

     最后输出

    https://www.codeproject.com/KB/aspnet/1278018/image040.png

    https://www.codeproject.com/KB/aspnet/1278018/image042.png

    最后,我们使用serilog完成了日志消息,接下来我们将要看看最后一个最好的日志记录选项是ELMAH

    ELMAH

    什么是ELMAH

    错误日志记录模块和处理程序(ELMAH)。

    • 记录几乎所有未处理的异常。
    • 用于远程查看记录的异常的整个日志的网页。
    • 用于远程查看任何一个已记录异常的完整详细信息的网页,包括彩色堆栈跟踪。
    • 在许多情况下,您可以查看ASP.NET为给定异常生成的原始黄色死亡屏幕,即使关闭了自定义错误模式也是如此。
    • 每个错误发生时的电子邮件通知。
    • 来自日志的最后15个错误的RSS提要。

    参考来自:——https://code.google.com/archive/p/elmah/

    我们将看到在Elmah中日志记录的一种方式

    1. 在数据库中

    在这部分中,我们将使用ELMAH将错误记录到数据库中。首先,我们需要从NuGet包中安装2ELMAH包。

    ELMAH记录数据库

    NuGet添加项目的引用

    1. Elmah.MVC

    https://www.codeproject.com/KB/aspnet/1278018/image044.png

    2. elmah.sqlserver

    https://www.codeproject.com/KB/aspnet/1278018/image046.png

    安装elmah.sqlserver包时,它创建App_Readme文件夹,在该文件夹中,它添加了SQL查询脚本,用于创建Elmah的表和存储过程。

    https://www.codeproject.com/KB/aspnet/1278018/image048.png

    以类似的方式,在安装elmahelmah.sqlserver包时,它将XML配置添加到该配置文件中的web.config文件中,您将在此处看到elmah-sqlserver标记添加到连接字符串部分中,您需要提供数据库连接设置。

    安装elmah.sqlserver包后添加的elmah-sqlserver标记片段。

    <connectionStrings>
      <!-- TODO: Replace the ****'s with the correct entries -->
      <add name="elmah-sqlserver"
           connectionString="Data Source=****;User ID=****;Password=****;Initial Catalog=****;"
           providerName="System.Data.SqlClient" />
    </connectionStrings>

    用正确的数据库凭据替换“****”以连接到数据库。

    <connectionStrings>
        <!-- TODO: Replace the ****'s with the correct entries -->
        <add name="elmah-sqlserver" 
    
             connectionString="Data Source=SAI-PC\SQLEXPRESS;User ID=sa;Password=Pass$123;
             Initial Catalog=LoggingDatabase;" 
    
             providerName="System.Data.SqlClient" />
      </connectionStrings>

    设置连接字符串后,接下来你需要在数据库中执行Elmah.Sqlserver脚本,以生成Elmah的表和存储过程。

    下面是执行Elmah SqlServer 脚本后的快照

    https://www.codeproject.com/KB/aspnet/1278018/image049.png

    现在我们已经配置了ELMAH,让我们创建一个带有Index操作方法的默认控制器,这会抛出一个错误。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    
    namespace WebErrorLogging.Controllers
    {
        public class DefaultController : Controller
        {
            public ActionResult Index()
            {
                try
                {
                    throw new NotImplementedException();
                }
                catch (Exception)
                {
                    throw;
                }
                return View();
            }
        }
    }

    添加ControllerIndex操作之后,接下来我们将保存应用程序并对其进行测试。

    输出

    使用ELMAH在数据库中记录错误后的最终输出

    https://www.codeproject.com/KB/aspnet/1278018/image050.png

    现在我们在浏览器上存储错误查看错误,我们需要在web.config文件中进行配置,如下所示。

    1.web.config中的<system.web>元素下添加的片段

    https://www.codeproject.com/KB/aspnet/1278018/image052.png

    片段

    <!--Newly Added-->  
        <httpHandlers>  
          <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />  
        </httpHandlers>  
    <!--Newly Added-->

    2.要在web.config中的<system.webServer>元素下添加的代码段

    https://www.codeproject.com/KB/aspnet/1278018/image054.png

    片段

    <!--Newly Added-->
    <handlers>
      <add name="Elmah" verb="POST,GET,HEAD" path="elmah.axd"
           type="Elmah.ErrorLogPageFactory, Elmah" />
    </handlers>
    <!--Newly Added-->

    3.要添加到web.config中的 <elmah>元素中的代码段 

    https://www.codeproject.com/KB/aspnet/1278018/image056.png

    片段

    <elmah>
      <security allowRemoteAccess="0" />
      <errorLog type="Elmah.SqlErrorLog, Elmah" connectionStringName="elmah-sqlserver" />
    </elmah>

    什么是allowRemoteAccess

    https://www.codeproject.com/KB/aspnet/1278018/image058.png

    默认情况下,不允许远程访问/elmah.axd,这意味着请求除了localhost之外的其他所有URL,返回HTTP状态代码403。不建议打开远程访问ELMAH UI,但是在某些情况下,它可能是有道理的。将allowRemoteAccess设置为1true,可以在面向公众的网站上访问/elmah.axd

    参考自:——https://blog.elmah.io/elmah-security-and-allowremoteaccess-explained/

    现在要查看网页上的错误,您需要在URL http://localhost:55946/elmah.axd末尾输入“elmah.axd”  如下所示。

    如果您已在IIS中托管您的站点,则需要输入URL,例如  http://xyz.com/elmah.axd

    最后输出

    https://www.codeproject.com/KB/aspnet/1278018/image059.png

    最后,我们学习了如何使用最适合以简单方式记录ASP.NET MVC应用程序的所有4个记录器。这些记录器已被许多公司用于真是生成应用中。

    源代码Log4Net的链接:——https://github.com/saineshwar/WebErrorLogging_Log4Net

    源代码NLog的链接:——https://github.com/saineshwar/WebErrorLogging_NLog

    源代码Serilog的链接:——https://github.com/saineshwar/WebErrorLogging_Serilog

    源代码链接ELMAH:——https://github.com/saineshwar/WebErrorLogging_ELMAH

     

    原文地址:https://www.codeproject.com/Articles/1278018/Best-Logging-libraries-for-ASP-NET-MVC

     

    展开全文
  • Mysql访问日志记录

    千次阅读 2018-11-14 10:31:54
    Mysql访问日志记录 假设这么一个情况,你是某公司mysql-DBA,某日突然公司数据库中的所有被人为删了。 尽管有数据备份,但是因服务停止而造成的损失上千万,现在公司需要查出那个做删除操作的人。 但是拥有数据库...
  • AOP实现操作日志记录

    千次阅读 2018-06-22 16:36:19
    一、设计: 操作日志记录 根据业务场景 一般是需要记录下数据修改更新的日志,查询类可以忽略。 所以需要对指定的某些方法进行记录。这块希望可以结合注解灵活操作,对于注解的方法进行日志记录操作日志表设计如下...
  • MySQL慢查询日志记录和分析

    千次阅读 2019-01-08 21:05:41
    一、引言 在日常的开发中,有时候会有用户反馈说网站的响应速度...这个时候,我们就需要慢慢分析,我们可以通过开启慢查询日志,来监控生产环境上有没有执行特别慢的SQL,如果有,我们可以定位到是哪一条SQL,从而...
  • Spring AOP 实现日志记录功能

    万次阅读 2018-02-12 12:00:36
    最近项目中需要记录日志,跟大家想的一样 ,利用spring aop去实现,之前也参考了一些代码,自己实现了...本文利用注解实现了一套可以扩展等日志记录模块。1. 定义注解@Target(ElementType.METHOD) @Retention(Rete...
  • 我们开发的业务系统通常会提供给很多人使用,那在使用的过程中,日志系统变得非常重要。 日志系统记录的用户行为有以下的作用: 从系统用户角度看:它展示了用户...对于这样的日志记录,我们可以在相关记录点添加对...
  • Spring Boot各种日志记录方式详解

    万次阅读 多人点赞 2018-04-10 21:18:39
    Spring Boot在所有内部日志中使用Commons Logging,但是默认配置也提供了对常用日志的支持,如:Java Util Logging,Log4J, Log4J2和Logback。每种Logger都可以通过配置使用控制台或者文件输出日志...
  • SpringBoot-3:集成SLF4J+logback进行日志记录

    万次阅读 多人点赞 2019-08-20 21:02:25
    SLF4J+logback进行日志记录1 yml配置文件2 logback.xml常用配置详解2.1 根节点(configuration)2.1.1 读取配置文件:springProperty2.1.2 设置上下文名称:contextName2.1.3 设置变量:property2.2 appender节点...
  • springboot基于AOP实现操作日志记录

    千次阅读 2019-03-27 12:12:29
    一、需求 在开发系统时,尤其是后台管理系统,几乎...本例中,采用AOP来实现日志记录功能,一个注解即可实现同样的效果。 1、新建一个注解SysLogPoint,用于标识需要记录日志的切面 package com.yclouds.common....
  • kettle日志记录

    千次阅读 2019-05-26 21:55:18
    2016/09/08 17:22:22 - 日志记录 - 为了转换解除补丁开始 [日志记录] 2016/09/08 17:22:22 - 表输出.0 - Connected to database [DB2] (commit=1000) 2016/09/08 17:22:22 - 表输入.0 - Finished reading query, ...
  • TP5实现日志记录

    千次阅读 2018-11-16 09:35:27
    TP5实现日志记录 1.安装 psr/log composer require psr/log --指令下载到相应的目录 它的作用就是提供一套接口,实现正常的日志功能! 我们可以来细细的分析一下,LoggerInterface.php &lt;?php ...
  • PHP 接口日志记录

    千次阅读 2020-11-04 13:57:40
    error_log(date("Y-m-d H:i:s",time()).'【getProducts获取商品编号SKU接口的getFieldById】'."\n".serialize($aCat)."\n\n", 3, $this->FILE); error_log(date('Y-m-d H:i:s',time())."\tnow=".$now."\n",3,...
  • gorm中关于sql日志记录 golang

    千次阅读 2020-10-23 15:44:16
    记录sql的慢查询日志和错误日志是很有必要的。 gorm 版本 gorm.io/gorm v1.20.1 gorm提供了默认的logger实现: if config.Logger == nil { config.Logger = logger.Default } Default = New(log.New(os.Stdout, "\...
  • DNS的日志记录参数详解及示例

    千次阅读 2019-11-25 00:32:43
    DNS的日志记录参数详解及示例 在"man named.conf"中能找到关于日志的配置选项: logging { category string { string; ... }; channel string { buffered boolean; file quoted_string [...
  • spring aop实现操作日志记录

    千次阅读 2019-02-28 17:42:03
    spring aop实现操作日志记录 此次的目的是实现 对 controller中的方法执行情况进行记录,记录的有方法执行时间,操作人,请求的路径,方法的入参,模块,功能等。并实现利用注解的方式实现对被操作方法的简单注释...
  • AOP实现日志记录(Aspect)

    万次阅读 2018-01-08 12:06:44
    * @Description: 日志记录AOP实现 * @author shaojian.yu * @date 2014年11月3日 下午1:51:59 * */ @Aspect @Component public class LogAspect { private final Logger logger = LoggerFactory.getLogger...
  • ThinkPHP日志记录

    千次阅读 2018-07-13 15:14:49
    ThinkPHP日志记录(摘自TP手册)日志的处理工作是由系统自动进行的,在开启日志记录的情况下,会记录下允许的日志级别的所有日志信息。其中,为了性能考虑,SQL日志级别必须在调试模式开启下有效,否则就不会记录。 ...
  • 数据库之日志记录

    千次阅读 2019-08-14 17:29:23
    日志一般分成Undo与Redo:Undo一般用于事务的取消与回滚,记录的是数据被修改前的值,Redo一般用于恢复已确认但未写入数据库的数据,记录的是数据修改后的值,例如:数据库忽然断电重启,数据库启动时一般要做一致性...
  • CRT 开启日志记录功能

    千次阅读 2020-07-08 17:18:16
    查了一下CRT有自动记录日志的功能,用了一段时间发现挺好用的,记录一下配置步骤。 1、CRT版本 2、配置步骤 1、点击导航栏的Options -> Global Options 2、点击General -> Default Dession,选择Edit ...
  • 业务日志记录规范

    千次阅读 2019-07-01 09:50:15
    日志服务提供大规模日志实时查询与分析能力(LogSearch/Analytics),开启索引后,可以对日志数据进行统计与查询。 查询分析语句格式 对采集到的日志数据进行实时查询分析时,需要输入查询分析语句(Query)。 由...
  • 应用日志记录规范

    千次阅读 2016-12-31 21:47:27
    一、日志分类 1、按等级分类 1)TRACE级、DEBUG级:理论上“不属于错误”,只是打印一些状态、提示信息,以便开发过程中观察,开发完成、正式上线后,要把它们都屏蔽掉。 2)INFO级: 理论上”不属于错误”...
  • YII2中实现操作日志记录

    千次阅读 2018-04-27 16:44:17
    操作日志记录 对操作日志进行记录,这个可以清晰的对用户(管理员)的操作行为进行记录。这个对于后期的一些必要查询等操作是很方便的。 YII2日志框架 Yii提供了一个强大的日志框架,这个框架具有高度的可定制...
  • Springbooot中自定义注解,实现日志记录 Spring中有很多的注解,通过简单的注解就能实现当时在xml文件中的各种配置,非常的方便,在项目中我们可以通过自定义注解实现一些功能,也会很方便,比如说在需要的方法上...
  • MySQL的操作日志记录

    千次阅读 2018-12-05 17:00:46
    如何配置mysql数据库的操作日志及如何查看mysql的操作日志记录 MySQL的几种操作日志 1、错误日志 log_error (主要是记录启动、运行、停止mysql时出现的致命问题,系统级别的错误记录) 2、查询日志 log (主要...
  • 前言 个人觉得开发中比较重要的一点就是...在这里我需要的是一个简单实用、轻巧的日志记录程序,log4plus对我有点臃肿,所以才自己花店时间写了一个简单的日志记录类。 日志类实现 刚开始想的是为了避免大量的读写程序
  • JAVA实现通用日志记录

    万次阅读 2016-07-11 17:43:37
    使用spring aop实现统一日志记录
  • 最近项目中需要记录服务端访问...本文只介绍Controller层日志记录,不涉及Service层与Mapper层日志记录: Service层日志监控:SpringBoot通过AOP实现系统日志记录(二)-Service层日志监控 Mapper层日志监控:Spr...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 953,296
精华内容 381,318
关键字:

日志记录