Commit d503df1208abb2f8bc8c2d309b3ce955040ff0bf

Authored by 徐烜
1 parent 7dfd8bc8

1、添加ModuleOperatorLog实体对象,并创建与之对应的logback的ModuleOperatorDbAppender扩展

2、添加Log相关枚举的Jpa convert,用于ModuleOperatorLog实体枚举类型属性
3、添加PlanLogger,统一记录排班计划的创建修改的日志(TODO:之后还要修改)
4、添加TimetableLogger,统一记录时刻表的创建,导入,修改的日志(TODO:之后还要修改)
5、修改logback.xml配置,配置append扩展日志(TODO:之后还要修改)
6、修改pom.xml配置,添加bsth-control_v2-plan_module-common依赖
... ... @@ -295,6 +295,13 @@
295 295 <artifactId>jpinyin</artifactId>
296 296 <version>1.1.8</version>
297 297 </dependency>
  298 +
  299 + <!-- plan common工程依赖 -->
  300 + <dependency>
  301 + <groupId>com.bsth.control_v2</groupId>
  302 + <artifactId>plan_module-common</artifactId>
  303 + <version>1.0-SNAPSHOT</version>
  304 + </dependency>
298 305 </dependencies>
299 306  
300 307 <dependencyManagement>
... ...
src/main/java/com/bsth/entity/schedule/log/ModuleOperatorLog.java 0 → 100644
  1 +package com.bsth.entity.schedule.log;
  2 +
  3 +import com.bsth.control_v2.plan_module.common.enums.log.ModuleOperatorType;
  4 +import com.bsth.control_v2.plan_module.common.enums.log.ModuleType;
  5 +import com.bsth.control_v2.plan_module.common.enums.log.OperatorType;
  6 +import com.bsth.entity.schedule.log.convert.ModuleOperatorTypeConverter;
  7 +import com.bsth.entity.schedule.log.convert.ModuleTypeConverter;
  8 +import com.bsth.entity.schedule.log.convert.OperatorTypeConverter;
  9 +
  10 +import javax.persistence.*;
  11 +import java.io.Serializable;
  12 +import java.util.Date;
  13 +
  14 +/**
  15 + * 模块业务操作日志。
  16 + */
  17 +@Entity
  18 +@Table(name = "bsth_c_s_op_log")
  19 +public class ModuleOperatorLog implements Serializable {
  20 +
  21 + /** 主键Id */
  22 + @Id
  23 + @GeneratedValue
  24 + private Long id;
  25 +
  26 + /** 日志插入时间 */
  27 + @Column(nullable = false)
  28 + private Long timestmp;
  29 +
  30 + /** 模块类型 */
  31 + @Column(nullable = false)
  32 + @Convert(converter = ModuleTypeConverter.class)
  33 + private ModuleType moduleType;
  34 +
  35 + @Column(nullable = false)
  36 + @Convert(converter = OperatorTypeConverter.class)
  37 + private OperatorType operatorType;
  38 +
  39 + /** 模块操作类型 */
  40 + @Column(nullable = false)
  41 + @Convert(converter = ModuleOperatorTypeConverter.class)
  42 + private ModuleOperatorType moduleOperatorType;
  43 +
  44 + /** 日志描述 */
  45 + @Column(nullable = false)
  46 + private String message;
  47 +
  48 + /** 日志级别 */
  49 + @Column(nullable = false)
  50 + private String levelName;
  51 + /** 模块业务操作开始时间 */
  52 + @Column(nullable = false)
  53 + private Date opStartTime;
  54 + /** 模块业务操作结束时间 */
  55 + @Column(nullable = false)
  56 + private Date opEndTime;
  57 + /** 模块业务操作人姓名 */
  58 + @Column(nullable = false)
  59 + private String opUserName;
  60 + /** 模块业务操作人工号 */
  61 + @Column(nullable = false)
  62 + private String opUserCode;
  63 + /** 关联的实体class名 */
  64 + @Column(nullable = false)
  65 + private String opEntityClass;
  66 + /** 操作前的实体json */
  67 + @Column(nullable = false)
  68 + private String opBeforeJson;
  69 + /** 操作后的实体json */
  70 + @Column(nullable = false)
  71 + private String opAfterJson;
  72 +
  73 + /** 备用-可选参数1 */
  74 + private String optArg1;
  75 + /** 备用-可选参数2 */
  76 + private String optArg2;
  77 + /** 备用-可选参数3 */
  78 + private String optArg3;
  79 +
  80 + public Long getId() {
  81 + return id;
  82 + }
  83 +
  84 + public void setId(Long id) {
  85 + this.id = id;
  86 + }
  87 +
  88 + public Long getTimestmp() {
  89 + return timestmp;
  90 + }
  91 +
  92 + public void setTimestmp(Long timestmp) {
  93 + this.timestmp = timestmp;
  94 + }
  95 +
  96 + public ModuleType getModuleType() {
  97 + return moduleType;
  98 + }
  99 +
  100 + public void setModuleType(ModuleType moduleType) {
  101 + this.moduleType = moduleType;
  102 + }
  103 +
  104 + public OperatorType getOperatorType() {
  105 + return operatorType;
  106 + }
  107 +
  108 + public void setOperatorType(OperatorType operatorType) {
  109 + this.operatorType = operatorType;
  110 + }
  111 +
  112 + public ModuleOperatorType getModuleOperatorType() {
  113 + return moduleOperatorType;
  114 + }
  115 +
  116 + public void setModuleOperatorType(ModuleOperatorType moduleOperatorType) {
  117 + this.moduleOperatorType = moduleOperatorType;
  118 + }
  119 +
  120 + public String getMessage() {
  121 + return message;
  122 + }
  123 +
  124 + public void setMessage(String message) {
  125 + this.message = message;
  126 + }
  127 +
  128 + public String getLevelName() {
  129 + return levelName;
  130 + }
  131 +
  132 + public void setLevelName(String levelName) {
  133 + this.levelName = levelName;
  134 + }
  135 +
  136 + public Date getOpStartTime() {
  137 + return opStartTime;
  138 + }
  139 +
  140 + public void setOpStartTime(Date opStartTime) {
  141 + this.opStartTime = opStartTime;
  142 + }
  143 +
  144 + public Date getOpEndTime() {
  145 + return opEndTime;
  146 + }
  147 +
  148 + public void setOpEndTime(Date opEndTime) {
  149 + this.opEndTime = opEndTime;
  150 + }
  151 +
  152 + public String getOpUserName() {
  153 + return opUserName;
  154 + }
  155 +
  156 + public void setOpUserName(String opUserName) {
  157 + this.opUserName = opUserName;
  158 + }
  159 +
  160 + public String getOpUserCode() {
  161 + return opUserCode;
  162 + }
  163 +
  164 + public void setOpUserCode(String opUserCode) {
  165 + this.opUserCode = opUserCode;
  166 + }
  167 +
  168 + public String getOpEntityClass() {
  169 + return opEntityClass;
  170 + }
  171 +
  172 + public void setOpEntityClass(String opEntityClass) {
  173 + this.opEntityClass = opEntityClass;
  174 + }
  175 +
  176 + public String getOpBeforeJson() {
  177 + return opBeforeJson;
  178 + }
  179 +
  180 + public void setOpBeforeJson(String opBeforeJson) {
  181 + this.opBeforeJson = opBeforeJson;
  182 + }
  183 +
  184 + public String getOpAfterJson() {
  185 + return opAfterJson;
  186 + }
  187 +
  188 + public void setOpAfterJson(String opAfterJson) {
  189 + this.opAfterJson = opAfterJson;
  190 + }
  191 +
  192 + public String getOptArg1() {
  193 + return optArg1;
  194 + }
  195 +
  196 + public void setOptArg1(String optArg1) {
  197 + this.optArg1 = optArg1;
  198 + }
  199 +
  200 + public String getOptArg2() {
  201 + return optArg2;
  202 + }
  203 +
  204 + public void setOptArg2(String optArg2) {
  205 + this.optArg2 = optArg2;
  206 + }
  207 +
  208 + public String getOptArg3() {
  209 + return optArg3;
  210 + }
  211 +
  212 + public void setOptArg3(String optArg3) {
  213 + this.optArg3 = optArg3;
  214 + }
  215 +}
