All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ecfront.ez.framework.service.distributed.trace.DTrace.scala Maven / Gradle / Ivy

The newest version!
package com.ecfront.ez.framework.service.distributed.trace

import java.util.Date

import com.ecfront.ez.framework.core.helper.TimeHelper
import com.ecfront.ez.framework.service.redis.RedisProcessor
import com.typesafe.scalalogging.slf4j.LazyLogging

import scala.beans.BeanProperty
import scala.collection.mutable.ArrayBuffer

/**
  * 分布式追踪日志
  */
object DTrace extends LazyLogging {

  /**
    * 追踪日志定义
    * 日志流程定义code -> 日志节点code -> 日志节点定义
    */
  private val traceNodeDefs = collection.mutable.Map[String, collection.mutable.Map[String, TraceNodeDef]]()
  /**
    * 追踪日志状态持久化到Redis
    */
  private val rMap = RedisProcessor.custom().getMap[String, TraceFlowInst]("ez:trace")

  /**
    * 追踪日志流程定义
    *
    * @param flowCode 流程code,要求全局唯一
    * @param flow     流程定义,支持分支,如
    *                 List(
    *                 "m1#s1",
    *                 "m1#s2",
    *                 List(
    *                 List("m2#s3-1", "m2#s4-1"),
    *                 List("m3#s3-2")
    *                 ),
    *                 "m1#s5"
    *                 )
    */
  def define(flowCode: String, flow: List[Any]): Unit = {
    traceNodeDefs += flowCode -> collection.mutable.Map[String, TraceNodeDef]()
    addNodeDefs(flowCode, List(), flow)
  }

  private def addNodeDefs(flowCode: String, _parentNodes: List[TraceNodeDef], node: List[Any]): List[TraceNodeDef] = {
    var parentNodes = _parentNodes
    node.foreach {
      case nodeCode: String =>
        val module = nodeCode.split("#")(0)
        val stage = nodeCode.split("#")(1)
        val parentNodeCodes = parentNodes.map(_.code)
        val node = TraceNodeDef(nodeCode, module, stage, parentNodeCodes)
        traceNodeDefs(flowCode) += nodeCode -> node
        parentNodes.foreach(_.childrenNodeCodes += node.code)
        parentNodes = List(node)
      case node: List[List[Any]] =>
        parentNodes = node.flatMap(addNodeDefs(flowCode, parentNodes, _))
      case _ =>
        throw new Exception("Trace define error,flow only support [string][list] types")
    }
    parentNodes
  }

  /**
    * 写日志
    *
    * @param clueId   追踪线索,每个流程实例唯一,如贷款流程追踪可用身份证号做为clueId
    * @param flowCode 日志流程定义code
    * @param nodeCode 当前节点code,由模块#阶段 组成
    * @param message  消息
    */
  def simpleLog(flowCode: String)(clueId: String, nodeCode: String, message: String): Unit = {
    log(clueId, flowCode, nodeCode.split("#")(0), nodeCode.split("#")(1), message)
  }

  /**
    * 写日志
    *
    * @param clueId   追踪线索,每个流程实例唯一,如贷款流程追踪可用身份证号做为clueId
    * @param flowCode 日志流程定义code
    * @param module   当前模块
    * @param stage    当前阶段
    * @param message  消息
    */
  def flowLog(flowCode: String)(clueId: String, module: String, stage: String, message: String): Unit = {
    log(clueId, flowCode, module, stage, message)
  }

