NoSql存储日志数据之Spring+Logback+Hbase深度集成

关键词:nosql, spring logback, logback hbase appender
技术框架:spring-data-hadoop, logback

一些比较重要的日志信息需要经常查看,比如用户行为日志,报错或关键业务日志数据然而同一系统多结点运行时这个工作会变的非常繁琐。

本例借用Logback日志框架和Hbase数据库来解决这一问题。

主要功能:

  1. 所有结点日志数据可通过配置同步到一个Hbase数据库
  2. 与Spring整合,全局共享一个Hbase操作实例,动态为某日志添加Appender
  3. 存储的日志数据可指定日志和日志级别,日志过滤
  4. Key-Value方式存储,可指定Value的生成格式

Hbase的操作采用的是Spring-Hadoop中Hbase部分实现,没有直接引用Spring-Hadoop而是只提取Hbase实现部分,原因是Hbase只是其中很小一部分又不想自己封装,因此直接提取使用,封装功能包括Hbase配置和HbaseTemplate等,相关源码请参考Spring-Data-Hadoop下Hbase部分!

关于Logback没有直接使用Logback的配置文件来配置HbaseAppender的原因是想与Spring整合使用Spring的Hbase-bean实例,不用单为Logback建一个Hbase实现等等。

使用相关配置

本文采用的NoSql数据库为Hbase,并且只有Hbase的Logback Appender实现类,可参考实现其它类型的Appender。

Logback Nosql Appender工厂类
在这里指定用哪种类型的NoSql数据库操作对像和对应的LOGBACK APPENDER操作类。

<beanid="logbackNosqlFactory"class="b2gonline.wap.logback.NosqlAppenderFactoryBean"><!--数据库操作类--><propertyname="template"ref="hbaseTemplate"/><!--日志存储表名--><propertyname="tbname"value="waplogdata"/><!--Logback Appender 类全路径--><propertyname="appenderPaht"value="b2gonline.wap.logback.hbase.HbaseAppender"/></bean>

Logback Appender配置
在这里配置LOGGER和APPENDER的对应关系和详细的APPENDER配置,引用的APPENDER从上面配置的Logback Nosql Appender工厂类获得。

<!--单个LOGGER配置--><beanid="hbaseappender1"class="b2gonline.wap.logback.SpringLogbackBean"lazy-init="false"><propertyname="appenderName"value="hbaseappender1"/><propertyname="logLevel"value="INFO"/><propertyname="filterLevel"value="INFO"/><propertyname="logName"value="logbackhbaseappendertest"/><propertyname="appender"ref="logbackNosqlFactory"/></bean><!--多个LOGGER配置--><beanid="hbaseappender2"class="b2gonline.wap.logback.SpringLogbackBean"lazy-init="false"><propertyname="appenderName"value="hbaseappender2"/><propertyname="filterLevel"value="WARN"/><propertyname="logName"><map><entrykey="logbackhbaseappendertest2"value="WARN"/><entrykey="logbackhbaseappendertest3"value="WARN"/></map></property><propertyname="appender"ref="logbackNosqlFactory"/></bean>

JAVA代码实现

NosqlAppenderFactoryBean
不同NOSQL数据库实现的LOGBACK APPENDER工厂类

import b2gonline.wap.logback.hbase.HbaseAppender;import org.springframework.beans.factory.FactoryBean;import org.springframework.util.Assert;/**
* NosqlAppenderFactoryBean工厂类,根据Nosql类型返回实例
*/publicclassNosqlAppenderFactoryBeanimplementsFactoryBean<NoSqlAppender>{privateObject template;privateString tbname;privateString appenderPaht;/**
* Appender类路径,实例化不同类型Appender实例
* @param appender
*/publicvoid setAppenderPaht(String appender){this.appenderPaht = appender;}/**
* 指定类型数据库操作类
* @param template
*/publicvoid setTemplate(Object template){this.template = template;}/**
* 数据存储表名
* @param tbname
*/publicvoid setTbname(String tbname){this.tbname = tbname;}/**
* 根据数据库类型返回Appender实例
*
* @return
* @throws Exception
*/@OverridepublicNoSqlAppender getObject()throwsException{//校验配置Assert.notNull(template);Assert.notNull(appenderPaht);Assert.notNull(tbname);//生成实例Class<?> appenderClass =Class.forName(appenderPaht);StringSCname= appenderClass.getSuperclass().getSimpleName();if(SCname.equals("NoSqlAppender")){NoSqlAppender hbaseAppender =(NoSqlAppender) appenderClass.newInstance();
hbaseAppender.setTemplate(template);
hbaseAppender.setTbname(tbname);return hbaseAppender;}else{thrownewIllegalArgumentException(appenderPaht +"is not NoSqlAppender subclass!");}}@OverridepublicClass<?> getObjectType(){returnHbaseAppender.class;}@Overridepublicboolean isSingleton(){returnfalse;}}

