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是分布式数据库只能高效处理独个扁平类型文件,无法支持关系式数据库那样的文件拼接.但是,任何数据库应用都无法避免树型文件关系,因 ... 
随机推荐
- json.dumps() 和 json.loads()
			转载: https://blog.csdn.net/qinglingls/article/details/96476368 
- 【硬核】使用替罪羊树实现KD-Tree的增删改查
			本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是机器学习的第16篇文章,我们来继续上周KD-Tree的话题. 如果有没有看过上篇文章或者是最新关注的小伙伴,可以点击一下下方的传送门: ... 
- TP的where方法的使用
			1.Thinkphp中where()条件的使用 总是有人觉得,thinkphp的where()就是写我要进行增加.查询.修改.删除数据的条件,很简单的,其实我想告诉你,where()是写条件语句的,但 ... 
- Java 数据类型转换的骚操作和神奇现象
			问:银行用什么数据类型来表示余额?float还是double? 都不是!!下面说明为什么都不可以呢? public static void main(String[] args) { //浮点数他都是 ... 
- 简单的Tuple声明和输出
			using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ... 
- stand up meeting 12/2/2015
			part 组员 今日工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 将logic部分提供的delete接口接入,与logic,数据库部分沟通实现了add/delete按钮的复用:解决UI部 ... 
- 学会这项python技能,就再也不怕孩子偷偷打游戏了
			前言 本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:鸟哥 PS:如果想了解更多关于python的应用,可以私信小编,资料 ... 
- D. Feeding Chicken(构造)
			题目大意:将k个鸡放到一个n*m的矩阵中,要求每个鸡所占的rice的个数只差最小 题解:构造,设一共有cnt个rice,可以分cnt/k个,即每一只鸡要么占用cnt/k个rice,要么占cnt/k+1 ... 
- [转+自]SSH工作原理
			SSH工作原理 熟悉Linux的人肯定都知道SSH.SSH是一种用于安全访问远程服务器的网络协议.它将客户端与服务端之间的消息通过加密保护起来,这样就无法被窃取或篡改了.那么它安全性是如何实现的呢? ... 
- redis: 乐观锁(十)
			监视:watch 正常业务(单线程): 127.0.0.1:6379> set money 100 #模拟存款100元 OK 127.0.0.1:6379> set moneyout 0 ... 
