前面写了个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的更多相关文章

  1. java日志框架系列(4):logback框架xml配置文件语法

    1.xml配置文件语法 由于logback配置文件语法特别灵活,因此无法用DTD或schema进行定义. 1.配置文件基本结构 配置文件基本结构:以<configuration>标签开头, ...

  2. 【CF528D】Fuzzy Search(FFT)

    [CF528D]Fuzzy Search(FFT) 题面 给定两个只含有\(A,T,G,C\)的\(DNA\)序列 定义一个字符\(c\)可以被匹配为:它对齐的字符,在距离\(K\)以内,存在一个字符 ...

  3. java日志框架系列(5):logback框架appender详解

    1.appender 1.什么是appender Appender 是负责写记录事件的组件. Appender 必须实现接口“ch.qos.logback.core.Appender”.该接口的重要方 ...

  4. java日志框架系列(9):logback框架过滤器(filter)详解

    过滤器放在了logback-classic模块中. 1.logback-classic模块中过滤器 分类(2种):常规过滤器.TurboFilter过滤器. 1.常规过滤器 常规过滤器可以通过自定义进 ...

  5. java日志框架系列(6):logback框架encoder详解

    1.Encoder 1.encoder功能 Encoder 负责两件事,一是把事件转换为字节数组,二是把字节数组写入输出流. 注意:在logback 0.9.19 版之前没有 encoder. 在之前 ...

  6. java日志框架系列(2):logback框架详解

    1.logback介绍 1.什么是logback Logback 为取代 log4j 而生. Logback 由 log4j 的创立者 Ceki Gülcü设计.以十多年设计工业级记录系统的经验为基础 ...

  7. springboot(十)使用LogBack作为日志组件

    简介: 企业级项目在搭建的时候,最不可或缺的一部分就是日志,日志可以用来调试程序,打印运行日志以及错误信息方便于我们后期对系统的维护,在SpringBoot兴起之前记录日志最出色的莫过于log4j了, ...

  8. search(0)- 企业搜索,写在前面

    计划研究一下搜索search,然后写个学习过程系列博客.开动之前先说说学习搜索的目的:不是想开发个什么搜索引擎,而是想用现成的搜索引擎在传统信息系统中引进搜索的概念和方法.对我来说,传统的管理系统le ...

  9. search(16)- elastic4s-内嵌文件:nested and join

    从SQL领域来的用户,对于ES的文件关系维护方式会感到很不习惯.毕竟,ES是分布式数据库只能高效处理独个扁平类型文件,无法支持关系式数据库那样的文件拼接.但是,任何数据库应用都无法避免树型文件关系,因 ...

随机推荐

  1. C++语言实现顺序栈

    C++语言实现顺序栈 在写C语言实现顺序栈的时候,我已经向大家介绍了栈的特点以及介绍了栈的相关操作,并利用C语言实现了相关算法.在这里小编就不在继续给大家介绍了,需要温习的可以去我的博客看看.在这篇博 ...

  2. k8s Service学习

    service的概念 kubernetes service定义了一个抽象概念,一个pod的逻辑分组,一种可以访问的策略---通常称为服务.这组pod能够被service访问到,通常通过label se ...

  3. Docker之hello world

    Docker Hello World Docker 允许你在容器内运行应用程序, 使用 docker run 命令来在容器内运行一个应用程序. 输出Hello world runoob@runoob: ...

  4. 【Java】关键字 和 保留字

    Java的关键字[Keyword]和 保留字[Reserved word] 官方描述: https://docs.oracle.com/javase/tutorial/java/nutsandbolt ...

  5. 绕过CDN查找真实 IP 姿势总结

    返回域名解析对应多个 IP 地址,网站可能部署CDN业务,我们就需要bypass CDN,去查找真正的服务器ip地址 0x01.域名搜集 由于成本问题,可能某些厂商并不会将所有的子域名都部署 CDN, ...

  6. Ajax 简述与基础语法

    目录 Ajax 1. 原生 JS 实现 Ajax 2. 使用 Ajax 实现异步通信 a. Ajax 的基础语法 b. 用 Ajax 传递数据 i. 传递字符串数据 ii. 传递 JSON 数据 3. ...

  7. CSS两种盒子模型:cntent-box和border-box

    cntent-box 平时普通盒子模型,padding,border盒子会变大,向外扩展border-box 特殊盒子模型,padding,border盒子会变大,向内扩展

  8. Python中赋值、浅拷贝和深拷贝的区别

    前言文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. PS:如有需要Python学习资料的小伙伴可以加点击下方链接自行获取http: ...

  9. el-tab-pane label的文字内容怎样设间距

    el-tab-pane label的文字内容怎样设间距 问题描述: 在使用element-ui的el-tab-pane做标签页时,label属性的位置与样式不能通过style样式直接解决 百度后几乎没 ...

  10. js的localStorage基础认识

    新建a.html文件: <!DOCTYPE html> <html> <body> <div id="result"></di ...