SpringLogbackBean
Logback Appender与Spring整合类,参考上面第二部分配置

import ch.qos.logback.classic.Level;import ch.qos.logback.classic.Logger;import ch.qos.logback.classic.LoggerContext;import ch.qos.logback.classic.filter.LevelFilter;import ch.qos.logback.core.spi.FilterReply;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.InitializingBean;import org.springframework.util.Assert;import java.util.Map;/**
* Logback Appender与Spring整合类
*/publicclassSpringLogbackBeanimplementsInitializingBean{ org.slf4j.Logger _logger =LoggerFactory.getLogger(this.getClass());privateLevel logLevel =Level.INFO;privateString appenderName ="NoSqlAppender";privateNoSqlAppender appender;privateObject logName ="root";privateLevel filterLevel =Level.INFO;privateboolean useFilterLevel =true;privateboolean additiveAppender =true;privateString pattern ="%d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n";/**
* Appender 名称
*
* @param appenderName
*/publicvoid setAppenderName(String appenderName){this.appenderName = appenderName;}/**
* 设置存储的日志级别,默认是INFO
*
* @param logLevel
*/publicvoid setLogLevel(Level logLevel){this.logLevel = logLevel;}/**
* 设置Logback appender
*
* @param appender
*/publicvoid setAppender(NoSqlAppender appender){this.appender = appender;}/**
* 设置需要记录的日志名,默认是root
*
* @param logName
*/publicvoid setLogName(Object logName){this.logName = logName;}/**
* 使用过滤器过滤的日志级别,默认INFO
*
* @param filterLevel
*/publicvoid setFilterLevel(Level filterLevel){this.filterLevel = filterLevel;}/**
* 是否累加Appender(继承root appender),默认 true
*
* @param additiveAppender
*/publicvoid setAdditiveAppender(boolean additiveAppender){this.additiveAppender = additiveAppender;}/**
* 是否使用日志过滤,其它级别日志数据交给继承来的APPENDER处理
*
* @param useFilterLevel
*/publicvoid setUseFilterLevel(boolean useFilterLevel){this.useFilterLevel = useFilterLevel;}/**
* 设置日志格式
*
* @param pattern
*/publicvoid setPattern(String pattern){this.pattern = pattern;}@Overridepublicvoid afterPropertiesSet()throwsException{Assert.notNull(appender,"property `appender` must set!");
buildAppender();//多LOGGER支持配置if(logName.getClass().getSimpleName().equals("LinkedHashMap")){Map<String,String> loggers =(Map) logName;for(Map.Entry<String,String> log : loggers.entrySet()){Logger logger =(Logger)LoggerFactory.getLogger(log.getKey());
logger.setLevel(Level.toLevel(log.getValue()));
logger.setAdditive(additiveAppender);
logger.addAppender(appender);
_logger.debug("Set appender: {} to logger: {} ", appender.getName(), log.getKey());}}//单个配置else{Logger logger =(Logger)LoggerFactory.getLogger(logName.toString());//将appender添加到指定logger
logger.setLevel(logLevel);
logger.setAdditive(additiveAppender);
logger.addAppender(appender);
_logger.debug("Set appender: {} to logger: {} ", appender.getName(), logName);}}privatevoid buildAppender(){LoggerContext loggerContext =(LoggerContext)LoggerFactory.getILoggerFactory();//启用级别过滤,适用场景:把级别为 warn 是放入数据库。if(useFilterLevel){LevelFilter levelFilter =newLevelFilter();
levelFilter.setContext(loggerContext);
levelFilter.setLevel(filterLevel);
levelFilter.setOnMatch(FilterReply.ACCEPT);
levelFilter.setOnMismatch(FilterReply.DENY);
levelFilter.start();
appender.addFilter(levelFilter);}//设置appender相关属性
appender.setName(appenderName);
appender.setPattern(pattern);
appender.setContext(loggerContext);
appender.start();}}

