search(9)- elastic4s logback-appender
前面写了个cassandra-appender,一个基于cassandra的logback插件。正是cassandra的分布式数据库属性才合适作为akka-cluster-sharding分布式应用的logger。所以,cassandra-appender核心功能就是对logback消息的存写部分了。同样,基于ES的logback-appender核心部分就是对ES的存写过程了。在ES里这个过程还附带了索引indexing过程。将来对历史消息的搜索、分析会更加方便。直接看看消息存写这部分elastic4代码:
def writeLog(event: ILoggingEvent)(client: ElasticClient, idx: String)(appName: String, ip: String, hostName: String, default: String) = {
var content: List[(String,Any)] = List(
APP_NAME -> appName,
HOST_IP -> ip,
HOST_NAME -> hostName,
LOGGER_NAME -> event.getLoggerName(),
LEVEL -> event.getLevel().toString,
THREAD_NAME -> event.getThreadName(),
LOG_DATE -> logDate,
LOG_TIME -> logTime
)
try {
val callerData = event.getCallerData()
if (callerData.nonEmpty) {
content = content ++ List(
CLASS_NAME -> callerData.head.getClassName(),
FILE_NAME -> callerData.head.getFileName(),
LINE_NUMBER -> callerData.head.getLineNumber().toString,
METHOD_NAME -> callerData.head.getMethodName()
)
}
} catch {case e: Throwable => println(s"logging event error: ${e.getMessage}")}
try {
if (event.getThrowableProxy() != null) {
val throwableStrs = event.getThrowableProxy().getSuppressed().asInstanceOf[List[IThrowableProxy]]
val throwableStr = throwableStrs.foldLeft("") { case (b, t) => b + "," + t.getMessage() }
content = content :+ (THROWABLE_STR -> throwableStr)
}
} catch {case e: Throwable => println(s"logging event error: ${e.getMessage}")}
var logmsgs = event.getMessage()
try {
val logMap = fromJson[Map[String,String]](logmsgs)
logMap.foreach ( m => content = content :+ (m._1 -> m._2))
} catch {
case e: Throwable =>
content = content :+ (MESSAGE -> logmsgs)
try {
val dftMap = fromJson[Map[String,String]](default)
dftMap.foreach ( m => content = content :+ (m._1 -> m._2))
} catch {
case e: Throwable => }
}
val newRecord = indexInto(idx)
.fields(
content
).createOnly(true)
client.execute(newRecord) //.await
}
可以看到,我们先判断了一下event.getMessage()消息是否是json格式的:如果是正确的json格式,那么解析成为字段名和字段值,否则就直接写入log_msg字段 + 一串默认的字段和值。干什么呢?要知道这个elastic-appender是一个通用的logback-plugin,是可以在任何软件中使用的。因为各种软件对运行状态跟踪目标、方式的要求不同,为了满足这些要求,那么通过用户自定义跟踪目标字段的方式应该是一个好的解决方案。从测试例子里可以理解:
var loggedItems = Map[String,String]()
loggedItems = loggedItems ++ Map(
("app_customer" -> "logback.com"),
("app_device" -> ""),
("log_msg" -> "specific message for elastic ...")) log.debug(toJson(loggedItems)) //logback.xml
<appender name="elasticLogger" class="com.datatech.logback.ElasticAppender">
<host>http://localhost</host>
<port></port>
<appName>ESLoggerDemo</appName>
<defaultFieldValues>{"app_customer":"中心书城","app_device":""}</defaultFieldValues>
<indexName>applog</indexName>
</appender>
上面代码里定义了app_customer,app_device,log_msg这几个自定义字段和值。这样做的意思是:logback只定义了log.info(msg)里msg一个字段。如果存放在数据库里我们只能在msg一个字段里进行分类、查询了。但既然已经使用了数据库作为存储我们更希望用更多的字段来代表一条消息,如用户号,机器号,店号等等。这样跟踪起来方便很多。所以,对于内部的用户可以要求把因应特殊需要额外增加的字段-值加密成json,然后传递给ElasticAppender去处理。对于应用中引用三方软件所产生的logback-msg,我们可没办法要求他们按照这个格式来传递消息,但仍然会存进ES,所以就用logback.xml中defaultFieldValaues定义的默认字段-值来填写这些额外的信息了。
这一篇我们主要讨论一下这个特别的elastic-appender,它的使用方法。那么先重复一下logback的工作原理:
首先认识一下logback:感觉需要重点了解的logging运作核心应该是消息等级level的操作。消息等级是指logback根据不同的消息等级来筛选需要记录的消息。logback支持下面几个消息等级,按照各自记录动作覆盖面由弱到强排列,包括:
TRACE -> DEBUG -> INFO -> WARN -> ERROR 分别对应记录函数 trace(msg),debug(msg),info(msg),warn(msg),error(msg)
logback按消息等级进行记录筛选的规则如下:
假设记录函数为p,某个class的消息等级level为q:当p>=q时选择记录消息。换言之调用函数error(msg)时logback会记录所有等级消息,反之trace(msg)只能记录TRACE级别的消息。logback手册中如下表示:
TRACE DEBUG INFO WARN ERROR OFF
trace() YES NO NO NO NO NO
debug() YES YES NO NO NO NO
info() YES YES YES NO NO NO
warn() YES YES YES YES NO NO
error() YES YES YES YES YES NO
logback中每个类的默认消息等级可以按照类型继承树结构继承。当一个子类没有定义消息等级时,它继承对上父类的消息等级,即:X.Y.Z中Z的默认消息等级从Y继承。
再看看下面logback.xml例子:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<Pattern>
%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{} - %msg%n
</Pattern>
</encoder>
</appender> <appender name="FILE" class="ch.qos.logback.core.FileAppender">
<!-- path to your log file, where you want to store logs -->
<file>~/logback.log</file>
<append>false</append>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{} - %msg%n</pattern>
</encoder>
</appender> <appender name="cassandraLogger" class="com.datatech.logback.CassandraAppender">
<appName>POCServer</appName>
<defaultFieldValues>{"app_customer":"","app_device":""}</defaultFieldValues>
<keyspaceName>applog</keyspaceName>
<columnFamily>txnlog</columnFamily>
</appender> <appender name="elasticLogger" class="com.datatech.logback.ElasticAppender">
<host>http://localhost</host>
<port></port>
<appName>ESLoggerDemo</appName>
<defaultFieldValues>{"app_customer":"中心书城","app_device":""}</defaultFieldValues>
<indexName>applog</indexName>
</appender> <logger name="com.datatech" level="info"
additivity="false">
<appender-ref ref="cassandraLogger" />
<appender-ref ref="elasticLogger" />
<appender-ref ref="STDOUT" />
</logger> <logger name="com.datatech.sdp" level="info"
additivity="false">
<appender-ref ref="cassandraLogger" />
<appender-ref ref="elasticLogger" />
<appender-ref ref="STDOUT" />
</logger> <root level="info">
<appender-ref ref="cassandraLogger" />
<appender-ref ref="elasticLogger" />
<appender-ref ref="STDOUT" />
</root> <shutdownHook class="ch.qos.logback.core.hook.DelayingShutdownHook"/>
</configuration>
上面配置文件中定义了包括STDOUT,FILE,cassandraLoggeer,elasticLogger几个appender。首先,不同level可以使用不同的appender。cassandraLogger,elasticLogger是我们自定义的appender。在elasticLogger段落里定义了ES终端连接参数如host,port。在ElasticAppender类源码中的elastic终端连接和关闭如下:
override def start(): Unit = {
if(! _hosts.isEmpty) {
connectES()
super.start()
}
}
override def stop(): Unit = {
if(optESClient.isDefined) {
(optESClient.get).close()
optESClient = None
}
super.stop()
}
def connectES(): Unit = {
try {
val url = _hosts + ":" + _port.toString
val esjava = JavaClient(ElasticProperties(url))
val client = ElasticClient(esjava)
optESClient = Some(client)
} catch {
case e: Throwable =>
optESClient = None
}
}
注意,假如host在logback.xml里定义了那么在ElasticAppender实例化时系统会自动直接连接,否则需要手工调用logger.start()来连接ES。xml文件里的属性是通过getter来获取的,如下:
private var _hosts: String = ""
def setHost(host: String): Unit = _hosts = host
def getHost : String = _hosts private var _port: Int =
def setPort(port: Int): Unit = _port = port private var _idxname: String = "applog"
def setIndexName(indexName: String): Unit = _idxname = indexName private var _username: String = ""
def setUsername(username: String): Unit = _username = username private var _password: String = ""
def setPassword(password: String): Unit = _password = password private var _defaultFieldValues: String = ""
def setDefaultFieldValues(defaultFieldValues: String) = _defaultFieldValues = defaultFieldValues
下面是ElasticAppender的使用示范:(先把logback_persist.jar放入lib目录)
import scala.concurrent.ExecutionContext.Implicits.global
import com.sksamuel.elastic4s.ElasticDsl._
import com.sksamuel.elastic4s.http.JavaClient
import com.sksamuel.elastic4s.{ElasticClient, ElasticProperties}
import ch.qos.logback.classic.Logger
import ch.qos.logback.core.{ConsoleAppender, FileAppender}
import com.datatech.logback.{CassandraAppender,ElasticAppender, JsonConverter}
import ch.qos.logback.classic.spi.ILoggingEvent
import org.slf4j.LoggerFactory
import ch.qos.logback.classic.LoggerContext
import java.time._
import java.time.format._
import java.util.Locale object ElasticAppenderDemo extends App with JsonConverter {
val log: Logger = LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME).asInstanceOf[Logger]
val elasticAppender = log.getAppender("elasticLogger").asInstanceOf[ElasticAppender]
val stdoutAppender = log.getAppender("STDOUT").asInstanceOf[ConsoleAppender[ILoggingEvent]]
val fileAppender = log.getAppender("FILE").asInstanceOf[FileAppender[ILoggingEvent]]
val cassAppender = log.getAppender("cassandraLogger").asInstanceOf[CassandraAppender] //stop other appenders
if (stdoutAppender != null)
stdoutAppender.stop()
if (fileAppender != null)
fileAppender.stop()
if (cassAppender != null)
cassAppender.stop() //check if host not set in logback.xml
if(elasticAppender != null) {
if (elasticAppender.getHost.isEmpty) {
elasticAppender.setHost("http://localhost")
elasticAppender.setPort()
elasticAppender.start()
}
} val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
val now = LocalDateTime.now.format(dateTimeFormatter) ( to ).foreach { idx =>
log.info(s"************this is a info message $idx ")
} log.debug("***********debugging message here ..." + now) log.debug(toJson(loggedItems)) //stop the logger val loggerContext = LoggerFactory.getILoggerFactory.asInstanceOf[LoggerContext]
loggerContext.stop() }
在Appender实例化时getAppender("elasticLogger")中这个elasticLogger是xml文件中appender段落名称。如果host,port没在xml文件中定义的话可以手工用setter setHost,setPort在程序里设置。loggerContext.stop()一次性关闭所有appender,包括它们连接的数据库。也可以用elasticAppender.stop()来关闭独立的appender。
我们可以用elastic4自定义一个表结构mapping, 如下:
val esjava = JavaClient(ElasticProperties("http://localhost:9200"))
val client = ElasticClient(esjava)
//删除索引
val rspExists = client.execute(indexExists("applog")).await
if (rspExists.result.exists)
client.execute(deleteIndex("applog")).await
//构建索引
val idxCreate = client.execute(createIndex("applog")
.shards().replicas()).await
//创建表结构
if(idxCreate.isSuccess) {
val applogMapping = client.execute(
putMapping("applog").fields(
textField("class_name"),
textField("file_name"),
ipField("host_ip"),
textField("host_name"),
keywordField("level"),
keywordField("line_number"),
keywordField("logger_name"),
keywordField("method_name"),
keywordField("thread_name"),
textField("throwable_str_rep"),
dateField("log_date").format("basic_date").ignoreMalformed(true),
dateField("log_time").format("basic_date_time").ignoreMalformed(true),
textField("log_msg"),
keywordField("app_name"),
keywordField("app_customer"),
keywordField("app_device")
)).await
if(applogMapping.isSuccess)
println(s"mapping successfully created.")
else
println(s"mapping creation error: ${applogMapping.error.reason}")
} else {
println(s"index creation error: ${idxCreate.error.reason}")
}
client.close()
依赖引用在build.sbt里:
name := "logback-persist-demo" version := "0.1" scalaVersion := "2.12.9" val elastic4sVersion = "7.6.0" libraryDependencies ++= Seq(
"com.datastax.cassandra" % "cassandra-driver-core" % "3.6.0",
"com.datastax.cassandra" % "cassandra-driver-extras" % "3.6.0", "com.sksamuel.elastic4s" %% "elastic4s-core" % elastic4sVersion, // for the default http client
"com.sksamuel.elastic4s" %% "elastic4s-client-esjava" % elastic4sVersion, "ch.qos.logback" % "logback-classic" % "1.2.3",
"org.typelevel" %% "cats-core" % "2.0.0-M1",
"org.json4s" %% "json4s-native" % "3.6.1",
"org.json4s" %% "json4s-jackson" % "3.6.7",
"org.json4s" %% "json4s-ext" % "3.6.7"
)
search(9)- elastic4s logback-appender的更多相关文章
- java日志框架系列(4):logback框架xml配置文件语法
1.xml配置文件语法 由于logback配置文件语法特别灵活,因此无法用DTD或schema进行定义. 1.配置文件基本结构 配置文件基本结构:以<configuration>标签开头, ...
- 【CF528D】Fuzzy Search(FFT)
[CF528D]Fuzzy Search(FFT) 题面 给定两个只含有\(A,T,G,C\)的\(DNA\)序列 定义一个字符\(c\)可以被匹配为:它对齐的字符,在距离\(K\)以内,存在一个字符 ...
- java日志框架系列(5):logback框架appender详解
1.appender 1.什么是appender Appender 是负责写记录事件的组件. Appender 必须实现接口“ch.qos.logback.core.Appender”.该接口的重要方 ...
- java日志框架系列(9):logback框架过滤器(filter)详解
过滤器放在了logback-classic模块中. 1.logback-classic模块中过滤器 分类(2种):常规过滤器.TurboFilter过滤器. 1.常规过滤器 常规过滤器可以通过自定义进 ...
- java日志框架系列(6):logback框架encoder详解
1.Encoder 1.encoder功能 Encoder 负责两件事,一是把事件转换为字节数组,二是把字节数组写入输出流. 注意:在logback 0.9.19 版之前没有 encoder. 在之前 ...
- java日志框架系列(2):logback框架详解
1.logback介绍 1.什么是logback Logback 为取代 log4j 而生. Logback 由 log4j 的创立者 Ceki Gülcü设计.以十多年设计工业级记录系统的经验为基础 ...
- springboot(十)使用LogBack作为日志组件
简介: 企业级项目在搭建的时候,最不可或缺的一部分就是日志,日志可以用来调试程序,打印运行日志以及错误信息方便于我们后期对系统的维护,在SpringBoot兴起之前记录日志最出色的莫过于log4j了, ...
- search(0)- 企业搜索,写在前面
计划研究一下搜索search,然后写个学习过程系列博客.开动之前先说说学习搜索的目的:不是想开发个什么搜索引擎,而是想用现成的搜索引擎在传统信息系统中引进搜索的概念和方法.对我来说,传统的管理系统le ...
- search(16)- elastic4s-内嵌文件:nested and join
从SQL领域来的用户,对于ES的文件关系维护方式会感到很不习惯.毕竟,ES是分布式数据库只能高效处理独个扁平类型文件,无法支持关系式数据库那样的文件拼接.但是,任何数据库应用都无法避免树型文件关系,因 ...
随机推荐
- winform怎么实现财务上凭证录入和打印
序言 现如今存在的财务软件层出不穷,怎么样让自己的业务系统与财务系统相结合,往往是很多公司头痛的问题.大多数公司也没有这个能力都去开发一套属于自己的财务软件,所以只有对接像金蝶用友这类的财务软件,花费 ...
- Kubectl patch命令使用
kubectl patch 使用(patch)补丁修改.更新资源的字段. 支持JSON和YAML格式. 请参阅https://htmlpreview.github.io/?https://github ...
- Mycat使用配置实践
本来写了好多,关于配置的解释和使用以及注意,但是发现有点啰嗦含金量也不高,所以直接把实际使用的一个例子放着吧,供参考. <!DOCTYPE mycat:schema SYSTEM "s ...
- OSI 七层模型以及TCP/IP模型
OSI 七层模型 定义 OSI(Open System Interconnection)即开放式系统互联通信参考模型.该模型是国际标准化组织(ISO)制定的一个用于计算机或通信系统间互联的标准体系,一 ...
- 试验使用t检验
官方解释 Excel中使用T.TEST函数 T.TEST(array1,array2,tails,type) Array1 必需.第一个数据集. Array2 必需.第二个数据集. ...
- 学习Salesforce | Einstein业务机会评分怎么玩
Einstein 业务机会评分(Opportunity Scoring)是销售团队的得力助手,通过分数以及研究影响分数的因素,确定业务机会的优先级,赢得更多交易. Einstein 业务机会评分可以给 ...
- C++基础的一些代码和笔记 stl乱炖
STL: 标准模板库.各种函数的模板和类的模板几个概念:容器:可容纳各种数据类型的通用数据结构,是类模板.迭代器:可用于依次存取容器中的元素,类似于指针,用iterator来进行对一个容器中单个元素的 ...
- 怎样才能拥有营销号生成器功能?python帮你实现
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http ...
- D - Complete Tripartite
三分图染色 链接:https://codeforces.com/contest/1228/problem/D 三分图染色步骤:First 首先找一个点1作为集合A中的点,再找到与1相连的一个点设为2, ...
- 基于canvas的画板
最近重新在看Html5&CSS3的知识,看到canvas的时候,想到了以前在学校学计算机图形学时做过的画图实验,于是想,可以基于html5和css3来做一款画板,经过1天的努力,完成了画板的一 ...