package com.jthink.skyeye.client.kafka.log4j; 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.partitioner.KeyModPartitioner; import com.jthink.skyeye.client.register.ZkRegister; import com.jthink.skyeye.client.util.SysUtil; import com.jthink.skyeye.base.constant.Constants; import com.jthink.skyeye.base.util.StringUtil; 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.*; import org.apache.kafka.common.serialization.ByteArraySerializer; import org.apache.kafka.common.serialization.StringSerializer; import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.helpers.LogLog; import org.apache.log4j.spi.Filter; import org.apache.log4j.spi.LoggingEvent; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicBoolean; /** * JThink@JThink * * @author JThink * @version 0.0.1 * @desc KafkaAppender, 包含log4j kafka appender的配置 * @date 2016-09-27 09:30:45 */ public class KafkaAppender extends AppenderSkeleton { // kafka topic private String topic; // 生产日志的host private String host = SysUtil.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>(); // zk注册器 private ZkRegister zkRegister; // kafka producer是否正在初始化 private volatile AtomicBoolean isInitializing = new AtomicBoolean(false); // kafka producer未完成初始化之前的消息存放的队列 private ConcurrentLinkedQueue<String> msgQueue = new ConcurrentLinkedQueue<String>(); // kafka server private String bootstrapServers; // 消息确认模式 private String acks; // linger.ms private String lingerMs; // max.block.ms private String maxBlockMs; // 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()); // 添加hook Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { close(); } }); } /** * 覆写doAppend, 去掉closed的log日志 * @param event */ @Override public synchronized void doAppend(LoggingEvent event) { if (closed) { return; } if (!isAsSevereAsThreshold(event.getLevel())) { return; } Filter f = this.headFilter; FILTER_LOOP: while(f != null) { switch(f.decide(event)) { case Filter.DENY: return; case Filter.ACCEPT: break FILTER_LOOP; case Filter.NEUTRAL: f = f.getNext(); } } this.append(event); } @Override protected void append(LoggingEvent event) { if (closed) { return; } this.sendMessage(this.getMessage(event)); } /** * 向kafka send * @param value */ private void send(String value) { final byte[] key = ByteBuffer.allocate(4).putInt(new StringBuilder(app).append(host).toString().hashCode()).array(); 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: 异常发生如何处理(直接停掉appender) if (null != e) { closed = true; LogLog.error("kafka send error in appender", 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); } } } }); } /** * 发送msg * @param msg */ private void sendMessage(String msg) { if (!LazySingletonProducer.isInstanced()) { if (this.isInitializing.get() != true) { this.isInitializing.compareAndSet(false, true); this.initKafkaConfig(); this.isInitializing.compareAndSet(true, false); this.send(msg); } else { this.msgQueue.add(msg); } } else if (this.msgQueue.size() > 0) { if (LazySingletonProducer.isInstanced() ) { this.msgQueue.add(msg); while (this.msgQueue.size() > 0) { this.send(this.msgQueue.remove()); } } } else { this.send(msg); } } /** * 初始化kafka config */ private void initKafkaConfig() { if (!LazySingletonProducer.isInstanced()) { // app配置 if (StringUtil.isBlank(this.host)) { // host未获取到 LogLog.error("can't get the host"); closed = true; return; } if (StringUtil.isBlank(this.app)) { // app name未设置 LogLog.error("log4j.xml is not set the app"); closed = true; return; } // zk配置 if (StringUtil.isBlank(this.zkServers)) { // zk地址未设置 LogLog.error("can't get zkServers"); closed = true; return; } if (StringUtil.isBlank(this.topic)) { // topic未设置(或者设置成了""),无法写入kafka LogLog.error("topic is not set, appender: " + name); closed = true; return; } if (StringUtil.isBlank(this.mail)) { // 报警mail未设置 LogLog.error("mail is not set, appender: " + name); closed = true; return; } if (StringUtil.isBlank(this.rpc) || !this.checkRpcType(this.rpc)) { // rpc未设置或者rpc值不对 LogLog.error("rpc is not set or value not right, appender: " + name); closed = true; return; } new Thread() { @Override public void run() { // 初始化zk zkRegister = new ZkRegister(new ZkClient(zkServers, 60000, 5000)); // 注册永久节点用于历史日志查询 zkRegister.create(Constants.SLASH + app + Constants.SLASH + host, NodeMode.PERSISTENT); zkRegister.getClient().writeData(Constants.ROOT_PATH_PERSISTENT + Constants.SLASH + app + Constants.SLASH + host, mail + Constants.SEMICOLON + SysUtil.userDir); // 注册临时节点用于日志滚屏 zkRegister.getClient().createPersistent(Constants.ROOT_PATH_EPHEMERAL + Constants.SLASH + app, true); zkRegister.create(Constants.SLASH + app + Constants.SLASH + host, NodeMode.EPHEMERAL, Constants.APPENDER_INIT_DATA + Constants.SEMICOLON + SysUtil.userDir); // rpc trace注册中心 if (rpc.equals(RpcType.dubbo.symbol())) { register(app, host, zkRegister.getClient()); } } }.start(); if (StringUtil.isNotBlank(this.bootstrapServers)) { this.config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, this.bootstrapServers); } if (StringUtil.isNotBlank(this.acks)) { this.config.put(ProducerConfig.ACKS_CONFIG, this.acks); } if (StringUtil.isNotBlank(this.lingerMs)) { this.config.put(ProducerConfig.LINGER_MS_CONFIG, this.lingerMs); } if (StringUtil.isNotBlank(this.maxBlockMs)) { this.config.put(ProducerConfig.MAX_BLOCK_MS_CONFIG, this.maxBlockMs); } if (StringUtil.isNotBlank(this.app) && StringUtil.isNotBlank(this.host)) { this.config.put(ProducerConfig.CLIENT_ID_CONFIG, this.app + Constants.MIDDLE_LINE + this.host + Constants.MIDDLE_LINE + "log4j"); } LazySingletonProducer.getInstance(this.config); } } /** * 进行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)); } /** * 监察rpc type是否正确 * @param rpcType * @return */ private boolean checkRpcType(String rpcType) { try { RpcType.valueOf(rpcType); return true; } catch (Exception e) { return false; } } /** * 获得message * @param event * @return */ private String getMessage(LoggingEvent event) { if (this.layout == null) { return event.getRenderedMessage(); } else { // 获取host和app String msg = System.nanoTime() + Constants.SEMICOLON + this.layout.format(event); return msg.replaceFirst(Constants.APP_NAME, this.app).replaceFirst(Constants.HOSTNAME, this.host); } } @Override public void close() { closed = true; // 关闭KafkaProuder if (LazySingletonProducer.isInstanced()) { // producer实际上已经初始化 LazySingletonProducer.getInstance(this.config).close(); } // 关闭client,临时节点消失,监控系统进行感知报警 ZkClient client = this.zkRegister.getClient(); if (null != client) { client.close(); } } @Override public boolean requiresLayout() { return true; } /** * 进行kafka配置设置 * @param key * @param value */ public void checkAndSetConfig(String key, String value) { if (!KafkaConfig.PRODUCER_CONFIG_KEYS.contains(key)) { // 当前kafka版本没有该配置项 LogLog.warn("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 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 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 getBootstrapServers() { return bootstrapServers; } public void setBootstrapServers(String bootstrapServers) { this.bootstrapServers = bootstrapServers; } public String getAcks() { return acks; } public void setAcks(String acks) { this.acks = acks; } public String getLingerMs() { return lingerMs; } public void setLingerMs(String lingerMs) { this.lingerMs = lingerMs; } public String getMaxBlockMs() { return maxBlockMs; } public void setMaxBlockMs(String maxBlockMs) { this.maxBlockMs = maxBlockMs; } public String getRpc() { return rpc; } public void setRpc(String rpc) { this.rpc = rpc; } }