... ...
src/main/java/com/bsth/entity/schedule/log/convert/ModuleOperatorTypeConverter.java 0 → 100644
  1 +package com.bsth.entity.schedule.log.convert;
  2 +
  3 +import com.bsth.control_v2.plan_module.common.enums.log.ModuleOperatorType;
  4 +
  5 +import javax.persistence.AttributeConverter;
  6 +import javax.persistence.Convert;
  7 +
  8 +@Convert
  9 +public class ModuleOperatorTypeConverter implements AttributeConverter<ModuleOperatorType, String> {
  10 + @Override
  11 + public String convertToDatabaseColumn(ModuleOperatorType attribute) {
  12 + return attribute.getDicDesc();
  13 + }
  14 +
  15 + @Override
  16 + public ModuleOperatorType convertToEntityAttribute(String dbData) {
  17 + return ModuleOperatorType.fromDicDesc(dbData);
  18 + }
  19 +}
... ...
src/main/java/com/bsth/entity/schedule/log/convert/ModuleTypeConverter.java 0 → 100644
  1 +package com.bsth.entity.schedule.log.convert;
  2 +
  3 +import com.bsth.control_v2.plan_module.common.enums.log.ModuleType;
  4 +
  5 +import javax.persistence.AttributeConverter;
  6 +import javax.persistence.Convert;
  7 +
  8 +@Convert
  9 +public class ModuleTypeConverter implements AttributeConverter<ModuleType, String> {
  10 + @Override
  11 + public String convertToDatabaseColumn(ModuleType attribute) {
  12 + return attribute.getDicDesc();
  13 + }
  14 +
  15 + @Override
  16 + public ModuleType convertToEntityAttribute(String dbData) {
  17 + return ModuleType.fromDicDesc(dbData);
  18 + }
  19 +}