  /**
    * 写日志
    *
    * @param clueId   追踪线索,每个流程实例唯一,如贷款流程追踪可用身份证号做为clueId
    * @param flowCode 日志流程定义code
    * @param module   当前模块
    * @param stage    当前阶段
    * @param message  消息
    */
  def log(clueId: String, flowCode: String, module: String, stage: String, message: String): Unit = {
    val instCode = flowCode + clueId
    try {
      // 根据传入参数获取实际的节点
      val realCurrNode = traceNodeDefs(flowCode)(module + "#" + stage)
      // 从Redis获取流程实例状态或新建一个流程实例状态
      val flowInst =
      if (realCurrNode.parentNodeCodes.isEmpty) {
        val inst = new TraceFlowInst
        inst.startTime = new Date()
        inst.parentNodeCode = ""
        inst.flow = ""
        inst.stepNumber = 0
        inst.success = true
        inst
      } else {
        rMap.get(instCode)
      }
      if (flowInst != null) {
        // 根据流程实例状态对照流程定义获取期望的节点codes
        val expectNodeCodes =
        if (realCurrNode.parentNodeCodes.isEmpty) {
          // 初始节点,期望节点肯定等于实际节点
          ArrayBuffer(realCurrNode.code)
        } else {
          traceNodeDefs(flowCode)(flowInst.parentNodeCode).childrenNodeCodes
        }
        flowInst.flow = flowInst.flow + " > " + realCurrNode.code
        // 写日志
        val logStr = new StringBuffer()
        if (realCurrNode.parentNodeCodes.isEmpty) {
          logStr.append(s"\r\n=|$instCode|${flowInst.stepNumber}|======================= START [$flowCode] ========================")
        } else {
          logStr.append(s"\r\n=|$instCode|${flowInst.stepNumber}|-------------------------------------------------------------------")
        }
        logStr.append(s"\r\n=|$instCode|${flowInst.stepNumber}|= ${TimeHelper.yyyy_MM_dd_HH_mm_ss_SSS.format(new Date())} [$flowCode] for [$clueId] at [$module]-[$stage] : $message")
        logStr.append(s"\r\n=|$instCode|${flowInst.stepNumber}|= Flow [${flowInst.flow}]")
        if (!expectNodeCodes.contains(realCurrNode.code)) {
          logStr.append(s"\r\n=|$instCode|${flowInst.stepNumber}|= Expect current in [${expectNodeCodes.mkString("/")}] But real current is ${realCurrNode.code}")
          flowInst.success = false
        }
        if (realCurrNode.childrenNodeCodes.isEmpty) {
          logStr.append(s"\r\n=|$instCode|${flowInst.stepNumber}|= Result [${if (flowInst.success) "SUCCESS" else "FAIL"}] , Use Time [${new Date().getTime - flowInst.startTime.getTime}ms]")
          logStr.append(s"\r\n=|$instCode|${flowInst.stepNumber}|======================= FINISH [$flowCode] ========================")
          rMap.remove(instCode)
        }
        if (flowInst.success) {
          logger.info(logStr.toString)
        } else {
          logger.warn(logStr.toString)
        }
        // 更新流程实例状态
        flowInst.parentNodeCode = realCurrNode.code
        flowInst.stepNumber = flowInst.stepNumber + 1
        rMap.put(instCode, flowInst)
      } else {
        logger.warn(s"\r\n=|$instCode|${flowInst.stepNumber}|= Not found start node [$flowCode] for [$clueId] at [$module]-[$stage] : $message")
      }
    } catch {
      case e: Throwable =>
        logger.error(s"Trace log write error | [$flowCode] for [$clueId] at [$module]-[$stage] : $message", e)
    }
  }
}

/**
  * 追踪日志流程实例状态,持久化到Redis中
  */
class TraceFlowInst {
  // 开始时间
  @BeanProperty var startTime: Date = _
  // 父节点code,上次执行的节点code
  @BeanProperty var parentNodeCode: String = _
  // 当前时序
  @BeanProperty var stepNumber: Int = _
  // 流程描述
  @BeanProperty var flow: String = _
  // 是否成功
  @BeanProperty var success: Boolean = _
}

/**
  * 追踪日志节点定义
  *
  * @param code              节点code
  * @param module            节点所属模块
  * @param stage             节点所属阶段
  * @param parentNodeCodes   父节点Codes
  * @param childrenNodeCodes 子节点Codes
  */
case class TraceNodeDef(code: String, module: String, stage: String,
                        parentNodeCodes: List[String] = List(), childrenNodeCodes: ArrayBuffer[String] = ArrayBuffer())




© 2015 - 2024 Weber Informatics LLC | Privacy Policy