# hoofungson-elk-log **Repository Path**: hoofungson/hoofungson-elk-log ## Basic Information - **Project Name**: hoofungson-elk-log - **Description**: 凤松轩ELK日志组件,支持APM有:ElasticAPM和SkyWalking等... - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 3 - **Created**: 2019-08-31 - **Last Updated**: 2022-06-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### **日志收集** 项目工程中各模块单元独立的部署在不同的服务器上,各模块独立运行期间输出的日志分散在不同服务器上,不便于运营期间运维排查问题。为此引入了日志收集方案,正如方案字面意思就是将分散在各服务器上的日志统一收集起来便于查阅及更好的助力于运维工作。 ### **ELK日志收集方案** 日志收集方案有很多种,小中型的项目常用日志收集方案就是ELK了。ELK是由Elasticsearch、Logstash和Kibana组合成的一套方案,整套方案中由部署在应用服务器上的Logstash负责采集指定目录(也是应用生成业务日志目录)下的日志,然后解析过滤采集到数据输出存储到Elasticsearch中,用户或运维可以在Kibana里可视化的查询被收集到的日志来排查问题。 ### **ELK日志收集拓扑图** ![1](https://images.blog.hoofungson.cn/2019/12/02/20191202103548.jpg) ### **ELK方案部署方式** * Elasticsearch集群部署(三节点); * Logstash多节点部署,每台应用服务器部署一个Logstash节点采集当前服务器应用日志,配置输入方式按行解析JSON结构,输出连接Elasticsearch集群; * Kibana单节点部署,配置好连接Elasticsearch集群; ### **ELK方案最小化部署规格** | 组件 | 模式 | 规格 | 备注 | | ------------- | ---- | ---- | ------------ | | Elasticsearch | 集群 | 3节点 | | | Logstash | 多点采集 | 若干节点 | 取决于应用服务器规模 | | kibana | 单机 | 2节点 | | | Nginx | 单机 | 单节点 | 负载均衡代理Kibana | ### **监控日志** 业务中某些地方需要输出一些日志,便于流程追踪。监控日志级别和普通日志级别类似同样分为:TRACE、DEBUG、INFO、WARNING、ERROR五个常用等级,在输出的日志中会多monitorCode、contextMessage和contextContent字段。 工程引入监控日志步骤如下: * 引入依赖 ```java cn.hoofungson.framework hoofungson-elk-log 1.0.1-SNAPSHOT ``` * SpringBoot工程配置文件中配置应用ID属性、日志内容格式属性和日志文件路径。 ```properties #服务端口配置 server.port=9100 #日志组件配置 hoofungson.elk.log.app-id=HBS01 #配置日志内容格式,支持JSON和Text两种格式,默认/推荐JSON格式 hoofungson.elk.log.content-format=JSON #配置日志文件输出路径 hoofungson.elk.log.file-path=E:\\app\\logs #配置日志支持APM,支持ElasticAPM和SkyWalking两种,默认/推荐ElasticAPM hoofungson.elk.log.support-apm=ElasticAPM ``` * SpringBoot工程启动类上配置扫描基础包 ```java // 引入类 import org.springframework.context.annotation.ComponentScan; // 注解扫描包 @ComponentScan(basePackages={"cn.hoofungson.*"}) ``` * 输出监控日志 ```java // 引入监控日志 import cn.hoofungson.framework.elk.log.monitor.MonitorLogger; // 引入监控日志工厂 import cn.hoofungson.framework.elk.log.monitor.MonitorLoggerFactory; // 声明监控日志常量 private static final MonitorLogger MONITOR_LOGGER = MonitorLoggerFactory.getMonitorLogger(HoofungsonLogDemoApplication.class); // 输出监控日志 MONITOR_LOGGER.info("200100", "监控日志应用启动成功!"); ``` **实例: 输出"监控日志应用启动成功!"** ```java package cn.hoofungson.framework.log.demo; import cn.hoofungson.framework.elk.log.monitor.MonitorLogger; import cn.hoofungson.framework.elk.log.monitor.MonitorLoggerFactory; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; import java.util.LinkedHashMap; import java.util.Map; @Slf4j @ComponentScan(basePackages = {"cn.hoofungson.*"}) @SpringBootApplication public class HoofungsonLogDemoApplication { /** * 声明监控日志 * **/ private static final MonitorLogger MONITOR_LOGGER = MonitorLoggerFactory.getMonitorLogger(HoofungsonLogDemoApplication.class); public static void main(String[] args) { SpringApplication.run(HoofungsonLogDemoApplication.class, args); log.info("============ 应用启动成功 ============"); MONITOR_LOGGER.info("200100", "监控日志应用启动成功!"); Map context = new LinkedHashMap<>(); context.put("username", "hoosung"); context.put("email", "hoofungson@163.com"); MONITOR_LOGGER.info("200111", "应用启动入参输出", context); } } ``` ### **集成ElasticAPM** ELK日志集成方案中已经集成了ElasticAPM来做业务链路追踪。ElasticAPM分为两部分,一部分是ApmServer,另一部分ApmAgent。ApmAgent部署在应用服务器上收集应用的业务请求,然后传送到ApmServer端,最后由ApmServer将收集到应用业务请求保存到Elasticsearch中。在Kibana上可以集成ElasticAPM,直接在Kibana可视化追踪业务链路。 当前日志组件hoofungson-elk-log中已经集成ElasticAPM了,在ELK方案中需要独立部署一台ApmServer并且配置Elasticsearch地址指向ELK中的Elasticsearch地址,部署应用在启动时设置ApmAgent参数指向ApmServer,在Kibana中添加APM模块即可完成。 当前日志组件基于ElasticAPM实现在日志中输出traceId字段,通过该字段在业务日志实现简单的链路追踪,详细的链路追踪工作可以在Kibana中进行。要实现日志中输出traceId字段必须将当前应用纳入ElasticAPM收集范围中,也就是在应用启动时设置ApmAgent。 #### **集成APM后ELK拓扑图** ![1](https://images.blog.hoofungson.cn/2019/12/02/20191202103950.jpg) 应用集成ApmAgent收集步骤: * 下载elastic-apm-agent-1.9.0.jar * 在SpringBoot工程启动命令中加入相关参数 ```bash # 指定elastic-apm-agent的jar包路径 -javaagent:C:\Users\Administrator\Downloads\elastic-apm-agent-1.9.0.jar # 指定应用在elastic apm中的业务名 -Delastic.apm.service_name=hoofungson-log-demo # 指定应用在elastic apm包名(SpringBoot启动类所在包) -Delastic.apm.application_packages=cn.hoofungson.framework.log.demo # 指定apm server的url -Delastic.apm.server_urls=http://192.168.124.23:8200 ``` * 完整的SpringBoot工程启动参数 ```java java -javaagent:C:\Users\Administrator\Downloads\elastic-apm-agent-1.9.0.jar -Delastic.apm.service_name=hoofungson-log-demo -Delastic.apm.application_packages=cn.hoofungson.framework.log.demo -Delastic.apm.server_urls=http://192.168.124.23:8200 -jar mall-user-service-provider.jar ``` ### **集成SkyWalking** ELK日志集成方案中已经集成了SkyWalking来做业务链路追踪。SkyWalking分为两部分,一部分是Server,另一部分Agent。Agent部署在应用服务器上收集应用的业务请求,然后传送到Server端,最后由Server将收集到应用业务请求保存到Elasticsearch中。 当前日志组件hoofungson-elk-log中已经集成SkyWalking了,实现在日志中输出traceId字段,通过该字段在业务日志实现简单的链路追踪,详细的链路追踪工作可以在Kibana中进行。在ELK方案中需要独立部署一台Server并且配置Elasticsearch地址指向ELK中的Elasticsearch地址,部署应用在启动时设置Agent参数指向Server,启动应用就行了。 应用集成Agent收集步骤: * 下载skywalking-agent.jar * 在SpringBoot工程启动命令中加入相关参数 ```bash # 指定skywalking-agent的jar包路径 -javaagent:E:/Program Files/apache-skywalking-apm-es7-6.6.0/apache-skywalking-apm-bin-es7/agent/skywalking-agent.jar # 指定应用在SkyWalking中的业务名 -Dskywalking.agent.service_name=hoofungson-log-demo # 指定apm server的url -Dskywalking.collector.backend_service=47.115.178.6:11800 ``` * 完整的SpringBoot工程启动参数 ```java java -javaagent:E:/Program Files/apache-skywalking-apm-es7-6.6.0/apache-skywalking-apm-bin-es7/agent/skywalking-agent.jar -Dskywalking.agent.service_name=hoofungson-log-demo -Dskywalking.collector.backend_service=47.115.178.6:11800 -jar mall-user-service-provider.jar ``` ### **集成业务接口入参输出** 业务中某些接口需要输出入参日志,结合监控日志方式输出便于业务链路追踪。入参输出这块基于SpringAOP技术实现,所以工程中在集成入参输出这块需要引入SpringAOP相关的依赖。 SpringBoot工程集成入参日志输出步骤: * 引入spring-boot-starter-aop依赖 ``` org.springframework.boot spring-boot-starter-aop ${spring-boot.version} ``` * SpringBoot工程配置切面 ```java /** * @Project Name:mall-user-service * @Package Name:cn.hoofungson.mall.user.service.provider.aspect.log * @Since JDK 1.8 */ package cn.hoofungson.mall.user.service.provider.aspect.log; import cn.hoofungson.framework.elk.log.util.LogServiceUtils; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @Description: 用户服务日志切面 * @Author 胡松 hoofungson@163.com * @Date 2019-08-31 18:02 * @Version V1.0 */ @Aspect @Component public class UserServiceLogAspect { @Pointcut("execution(* cn.hoofungson.mall.user.service.provider.service..*.*(..))") public void logService() { } @Before(value = "logService()") public void logServiceBefore(JoinPoint joinPoint) { LogServiceUtils.handlerJoinPoint(joinPoint); } } ``` * 在业务接口实现类方法上注解@LogServiceContext,这个注解有两个参数:code和note。code表示业务码,在输出的监控日志中对应为monitorCode字段,需要在整个应用中保持唯一性。note是当前方法上下文信息,在输出的监控日志中对应为contextMessage字段。该注解能扫描到所注解方法上的入参内容,如果扫描到方法有入参,在输出的监控日志中对应为contextContent字段。 ```java /** * @Project Name:mall-user-service * @Package Name:cn.hoofungson.mall.user.service.provider.service.impl.user * @Since JDK 1.8 */ package cn.hoofungson.mall.user.service.provider.service.impl.user; import cn.hoofungson.framework.elk.log.annotation.LogServiceContext; import cn.hoofungson.mall.user.service.interfaces.domain.dto.user.UserDTO; import cn.hoofungson.mall.user.service.interfaces.service.user.UserManageService; import cn.hoofungson.mall.user.service.provider.repositories.user.UserRepository; import org.springframework.stereotype.Service; import java.util.List; /** * @Description: 商城用户管理服务实现 * @Author 胡松 hoofungson@163.com * @Date 2019-08-26 14:41 * @Version V1.0 */ @Service public class UserManageServiceImpl implements UserManageService { /** * 新增用户 * * @param userDTO **/ @LogServiceContext(code = "200101", note = "新增用户") @Override public void addUser(UserDTO userDTO) { if (null == userDTO) { log.error("要新增的用户对象不能为空!!"); return; } UserRepository.getUserRepository().add(userDTO); } /** * 返回所有用户 * * @return **/ @LogServiceContext(code = "200102", note = "返回所有用户") @Override public List findAll() { return UserRepository.getUserRepository(); } } ``` ### **日志异步输出** 日志组件默认开启异步输出,不需要项目另外配置。