... ...
src/main/java/com/bsth/entity/schedule/log/convert/OperatorTypeConverter.java 0 → 100644
  1 +package com.bsth.entity.schedule.log.convert;
  2 +
  3 +import com.bsth.control_v2.plan_module.common.enums.log.OperatorType;
  4 +
  5 +import javax.persistence.AttributeConverter;
  6 +import javax.persistence.Convert;
  7 +
  8 +@Convert
  9 +public class OperatorTypeConverter implements AttributeConverter<OperatorType, String> {
  10 + @Override
  11 + public String convertToDatabaseColumn(OperatorType attribute) {
  12 + return attribute.getDicDesc();
  13 + }
  14 +
  15 + @Override
  16 + public OperatorType convertToEntityAttribute(String dbData) {
  17 + return OperatorType.fromDicDesc(dbData);
  18 + }
  19 +}
... ...
src/main/java/com/bsth/service/schedule/log/ModuleOperatorDbAppender.java 0 → 100644
  1 +package com.bsth.service.schedule.log;
  2 +
  3 +import ch.qos.logback.classic.spi.ILoggingEvent;
  4 +import ch.qos.logback.core.db.DBAppenderBase;
  5 +import ch.qos.logback.core.db.DBHelper;
  6 +import com.bsth.control_v2.plan_module.common.exception.PlanModuleException;
  7 +import com.bsth.entity.schedule.log.ModuleOperatorLog;
  8 +
  9 +import java.lang.reflect.Method;
  10 +import java.sql.Connection;
  11 +import java.sql.Date;
  12 +import java.sql.PreparedStatement;
  13 +
  14 +/**
  15 + * 自定义模块业务操作日志logback自定义appender。
  16 + */
  17 +public class ModuleOperatorDbAppender extends DBAppenderBase<ILoggingEvent> {
  18 + /** JDBC3.0中的绑定表自增的主键值,获取result需要此方法 */
  19 + protected static final Method GET_GENERATED_KEYS_METHOD;
  20 + /** 插入日志sql */
  21 + protected static final String INSERT_SQL;
  22 +
  23 + @Override
  24 + public void start() {
  25 + super.start();
  26 + }
  27 +
  28 + @Override
  29 + protected Method getGeneratedKeysMethod() {
  30 + return GET_GENERATED_KEYS_METHOD;
  31 + }
  32 +
  33 + @Override
  34 + protected String getInsertSQL() {
  35 + return INSERT_SQL;
  36 + }
  37 +
  38 + @Override
  39 + protected void subAppend(ILoggingEvent iLoggingEvent, Connection connection, PreparedStatement insertStatement) throws Throwable {
  40 + // 参数判定,第一个参数必须是ModuleOperatorLog,后续的参数最多取3个
  41 + if (iLoggingEvent.getArgumentArray().length == 0) {
  42 + throw new PlanModuleException(iLoggingEvent.getLoggerName() + "日志参数必须大于1");
  43 + } else {
  44 + Object arg1 = iLoggingEvent.getArgumentArray()[0];
  45 + if (!(arg1 instanceof ModuleOperatorLog)) {
  46 + throw new PlanModuleException(iLoggingEvent.getLoggerName() + "日志第一个参数必须是ModuleOperatorLog类型");
  47 + } else {
  48 + ModuleOperatorLog arg1_log = (ModuleOperatorLog) arg1;
  49 + // insertStatement设定ModuleOperatorLog关联数据
  50 + insertStatement.setLong(1, iLoggingEvent.getTimeStamp());
  51 + insertStatement.setString(2, arg1_log.getModuleType().getDicDesc());
  52 + insertStatement.setString(3, arg1_log.getOperatorType().getDicDesc());
  53 + insertStatement.setString(4, arg1_log.getModuleOperatorType().getDicDesc());
  54 + insertStatement.setString(5, arg1_log.getMessage());
  55 + insertStatement.setString(6, iLoggingEvent.getLevel().levelStr);
  56 + insertStatement.setDate(7, new Date(arg1_log.getOpStartTime().getTime()));
  57 + insertStatement.setDate(8, new Date(arg1_log.getOpEndTime().getTime()));
  58 + insertStatement.setString(9, arg1_log.getOpUserName());
  59 + insertStatement.setString(10, arg1_log.getOpUserCode());
  60 + insertStatement.setString(11, arg1_log.getOpEntityClass());
  61 + insertStatement.setString(12, arg1_log.getOpBeforeJson());
  62 + insertStatement.setString(13, arg1_log.getOpAfterJson());
  63 + insertStatement.setString(14, "");
  64 + insertStatement.setString(15, "");
  65 + insertStatement.setString(16, "");
  66 +
  67 + for (int i = 1; i < iLoggingEvent.getArgumentArray().length; i++) {
  68 + if (i == 1) {
  69 + insertStatement.setString(14, iLoggingEvent.getArgumentArray()[i].toString());
  70 + } else if (i == 2) {
  71 + insertStatement.setString(15, iLoggingEvent.getArgumentArray()[i].toString());
  72 + } else if (i == 3) {
  73 + insertStatement.setString(16, iLoggingEvent.getArgumentArray()[i].toString());
  74 + } else {
  75 + break;
  76 + }
  77 + }
  78 +
  79 + int updateCount = insertStatement.executeUpdate();
  80 + if (updateCount != 1) {
  81 + addWarn("Failed to insert loggingEvent");
  82 + }
  83 + }
  84 + }
  85 +
  86 + }
  87 +
  88 + @Override
  89 + protected void secondarySubAppend(ILoggingEvent iLoggingEvent, Connection connection, long l) throws Throwable {
  90 + // TODO:
  91 + }
  92 +
  93 + @Override
  94 + public void append(ILoggingEvent eventObject) {
  95 + Connection connection = null;
  96 + PreparedStatement insertStatement = null;
  97 + try {
  98 + connection = this.connectionSource.getConnection();
  99 + connection.setAutoCommit(false);
  100 + insertStatement = connection.prepareStatement(INSERT_SQL);
  101 + synchronized (this) {
  102 + subAppend(eventObject, connection, insertStatement);
  103 + }
  104 + // TODO:可能以后需要secondarySubAppend
  105 +
  106 + connection.commit();
  107 + } catch (Throwable exp) {
  108 + exp.printStackTrace();
  109 + this.addError("problem appending event", exp);
  110 + } finally {
  111 + DBHelper.closeStatement(insertStatement);
  112 + DBHelper.closeConnection(connection);
  113 + }
  114 + }
  115 +
  116 + static {
  117 + StringBuilder sql = new StringBuilder();
  118 + sql.append("insert into bsth_c_s_op_log (");
  119 + sql.append("timestmp, ");
  120 + sql.append("module_type, ");
  121 + sql.append("operator_type, ");
  122 + sql.append("module_operator_type, ");
  123 + sql.append("message, ");
  124 + sql.append("level_name, ");
  125 + sql.append("op_start_time, ");
  126 + sql.append("op_end_time, ");
  127 + sql.append("op_user_name, ");
  128 + sql.append("op_user_code, ");
  129 + sql.append("op_entity_class, ");
  130 + sql.append("op_before_json, ");
  131 + sql.append("op_after_json, ");
  132 + sql.append("opt_arg1, ");
  133 + sql.append("opt_arg2, ");
  134 + sql.append("opt_arg3) ");
  135 + sql.append(" values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)");
  136 + INSERT_SQL = sql.toString();
  137 +
  138 + Method getGeneratedKeysMethod;
  139 + try {
  140 + getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[])null);
  141 + } catch (Exception exp) {
  142 + getGeneratedKeysMethod = null;
  143 + }
  144 +
  145 + GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
  146 + }
  147 +}
