UNPKG

dd-trace

Version:

Datadog APM tracing client for JavaScript

242 lines (209 loc) 7.02 kB
'use strict' const { channel, addHook, AsyncResource } = require('./helpers/instrument') const shimmer = require('../../datadog-shimmer') const producerStartCh = channel('apm:kafkajs:produce:start') const producerCommitCh = channel('apm:kafkajs:produce:commit') const producerFinishCh = channel('apm:kafkajs:produce:finish') const producerErrorCh = channel('apm:kafkajs:produce:error') const consumerStartCh = channel('apm:kafkajs:consume:start') const consumerCommitCh = channel('apm:kafkajs:consume:commit') const consumerFinishCh = channel('apm:kafkajs:consume:finish') const consumerErrorCh = channel('apm:kafkajs:consume:error') const batchConsumerStartCh = channel('apm:kafkajs:consume-batch:start') const batchConsumerFinishCh = channel('apm:kafkajs:consume-batch:finish') const batchConsumerErrorCh = channel('apm:kafkajs:consume-batch:error') function commitsFromEvent (event) { const { payload: { groupId, topics } } = event const commitList = [] for (const { topic, partitions } of topics) { for (const { partition, offset } of partitions) { commitList.push({ groupId, partition, offset, topic }) } } consumerCommitCh.publish(commitList) } addHook({ name: 'kafkajs', file: 'src/index.js', versions: ['>=1.4'] }, (BaseKafka) => { class Kafka extends BaseKafka { constructor (options) { super(options) this._brokers = (options.brokers && typeof options.brokers !== 'function') ? options.brokers.join(',') : undefined } } shimmer.wrap(Kafka.prototype, 'producer', createProducer => function () { const producer = createProducer.apply(this, arguments) const send = producer.send const bootstrapServers = this._brokers const kafkaClusterIdPromise = getKafkaClusterId(this) producer.send = function () { const wrappedSend = (clusterId) => { const innerAsyncResource = new AsyncResource('bound-anonymous-fn') return innerAsyncResource.runInAsyncScope(() => { if (!producerStartCh.hasSubscribers) { return send.apply(this, arguments) } try { const { topic, messages = [] } = arguments[0] for (const message of messages) { if (message !== null && typeof message === 'object') { message.headers = message.headers || {} } } producerStartCh.publish({ topic, messages, bootstrapServers, clusterId }) const result = send.apply(this, arguments) result.then( innerAsyncResource.bind(res => { producerFinishCh.publish(undefined) producerCommitCh.publish(res) }), innerAsyncResource.bind(err => { if (err) { producerErrorCh.publish(err) } producerFinishCh.publish(undefined) }) ) return result } catch (e) { producerErrorCh.publish(e) producerFinishCh.publish(undefined) throw e } }) } if (!isPromise(kafkaClusterIdPromise)) { // promise is already resolved return wrappedSend(kafkaClusterIdPromise) } else { // promise is not resolved return kafkaClusterIdPromise.then((clusterId) => { return wrappedSend(clusterId) }) } } return producer }) shimmer.wrap(Kafka.prototype, 'consumer', createConsumer => function () { if (!consumerStartCh.hasSubscribers) { return createConsumer.apply(this, arguments) } const kafkaClusterIdPromise = getKafkaClusterId(this) const eachMessageExtractor = (args, clusterId) => { const { topic, partition, message } = args[0] return { topic, partition, message, groupId, clusterId } } const eachBatchExtractor = (args, clusterId) => { const { batch } = args[0] const { topic, partition, messages } = batch return { topic, partition, messages, groupId, clusterId } } const consumer = createConsumer.apply(this, arguments) consumer.on(consumer.events.COMMIT_OFFSETS, commitsFromEvent) const run = consumer.run const groupId = arguments[0].groupId consumer.run = function ({ eachMessage, eachBatch, ...runArgs }) { const wrapConsume = (clusterId) => { return run({ eachMessage: wrappedCallback( eachMessage, consumerStartCh, consumerFinishCh, consumerErrorCh, eachMessageExtractor, clusterId ), eachBatch: wrappedCallback( eachBatch, batchConsumerStartCh, batchConsumerFinishCh, batchConsumerErrorCh, eachBatchExtractor, clusterId ), ...runArgs }) } if (!isPromise(kafkaClusterIdPromise)) { // promise is already resolved return wrapConsume(kafkaClusterIdPromise) } else { // promise is not resolved return kafkaClusterIdPromise.then((clusterId) => { return wrapConsume(clusterId) }) } } return consumer }) return Kafka }) const wrappedCallback = (fn, startCh, finishCh, errorCh, extractArgs, clusterId) => { return typeof fn === 'function' ? function (...args) { const innerAsyncResource = new AsyncResource('bound-anonymous-fn') return innerAsyncResource.runInAsyncScope(() => { const extractedArgs = extractArgs(args, clusterId) startCh.publish(extractedArgs) try { const result = fn.apply(this, args) if (result && typeof result.then === 'function') { result.then( innerAsyncResource.bind(() => finishCh.publish(undefined)), innerAsyncResource.bind(err => { if (err) { errorCh.publish(err) } finishCh.publish(undefined) }) ) } else { finishCh.publish(undefined) } return result } catch (e) { errorCh.publish(e) finishCh.publish(undefined) throw e } }) } : fn } const getKafkaClusterId = (kafka) => { if (kafka._ddKafkaClusterId) { return kafka._ddKafkaClusterId } if (!kafka.admin) { return null } const admin = kafka.admin() if (!admin.describeCluster) { return null } return admin.connect() .then(() => { return admin.describeCluster() }) .then((clusterInfo) => { const clusterId = clusterInfo?.clusterId kafka._ddKafkaClusterId = clusterId admin.disconnect() return clusterId }) .catch((error) => { throw error }) } function isPromise (obj) { return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function' }