NoSqlAppender
NoSql Appender 基础类,不同NOSQL数据库依赖此类实现不同APPENDER。

import ch.qos.logback.classic.PatternLayout;import ch.qos.logback.classic.spi.ILoggingEvent;import ch.qos.logback.core.UnsynchronizedAppenderBase;import ch.qos.logback.core.spi.LogbackLock;import org.springframework.util.Assert;/**
* NoSql Appender 基础类
* 子类需要实现generatedKey方法和指定存储类实例
*/abstractpublicclassNoSqlAppender<E>extendsUnsynchronizedAppenderBase<E>{//日志存储protectedILogRepository logRepository;//日志表名protectedString tbname;//日志存储格式protectedString pattern;//日志格式解析器protectedPatternLayout patternLayout;//Nosql操作类protectedObject template;/**
* 日志存储KEY生成
*
* @param event
* @return
*/protectedabstractString generatedKey(E event);/**
* 使用指定的格式生成日志内容数据
*
* @param event
* @return
*/protectedString generatedValue(E event){return patternLayout.doLayout((ILoggingEvent) event);}/**
* 日志表名
*
* @param tbname
*/publicvoid setTbname(String tbname){this.tbname = tbname;}/**
* 日志存储
*
* @param eventObject
*/@Overrideprotectedvoid append(E eventObject){if(!isStarted()){return;}try{String key = generatedKey(eventObject);String value = generatedValue(eventObject);
logRepository.saveLog(key, value);}catch(Exception e){
addError(e.getMessage());}}/**
* 初始化,patternLayout
*/@Overridepublicvoid start(){Assert.notNull(tbname,"tbname not null !"); patternLayout =newPatternLayout();
patternLayout.setPattern(pattern);
patternLayout.setContext(context);
patternLayout.setOutputPatternAsHeader(false);
patternLayout.start();super.start();}@Overridepublicvoid stop(){super.stop();}/**
* 日志格式
*
* @param pattern
*/publicvoid setPattern(String pattern){this.pattern = pattern;}publicvoid setTemplate(Object template){this.template = template;}}

ILogRepository
日志存储接口,不同NOSQL数据库提供统一保存方法。

/**
* 日志存储接口
*/public interface ILogRepository<E>{/**
* 保存日志,KEY-VALUE形式
*
* @param key
* @param value
*/publicvoid saveLog(String key,String value);}

HbaseLogRepository
Hbase的日志存储实现类

import b2gonline.wap.hbase.HbaseTemplate;import b2gonline.wap.hbase.TableCallback;import b2gonline.wap.logback.ILogRepository;import org.apache.hadoop.hbase.client.HTableInterface;import org.apache.hadoop.hbase.client.Put;import org.apache.hadoop.hbase.util.Bytes;/**
* Hbase 日志存储实现类
*/publicclassHbaseLogRepositoryimplementsILogRepository<HbaseTemplate>{privateString tbname;privateHbaseTemplate hbaseTemplate;//日志列族publicstaticbyte[] CF_INFO =Bytes.toBytes("log");//日志列名privatebyte[] CF_CELL =Bytes.toBytes("data");//日志表名publicvoid setTbname(String tbname){this.tbname = tbname;}publicvoid setHbaseTemplate(HbaseTemplate hbaseTemplate){this.hbaseTemplate = hbaseTemplate;}/**
* 日志数据存储
*
* @param key
* @param value
*/@Overridepublicvoid saveLog(finalString key,String value){finalbyte[] bKey =Bytes.toBytes(key);finalbyte[] bValue =Bytes.toBytes(value); hbaseTemplate.execute(tbname,newTableCallback<Object>(){@OverridepublicObject doInTable(HTableInterface table)throwsThrowable{Put p =newPut(bKey);
p.add(CF_INFO, CF_CELL, bValue);
table.put(p);returnnull;}});}}