... ...
src/main/java/com/bsth/service/schedule/log/PlanLogger.java 0 → 100644
  1 +package com.bsth.service.schedule.log;
  2 +
  3 +import com.bsth.control_v2.plan_module.common.dto.schedule.PlanInfoDto;
  4 +import com.bsth.control_v2.plan_module.common.enums.log.ModuleOperatorType;
  5 +import com.bsth.control_v2.plan_module.common.enums.log.ModuleType;
  6 +import com.bsth.control_v2.plan_module.common.enums.log.OperatorType;
  7 +import com.bsth.entity.schedule.SchedulePlan;
  8 +import com.bsth.entity.schedule.log.ModuleOperatorLog;
  9 +import org.slf4j.Logger;
  10 +import org.slf4j.LoggerFactory;
  11 +import org.springframework.stereotype.Service;
  12 +
  13 +import java.util.Date;
  14 +
  15 +/**
  16 + * 排班计划日志。
  17 + */
  18 +@Service
  19 +public class PlanLogger {
  20 + /** 日志记录器 */
  21 + private static final Logger LOGGER = LoggerFactory.getLogger(PlanLogger.class);
  22 +
  23 + /**
  24 + * 生成排班计划日志。
  25 + * @param startTime 操作开始时间
  26 + * @param endTime 操作结束时间
  27 + */
  28 + public void createLog(Date startTime, Date endTime) {
  29 + ModuleOperatorLog moduleOperatorLog = new ModuleOperatorLog();
  30 + moduleOperatorLog.setMessage("排班生成!");
  31 + moduleOperatorLog.setModuleType(ModuleType.SCHEDULE);
  32 + moduleOperatorLog.setOperatorType(OperatorType.NEW);
  33 + moduleOperatorLog.setModuleOperatorType(ModuleOperatorType.SCHEDULE_NEW);
  34 + moduleOperatorLog.setOpStartTime(startTime);
  35 + moduleOperatorLog.setOpEndTime(endTime);
  36 + moduleOperatorLog.setOpUserName("root");
  37 + moduleOperatorLog.setOpUserCode("root");
  38 + moduleOperatorLog.setOpEntityClass(SchedulePlan.class.getName());
  39 + moduleOperatorLog.setOpBeforeJson("{}");
  40 + moduleOperatorLog.setOpAfterJson("{}");
  41 +
  42 + // 如果需要opArg1,opArg2,opArg3,从第2个参数加起,如:LOGGER.info("", {moduleOperatorLog}, {opArg1}, {opArg2})
  43 + LOGGER.info("排班计划生成日志:{}", moduleOperatorLog);
  44 + }
  45 +
  46 + /**
  47 + * 修改排班计划明细日志。
  48 + * @param startTime 操作开始时间
  49 + * @param beforeData 修改前数据
  50 + * @param endTime 操作结束时间
  51 + * @param afterData 修改后数据
  52 + */
  53 + public void modifyInfoLog(Date startTime, PlanInfoDto beforeData, Date endTime, PlanInfoDto afterData) {
  54 + // TODO:
  55 + }
  56 +}
