NoSql存储日志数据之Spring+Logback+Hbase深度集成
NoSql存储日志数据之Spring+Logback+Hbase深度集成
关键词:nosql, spring logback, logback hbase appender
技术框架:spring-data-hadoop, logback
一些比较重要的日志信息需要经常查看,比如用户行为日志,报错或关键业务日志数据然而同一系统多结点运行时这个工作会变的非常繁琐。
本例借用Logback日志框架和Hbase数据库来解决这一问题。
主要功能:
- 所有结点日志数据可通过配置同步到一个Hbase数据库
- 与Spring整合,全局共享一个Hbase操作实例,动态为某日志添加Appender
- 存储的日志数据可指定日志和日志级别,日志过滤
- 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深度集成的更多相关文章
- 应用Flume+HBase采集和存储日志数据
1. 在本方案中,我们要将数据存储到HBase中,所以使用flume中提供的hbase sink,同时,为了清洗转换日志数据,我们实现自己的AsyncHbaseEventSerializer. pac ...
- MongoDB应用案例:使用 MongoDB 存储日志数据
线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息,通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题,但当产生大量的日志之后,要想从大量日志里挖 ...
- 使用 MongoDB 存储日志数据
使用 MongoDB 存储日志数据 线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息.通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题 ...
- MongoDB 存储日志数据
MongoDB 存储日志数据 https://www.cnblogs.com/nongchaoer/archive/2017/01/11/6274242.html 线上运行的服务会产生大量的运行及访问 ...
- 日志数据如何同步到MaxCompute
摘要:日常工作中,企业需要将通过ECS.容器.移动端.开源软件.网站服务.JS等接入的实时日志数据进行应用开发.包括对日志实时查询与分析.采集与消费.数据清洗与流计算.数据仓库对接等场景.本次分享主要 ...
- Mongodb 存储日志信息
线上运行的服务会产生大量的运行及访问日志,日志里会包含一些错误.警告.及用户行为等信息,通常服务会以文本的形式记录日志信息,这样可读性强,方便于日常定位问题,但当产生大量的日志之后,要想从大量日志里挖 ...
- Nacos Config客户端与Spring Boot、Spring Cloud深度集成
目录 Nacos与Spring Boot集成 @NacosPropertySource和@NacosValue com.alibaba.nacos.spring.core.env.NacosPrope ...
- 用Hbase存储Log4j日志数据:HbaseAppender
业务需求: 需求很简单,就是把多个系统的日志数据统一存储到Hbase数据库中,方便统一查看和监控. 解决思路: 写针对Hbase存储的Log4j Appender,有一个简单的日志储存策略,把Log4 ...
- Spring Boot(十)Logback和Log4j2集成与日志发展史
一.简介 Java知名的日志有很多,比如:JUL.Log4j.JCL.SLF4J.Logback.Log4j2,那么这些日志框架之间有着怎样的关系?诞生的原因又是解决什么问题?下面一起来看. 1.1 ...
随机推荐
- IT职业选择与定位
(一) 位置有很多,最适合你的是哪个? 有的人在电子技术的层面工作,开发出性能强劲的芯片和硬件产品:有的人在别人开发的芯片和硬件上开发各种操作系统和驱动程序:有的人在各种操作系统或设备 ...
- OBD 14230 Slow, Addr激活
const u8 LinkCmd14230[6] = { 0xC2, 0x33, 0xF1, 0x01, 0x00, 0xE7 }; u8 ISO14230ADDR_Check(){ ...
- DIV设置了固定宽高出现文字(文本)的不能自动换行
如果你的div设置了固定的width和height,有时会出现文字不能自动换行的情况 查过相关资料后才知道,只有英文文本才会出现不能自动换行的情况,(中文不存在)而原因是因为英文文本之间没有加空格,浏 ...
- Bind[Exclude|Include]排除字段或只允许字段验证
public ActionResult xx([Bind(Exclude = "id")] xxModel xx, HttpPostedFileBase file)//排除id验证 ...
- ubuntu 修改终端命令显示的颜色
转于 http://www.blogbus.com/riusksk-logs/62891140.html 修改当前用户 gedit ~/.bashrc 在最后一行下面添加这行 PS1='${debi ...
- 调整iFrame高度
在Chrome中,即使将iframe的高度设置为100%,也无法根据内容页自动调节高度,需要在iframe的onload even中通过计算设置iframe的高度 function setIframe ...
- 对于改善 MySQL 数据装载操作有效率的方法是怎样
多时候关心的是优化SELECT 查询,因为它们是最常用的查询,而且确定怎样优化它们并不总是直截了当.相对来说,将数据装入数据库是直截了当的.然而,也存在可用来改善数据装载操作效率的策略,其基本原理如下 ...
- C#:控制台程序调用中间库创建窗体
1.类库项目引用System.Windows.Forms并添加引用后,才可创建窗体. 2.控制台应用程序调用中间库(DLL)中的方法创建窗体:中间类库使用反射下的Assembly加载包含窗体的类库及创 ...
- MFC中的CDC,CClientDC,CPaintDC,CWindowDC的区别
转自 http://blog.csdn.net/guoquan2003/article/details/4534716 CDC是Windows绘图设备的基类. CClientDC:(1)(客户区设备上 ...
- hdwiki 数据库结构说明
HDWiki数据库结构说明 以下标有“A”的表示该列为自增列,标有“P”的表示该列为主码,标有“I”的表示该列为索引列,标有“U”的表示该列为唯一列,标有“F”的表示全文搜索. ...