準備工作
npm i kafka-node
- 拷貝 Logger.js 進 node 項目
查看kafka-node文檔
文檔地址https://www.npmjs.com/package/kafka-node
需求日志接入kafka存到elk上,各種要求都不高,找一段最基礎的發送message
var kafka = require('kafka-node'),
Producer = kafka.Producer,
KeyedMessage = kafka.KeyedMessage,
client = new kafka.KafkaClient(),
producer = new Producer(client),
km = new KeyedMessage('key', 'message'),
payloads = [
{ topic: 'topic1', messages: 'hi', partition: 0 },
{ topic: 'topic2', messages: ['hello', 'world', km] }
];
producer.on('ready', function () {
producer.send(payloads, function (err, data) {
console.log(data);
});
});
producer.on('error', function (err) {})
來觀察一下這個乍看平平無奇的例子,我需要的調用方式為log.info('XXX')
,
而這里使用PubSub模式實現了分布式事件,并不是像axios那樣可以直接用promise去處理異步邏輯。
producer.on('ready', function () {
producer.send(payloads, function (err, data) {
console.log(data);
});
});
為了讓調用方有精神上的愉悅感(并不想:)),這塊需要處理一下
-
PubSub模式
題外話,根據《JavaScript異步編程》,PubSub簡單實現類似于
PubSub = {handlers: {}}
PubSub.on = function(eventType, handler) {
if (!(eventType in this.handlers)) {this.handlers[eventType] = []; }
this.handlers[eventType].push(handler);
return this;
}
// 接著,等到觸發事件的時候,再循環遍歷所有的事件處理器。
PubSub.emit = function(eventType) {
var handlerArgs = Array.prototype.slice.call(arguments, 1);
for (var i = 0; i < this.handlers[eventType].length; i++) {
this.handlers[eventType][i].apply(this, handlerArgs);
}
return this;
}
Logger代碼如下
// Logger.js
var kafka = require('kafka-node'),
Producer = kafka.Producer;
const EVENT = ['DEBUG', 'INFO', 'WARN', 'ERROR'];
function Logger(options) {
let client = new kafka.KafkaClient({
kafkaHost: 'logkafkafat05.test.com:9092',
});
this.producer = new Producer(client);
this.ready = readyFunc(this.producer);
this.message = createMessage(options);
}
// 發日志
Logger.prototype.sendMessage = function ({ level, layoutMessage }) {
let wholeMes = Object.assign(this.message, {
level,
layoutMessage,
timeStamp: new Date().getTime(),
});
let payloads = [
{
topic: 'test.log',
messages: JSON.stringify(wholeMes),
},
];
return this.ready.then(() => {
return new Promise((resolve, reject) => {
this.producer.send(payloads, function (err, data) {
// console.log(data);
resolve(data);
});
});
});
};
// 支持debug、warn、info、error等方法
EVENT.map((method) => {
Logger.prototype[method.toLowerCase()] = function (mes) {
return this.sendMessage({ level: method, layoutMessage: mes });
};
});
function readyFunc(producer) {
return new Promise((resolve, reject) => {
return producer.on('ready', resolve);
});
}
function createMessage({ appId, logName }) {
return {
appId,
logName,
level: '',
layoutMessage: '',
tags: {
HOST_IP: getIPAdress(),
catTrace: '',
},
};
}
// 獲取本機ip
function getIPAdress() {
var interfaces = require('os').networkInterfaces();
for (var devName in interfaces) {
var iface = interfaces[devName];
for (var i = 0; i < iface.length; i++) {
var alias = iface[i];
if (
alias.family === 'IPv4' &&
alias.address !== '127.0.0.1' &&
!alias.internal
) {
return alias.address;
}
}
}
}
module.exports = Logger;
使用
- 支持兩類調用方式
var Logger = require('./Logger.js');
var log = new Logger({ appId: 'XXXXX', logName: 'htmlquicker' });
// 調用方式1類
log.warn('warn test');
log.info('info test');
log.debug('debug test');
log.error('error test');
// 調用方式2類
log.sendMessage({ level: 'INFO', layoutMessage: 'test' });
log.sendMessage({ level: 'WARN', layoutMessage: 'test' });
log.sendMessage({ level: 'DEBUG', layoutMessage: 'test' });
log.sendMessage({ level: 'ERROR', layoutMessage: 'test' });
- koa 示例
let resContent = { Content: { downloadPath: `/download/${encodeURIComponent( fileName )}.html`, fileName: fileName, }, }; ctx.body = response.success(resContent); log.info( JSON.stringify({ href: ctx.href, ...resContent, }) );