... ...
src/main/java/com/bsth/service/schedule/log/TimetableLogger.java 0 → 100644
  1 +package com.bsth.service.schedule.log;
  2 +
  3 +import com.bsth.control_v2.plan_module.common.dto.schedule.timetable.TTInfoDetailDto;
  4 +import com.bsth.control_v2.plan_module.common.dto.schedule.timetable.TTInfoDto;
  5 +import org.slf4j.Logger;
  6 +import org.slf4j.LoggerFactory;
  7 +import org.springframework.stereotype.Service;
  8 +
  9 +import java.util.Date;
  10 +
  11 +/**
  12 + * 时刻表日志。
  13 + */
  14 +@Service
  15 +public class TimetableLogger {
  16 + /** 日志记录器 */
  17 + private static final Logger LOGGER = LoggerFactory.getLogger(TimetableLogger.class);
  18 +
  19 + /**
  20 + * 生成时刻表模版日志。
  21 + * @param startTime 操作开始时间
  22 + * @param endTime 操作结束时间
  23 + */
  24 + public void createTTLog(Date startTime, Date endTime) {
  25 + // TODO:
  26 + }
  27 +
  28 + /**
  29 + * 修改时刻表模版日志。
  30 + * @param startTime 操作开始时间
  31 + * @param beforeData 修改前数据
  32 + * @param endTime 操作结束时间
  33 + * @param afterData 修改后数据
  34 + */
  35 + public void modifyTTLog(Date startTime, TTInfoDto beforeData, Date endTime, TTInfoDto afterData) {
  36 + // TODO:
  37 + }
  38 +
  39 + /**
  40 + * 导入时刻表明细日志。
  41 + * @param startTime 操作开始时间
  42 + * @param endTime 操作结束时间
  43 + */
  44 + public void importTTDLog(Date startTime, Date endTime) {
  45 + // TODO:导入前备份一次,导入后备份一次,备份前后的id放入before和after中
  46 + }
  47 +
  48 + /**
  49 + * 修改时刻表明细日志。
  50 + * @param startTime 操作开始时间
  51 + * @param beforeData 修改前数据
  52 + * @param endTime 操作结束时间
  53 + * @param afterData 修改后数据
  54 + */
  55 + public void modifyTTDLog(Date startTime, TTInfoDetailDto beforeData, Date endTime, TTInfoDetailDto afterData) {
  56 + // TODO:
  57 + }
  58 +}