HbaseAppender
Hbase的Appender实现类

import b2gonline.wap.hbase.HbaseTemplate;import b2gonline.wap.logback.NoSqlAppender;import ch.qos.logback.classic.spi.ILoggingEvent;import org.apache.hadoop.hbase.HColumnDescriptor;import org.apache.hadoop.hbase.HTableDescriptor;import org.apache.hadoop.hbase.client.HBaseAdmin;import org.springframework.beans.factory.FactoryBean;import org.springframework.beans.factory.InitializingBean;import org.springframework.util.Assert;import java.util.Random;/**
* Hbase Appender 实现类
*/publicclassHbaseAppenderextendsNoSqlAppender<ILoggingEvent>implementsInitializingBean{Random generator =newRandom();privateHBaseAdmin admin;/**
* 初始化,HbaseLogRepository
*/@Overridepublicvoid start(){Assert.notNull(template,"hbaseTemplate not null !");try{
afterPropertiesSet();}catch(Exception e){
e.printStackTrace();}HbaseLogRepository repository =newHbaseLogRepository();
repository.setHbaseTemplate((HbaseTemplate) template);
repository.setTbname(tbname);
logRepository = repository;super.start();}/**
* 生成记录KEY,如果有必要也可以通过patternLayout生成
*
* @param event
* @return
*/@OverrideprotectedString generatedKey(ILoggingEvent event){//使用随机数防止并发生成同名KEYint id = generator.nextInt(9999)+1000;StringBuilder sb =newStringBuilder();
sb.append(event.getLoggerName());
sb.append(event.getLevel());
sb.append(event.getThreadName());
sb.append(event.getTimeStamp());
sb.append("-");
sb.append(id);return sb.toString().toLowerCase().replaceAll(" ","");}/**
* 没有表自动创建
*
* @throws Exception
*/@Overridepublicvoid afterPropertiesSet()throwsException{
admin =newHBaseAdmin(((HbaseTemplate) template).getConfiguration());if(!admin.tableExists(tbname)){HTableDescriptor tableDescriptor =newHTableDescriptor(tbname);HColumnDescriptor columnDescriptor =newHColumnDescriptor(HbaseLogRepository.CF_INFO);
tableDescriptor.addFamily(columnDescriptor);
admin.createTable(tableDescriptor);}}}

关于HbaseTemplate

如上所说,Hbase操作类HbaseTemplate提取自SPRING-DATA-HADOOP,参考https://github.com/SpringSource/spring-hadoop/trunk/spring-hadoop-core/src/main/java/org/springframework/data/hadoop/hbase

提取后会依赖ConfigurationUtils,源码如下:

import org.springframework.util.Assert;import java.util.Enumeration;import java.util.Properties;publicclassConfigurationUtils{publicstaticvoid addProperties(org.apache.hadoop.conf.Configuration configuration,Properties properties){Assert.notNull(configuration,"A non-null configuration is required");if(properties !=null){Enumeration<?> props = properties.propertyNames();while(props.hasMoreElements()){String key = props.nextElement().toString();
configuration.set(key, properties.getProperty(key));}}}}

提取后的Hbase连接配置:

<bean id="hbaseConfiguration"class="b2gonline.wap.hbase.HbaseConfigurationFactoryBean"><property name="zkPort" value="2181"/><property name="zkQuorum" value="hadoopmaster,hadoopnode1"/></bean><bean id="hbaseTemplate"class="b2gonline.wap.hbase.HbaseTemplate"><property name="configuration" ref="hbaseConfiguration"/></bean>

最后

实现了日志数据统一存储就还得有统一查看的功能,没错,下一步实现!

