package com.jthink.skyeye.client.kafka.logback;
import ch.qos.logback.core.Context;
import ch.qos.logback.core.CoreConstants;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import ch.qos.logback.core.hook.DelayingShutdownHook;
import ch.qos.logback.core.status.ErrorStatus;
import com.jthink.skyeye.base.constant.RpcType;
import com.jthink.skyeye.client.constant.KafkaConfig;
import com.jthink.skyeye.client.constant.NodeMode;
import com.jthink.skyeye.client.kafka.LazySingletonProducer;
import com.jthink.skyeye.client.kafka.logback.encoder.KafkaLayoutEncoder;
import com.jthink.skyeye.client.kafka.partitioner.KeyBuilder;
import com.jthink.skyeye.client.kafka.partitioner.KeyModPartitioner;
import com.jthink.skyeye.client.register.ZkRegister;
import com.jthink.skyeye.client.util.SysUtil;
import com.jthink.skyeye.base.util.StringUtil;
import com.jthink.skyeye.base.constant.Constants;
import com.jthink.skyeye.trace.dto.RegisterDto;
import com.jthink.skyeye.trace.generater.IncrementIdGen;
import com.jthink.skyeye.trace.registry.Registry;
import com.jthink.skyeye.trace.registry.ZookeeperRegistry;
import org.I0Itec.zkclient.ZkClient;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.ByteArraySerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* JThink@JThink
*
* @author JThink
* @version 0.0.1
* @desc KafkaAppender, 包含logback kafka appender的配置
* @date 2016-09-08 20:10:21
*/
public class KafkaAppender<E> extends UnsynchronizedAppenderBase<E> {
// kafka topic
private String topic;
// 生产日志的host
private String host;
// 生产日志的app,多节点部署会使日志有序
private String app;
// zookeeper的地址
private String zkServers;
// 接受报警邮件的接收方
private String mail;
// 标记是否为rpc服务, 取值为RpcType.java
private String rpc;
// KafkaProducer类的配置
private Map<String, Object> config = new HashMap<String, Object>();
// key生成器
private KeyBuilder<? super E> keyBuilder;
// 编码器
private KafkaLayoutEncoder<E> encoder;
// zk注册器
private ZkRegister zkRegister;
// hook
private DelayingShutdownHook shutdownHook;
// kafkaAppender遇到异常需要向zk进行写入数据,由于onCompletion()的调用在kafka集群完全挂掉时会有很多阻塞的日志会调用,所以我们需要保证只向zk写一次数据,监控中心只会发生一次报警
private volatile AtomicBoolean flag = new AtomicBoolean(true);
/**
* 构造方法
*/
public KafkaAppender() {
this.checkAndSetConfig(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class.getName());
this.checkAndSetConfig(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// 设置分区类, 使用自定义的KeyModPartitioner,同样的key进入相同的partition
this.checkAndSetConfig(ProducerConfig.PARTITIONER_CLASS_CONFIG, KeyModPartitioner.class.getName());
shutdownHook = new DelayingShutdownHook();
}
@Override
public void start() {
// xml配置校验
if (!this.checkNecessaryConfig()) {
addError("necessary config is not set, kafka appender is not started");
return;
}
super.start();
// 添加logback shutdown hook, 关闭所有的appender, 调用stop()方法
shutdownHook.setContext(this.getContext());
Runtime.getRuntime().addShutdownHook(new Thread(this.shutdownHook));
// 初始化zk
this.zkRegister = new ZkRegister(new ZkClient(this.zkServers, 60000, 5000));
// 注册永久节点用于历史日志查询
this.zkRegister.create(Constants.SLASH + this.app + Constants.SLASH + this.host, NodeMode.PERSISTENT);
this.zkRegister.getClient().writeData(Constants.ROOT_PATH_PERSISTENT + Constants.SLASH + this.app + Constants.SLASH + this.host,
this.mail + Constants.SEMICOLON + SysUtil.userDir);
// 注册临时节点用于日志滚屏
this.zkRegister.getClient().createPersistent(Constants.ROOT_PATH_EPHEMERAL + Constants.SLASH + this.app, true);
this.zkRegister.create(Constants.SLASH + this.app + Constants.SLASH + this.host, NodeMode.EPHEMERAL,
Constants.APPENDER_INIT_DATA + Constants.SEMICOLON + SysUtil.userDir);
// rpc trace注册中心
if (this.rpc.equals(RpcType.dubbo.symbol())) {
this.register(this.app, this.host, this.zkRegister.getClient());
}
}
/**
* 进行rpc trace注册
* @param app
* @param host
* @param zkClient
*/
private void register(String app, String host, ZkClient zkClient) {
RegisterDto dto = new RegisterDto(app, host, zkClient);
Registry registry = new ZookeeperRegistry();
IncrementIdGen.setId(registry.register(dto));
}
@Override
public void stop() {
super.stop();
// 关闭KafkaProuder
if (LazySingletonProducer.isInstanced()) {
// producer实际上已经初始化
LazySingletonProducer.getInstance(this.config).close();
}
// 关闭client,临时节点消失,监控系统进行感知报警
ZkClient client = this.zkRegister.getClient();
if (null != client) {
client.close();
}
}
@Override
protected void append(E e) {
if (!isStarted()) {
return;
}
final String value = System.nanoTime() + Constants.SEMICOLON + this.encoder.doEncode(e);
final byte[] key = this.keyBuilder.build(e);
final ProducerRecord<byte[], String> record = new ProducerRecord<byte[], String>(this.topic, key, value);
LazySingletonProducer.getInstance(this.config).send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata recordMetadata, Exception e) {
// TODO: 异常发生如何处理(目前使用RollingFileAppender.java中的方法)
if (null != e) {
// 如果发生异常, 将开始状态设置为false, 并每次append的时候都先check该状态
started = false;
addStatus(new ErrorStatus("kafka send error in appender", this, e));
// 发生异常,kafkaAppender 停止收集,向节点写入数据(监控系统会感知进行报警)
if (flag.get() == true) {
zkRegister.write(Constants.SLASH + app + Constants.SLASH + host, NodeMode.EPHEMERAL,
String.valueOf(System.currentTimeMillis()) + Constants.SEMICOLON + SysUtil.userDir);
flag.compareAndSet(true, false);
}
}
}
});
}
@Override
public void setContext(Context context) {
super.setContext(context);
this.host = context.getProperty(CoreConstants.HOSTNAME_KEY);
this.app = context.getName();
}
/**
* 校验最基本的配置是否在logback.xml进行配置
* @return
*/
private boolean checkNecessaryConfig() {
boolean flag = true;
// app配置
if (StringUtil.isBlank(this.host)) {
// host未获取到
addError("can't get the host");
flag = false;
}
if (StringUtil.isBlank(this.app)) {
// app name未设置
addError("logback.xml is not set the <contextName></contextName> node");
flag = false;
}
// zk配置
if (StringUtil.isBlank(this.zkServers)) {
// zk地址未设置
addError("can't get zkServers");
flag = false;
}
// kafka配置
if (null == config.get(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG)) {
// kafka的bootstrap.servers未设置
addError("kafka's " + ProducerConfig.BOOTSTRAP_SERVERS_CONFIG + " do not set, appender: " + name);
flag = false;
}
if (StringUtil.isBlank(this.topic)) {
// topic未设置(或者设置成了""),无法写入kafka
addError("topic is not set, appender: " + name);
flag = false;
}
if (StringUtil.isBlank(this.mail)) {
// 报警mail未设置
addError("mail is not set, appender: " + name);
flag = false;
}
if (StringUtil.isBlank(this.rpc) || !this.checkRpcType(this.rpc)) {
// rpc未设置或者rpc值不对
addError("rpc is not set or value not right, appender: " + name);
flag = false;
}
if (null == this.keyBuilder) {
// key生成器为设置
addError("key builder is not set, appender: " + name);
flag = false;
}
if (null == this.encoder) {
// 编码器未设置
addError("encoder is not set, appender: " + name);
flag = false;
}
return flag;
}
/**
* 监察rpc type是否正确
* @param rpcType
* @return
*/
private boolean checkRpcType(String rpcType) {
try {
RpcType.valueOf(rpcType);
return true;
} catch (Exception e) {
return false;
}
}
/**
* 将logback配置文件中<config></config>节点中的值读入Map<String, String> config
* @param kv
*/
public void addConfig(String kv) {
String[] keyValue = kv.split(Constants.EQUAL, 2);
if (keyValue.length == 2) {
this.checkAndSetConfig(keyValue[0], keyValue[1]);
} else {
// 值设置得不对
addError("config item value is wrong, appender: " + name);
}
}
/**
* 进行kafka配置设置
* @param key
* @param value
*/
public void checkAndSetConfig(String key, String value) {
if (!KafkaConfig.PRODUCER_CONFIG_KEYS.contains(key)) {
// 当前kafka版本没有该配置项
addWarn("in this kafka version don't has this config: " + key);
}
this.config.put(key, value);
}
public String getTopic() {
return topic;
}
public void setTopic(String topic) {
this.topic = topic;
}
public Map<String, Object> getConfig() {
return config;
}
public void setConfig(Map<String, Object> config) {
this.config = config;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getApp() {
return app;
}
public void setApp(String app) {
this.app = app;
}
public KeyBuilder<? super E> getKeyBuilder() {
return keyBuilder;
}
public void setKeyBuilder(KeyBuilder<? super E> keyBuilder) {
this.keyBuilder = keyBuilder;
}
public KafkaLayoutEncoder<E> getEncoder() {
return encoder;
}
public void setEncoder(KafkaLayoutEncoder<E> encoder) {
this.encoder = encoder;
}
public String getZkServers() {
return zkServers;
}
public void setZkServers(String zkServers) {
this.zkServers = zkServers;
}
public String getMail() {
return mail;
}
public void setMail(String mail) {
this.mail = mail;
}
public String getRpc() {
return rpc;
}
public void setRpc(String rpc) {
this.rpc = rpc;
}
}