... ...
src/main/resources/logback.xml
... ... @@ -160,6 +160,46 @@
160 160 </logger>
161 161  
162 162  
  163 + <!-- 时刻表,排班计划业务修改日志 -->
  164 + <!--<springProfile name="dev">-->
  165 + <appender name="MYDB" class="com.bsth.service.schedule.log.ModuleOperatorDbAppender">
  166 + <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">
  167 + <dataSource class="org.apache.commons.dbcp.BasicDataSource">
  168 + <driverClassName>com.mysql.jdbc.Driver</driverClassName>
  169 + <url>
  170 + <![CDATA[
  171 + jdbc:mysql://127.0.0.1/test_control?useUnicode=true&characterEncoding=utf-8&useSSL=false
  172 + ]]>
  173 + </url>
  174 + <username>root</username>
  175 + <password></password>
  176 + <testOnBorrow>true</testOnBorrow>
  177 + <validationQuery>
  178 + <![CDATA[
  179 + SELECT 1
  180 + ]]>
  181 + </validationQuery>
  182 + </dataSource>
  183 + </connectionSource>
  184 + </appender>
  185 + <!--</springProfile>-->
  186 +
  187 + <appender name="ASYNC_MYDB" class="ch.qos.logback.classic.AsyncAppender">
  188 + <!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
  189 + <discardingThreshold >0</discardingThreshold>
  190 + <!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
  191 + <queueSize>512</queueSize>
  192 + <!-- 添加附加的appender,最多只能添加一个 -->
  193 + <appender-ref ref ="MYDB"/>
  194 + </appender>
  195 +
  196 + <logger name="com.bsth.service.schedule.log.PlanLogger"
  197 + level="INFO" additivity="false">
  198 + <appender-ref ref="ASYNC_MYDB" />
  199 + </logger>
  200 +
  201 + <!-- TODO -->
  202 +
163 203 <!-- gps -->
164 204 <appender name="GPS_COUNT"
165 205 class="ch.qos.logback.core.rolling.RollingFileAppender">
... ...