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. SQL Server 数据库中关于死锁的分析

    SQL Server数据库发生死锁时不会像Oracle那样自动生成一个跟踪文件.有时可以在[管理]->[当前活动] 里看到阻塞信息(有时SQL Server企业管理器会因为锁太多而没有响应). ...

  2. 【jQuery UI 1.8 The User Interface Library for jQuery】.学习笔记.4.Tabs控件

    之前,我们已经介绍了 jQuery UI 库,CSS 框架.下面,我们将学习这些有增强可视化效果,高度可配置的用户交互组件. Tab 的特性是,点击 tab 后,会高亮该 tab,并显示他的关联con ...

  3. NSDictionary to jsonString || 对象转json格式

    -(NSString*)DataTOjsonString:(id)object { NSString *jsonString = nil; NSError *error; NSData *jsonDa ...

  4. SQLServer学习笔记<> 表连接查询----交叉连接、内连接、左连接、右连接

    (1)交叉连接(cross join)即我们所说的笛卡尔积.查询出满足两张表所有的记录数,A(3条记录),B(9条记录),A*B(27条记录). 比如:雇员表(HR.employees)和货运公司(S ...

  5. Effective STL

    第9条:慎重选择删除元素的方法 删除特定值元素,vector.string.deque用erase-remove:c.erase(remove(c.begin(),c.end(),1963),c.en ...

  6. 清空html代码

    不要使用 $("XXX").html(""); 清空html代码块一般有两种方式: 1)非miniui等组件区块:$("#id").empt ...

  7. YTU 3002: 出栈顺序(栈和队列)

    3002: 出栈顺序(栈和队列) 时间限制: 1 Sec  内存限制: 128 MB 提交: 80  解决: 20 题目描述 给出一个入栈序列,和一个出栈序列,判断该出栈序列是否正确. 输入 输入包含 ...

  8. Unity-Animato深入系列---FloatValue阻尼

    回到 Animator深入系列总目录 Animator的SetFloat接口可以设置阻尼,并且4种类型变量只有float是支持阻尼的. public void SetFloat(int id, flo ...

  9. C#字符串题目

    老师给小学生门布置了一些作业,让它们按照一个模版写一些字符串交上来,同学们把作业交上来了,问题来了,这么多的作业老师批改不过来,现在请你帮老师写一个程序,帮助老师确定各个字符串是否合格.首先老师有一个 ...

  10. CXF集成spring做webservice接口

    一 . cxf 的jar包 1.cxf-2.3.3.jar 2.wsdl4j-1.6.2.jar 3.wss4j-1.5.11.jar 4.wstx-asl-3.2.0.jar 5.XmlSchema ...