NoSql存储日志数据之Spring+Logback+Hbase深度集成的更多相关文章

  1. 应用Flume+HBase采集和存储日志数据

    1. 在本方案中,我们要将数据存储到HBase中,所以使用flume中提供的hbase sink,同时,为了清洗转换日志数据,我们实现自己的AsyncHbaseEventSerializer. pac ...

  2. MongoDB应用案例:使用 MongoDB 存储日志数据

    线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息,通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题,但当产生大量的日志之后,要想从大量日志里挖 ...

  3. 使用 MongoDB 存储日志数据

    使用 MongoDB 存储日志数据     线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息.通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题 ...

  4. MongoDB 存储日志数据

    MongoDB 存储日志数据 https://www.cnblogs.com/nongchaoer/archive/2017/01/11/6274242.html 线上运行的服务会产生大量的运行及访问 ...

  5. 日志数据如何同步到MaxCompute

    摘要:日常工作中,企业需要将通过ECS.容器.移动端.开源软件.网站服务.JS等接入的实时日志数据进行应用开发.包括对日志实时查询与分析.采集与消费.数据清洗与流计算.数据仓库对接等场景.本次分享主要 ...

  6. Mongodb 存储日志信息

    线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息,通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题,但当产生大量的日志之后,要想从大量日志里挖 ...

  7. Nacos Config客户端与Spring Boot、Spring Cloud深度集成

    目录 Nacos与Spring Boot集成 @NacosPropertySource和@NacosValue com.alibaba.nacos.spring.core.env.NacosPrope ...

  8. 用Hbase存储Log4j日志数据:HbaseAppender

    业务需求: 需求很简单,就是把多个系统的日志数据统一存储到Hbase数据库中,方便统一查看和监控. 解决思路: 写针对Hbase存储的Log4j Appender,有一个简单的日志储存策略,把Log4 ...

  9. Spring Boot(十)Logback和Log4j2集成与日志发展史

    一.简介 Java知名的日志有很多,比如:JUL.Log4j.JCL.SLF4J.Logback.Log4j2,那么这些日志框架之间有着怎样的关系?诞生的原因又是解决什么问题?下面一起来看. 1.1 ...

随机推荐

  1. jenkins相关

    1. jenkins maven tomcat做持续集成的时候几个关键配置:http://my.oschina.net/congqian/blog/112782?fromerr=PmIDbLs5 2. ...

  2. lamp环境编译(apache2.4.7 php5.4.25 mysql 5.5.23)

    环境要求 gcc.gcc-c++.cmake.bison(可能)支持 1.yum install gcc gcc-c++ cmake bison 2.修改yum配置,达到搜索本地设置 移走或改名/et ...

  3. TI CC2541的串口输出.

    http://blog.csdn.net/feilusia/article/details/47431659 基本上看上面这个博客的. 重点是: 1. 关闭流控, 在npi.h里面, 将 #defin ...

  4. linux下的module_param()解释【转】

    转自:http://blog.csdn.net/wavemcu/article/details/7762133 版权声明:本文为博主原创文章,未经博主允许不得转载. ***************** ...

  5. 收缩 虚拟硬盘 shrink vhd

    在使用WIN2012 的Hyper-v的虚拟磁盘时, 有时需要将磁盘中未使用的控件收缩掉, 这时就需要使用Hyper-v磁盘工具的收缩功能. 如果使用Hyper-v磁盘工具, 不能对vhd虚拟磁盘进行 ...

  6. Server.MapPath()获取本机绝对路径

    1.    Server.MapPath("/")  应用程序根目录所在的位置 如 C:\Inetpub\wwwroot\ 2.Server.MapPath("./&qu ...

  7. Hibernate,JPA注解@Entity

    通过@Entity注解将一个类声明为一个实体bean(即一个持久化POJO类), @Id注解则声明了该实体bean的标识属性. 其他的映射定义是隐式的. 就是说一个持久化POJO类,除了主键ID需要@ ...

  8. 一道面试题比较synchronized和读写锁

    一.科普定义 这篇博文的两个主角“synchronized”和“读写锁” 1)synchronized 这个同步关键字相信大家都用得比较多,在上一篇“多个线程之间共享数据的方式”中也详细列举他的应用, ...

  9. $q服务的API详解

    下面我们通过讲解$q的API让你更多的了解promise异步编程模式.$q是做为angularjs的一个服务而存在的,只是对promise异步编程模式的一个简化实现版,源码中剔除注释实现代码也就二百多 ...

  10. Oracle列操作引起的全表扫描

    首先是一种比较明显的情况: select * from table where column + 1 = 2 这里对column进行了列操作,加1以后,与column索引里的内容对不上,导致colum ...