cloki
Version:
LogQL API with Clickhouse Backend
138 lines (132 loc) • 4.34 kB
JavaScript
/* Zipkin Push Handler
Accepts JSON formatted requests when the header Content-Type: application/json is sent.
Example of the Zipkin span JSON format:
[{
"id": "1234",
"traceId": "0123456789abcdef",
"timestamp": 1608239395286533,
"duration": 100000,
"name": "span from bash!",
"tags": {
"http.method": "GET",
"http.path": "/api"
},
"localEndpoint": {
"serviceName": "shell script"
}
}]
*/
const stringify = require('json-stable-stringify')
function handler (req, res) {
const self = this
req.log.debug('POST /tempo/api/push')
if (!req.body) {
req.log.error('No Request Body!')
res.code(500).send()
return
}
if (this.readonly) {
req.log.error('Readonly! No push support.')
res.code(500).send()
return
}
let streams
if (
req.headers['content-type'] &&
req.headers['content-type'].indexOf('application/json') > -1
) {
streams = req.body
} else if (
req.headers['content-type'] &&
req.headers['content-type'].indexOf('application/x-protobuf') > -1
) {
streams = req.body
req.log.debug({ streams }, 'GOT protoBuf')
} else {
streams = req.body
}
req.log.info({ streams }, 'streams')
if (streams) {
streams.forEach(function (stream) {
req.log.debug({ stream }, 'ingesting tempo stream')
let finger = null
let JSONLabels = {}
try {
try {
JSONLabels.type = 'tempo'
if (this.tempo_tagtrace) JSONLabels.traceId = stream.traceId
if (stream.parentId) JSONLabels.parentId = stream.parentId
if (stream.localEndpoint) {
for (const key in stream.localEndpoint) {
JSONLabels[key] = stream.localEndpoint[key]
}
}
JSONLabels = Object.fromEntries(Object.entries(JSONLabels).sort())
} catch (err) {
req.log.error({ err })
return
}
// Calculate Fingerprint
const strJson = stringify(JSONLabels)
finger = self.fingerPrint(strJson)
req.log.debug({ JSONLabels, finger }, 'LABELS FINGERPRINT')
// Store Fingerprint
self.bulk_labels.add([[
new Date().toISOString().split('T')[0],
finger,
strJson,
JSONLabels.traceId || ''
]])
self.labels.add(finger.toString(), stream.labels)
for (const key in JSONLabels) {
req.log.debug({ key, data: JSONLabels[key] }, 'Storing label')
self.labels.add('_LABELS_', key)
self.labels.add(key, JSONLabels[key])
}
} catch (err) {
req.log.error({ err }, 'failed ingesting tempo stream')
}
// Form Tempo Object - or shall we materialize this from CH?
try {
var tags = Object.keys(stream.tags || {}).map(function (key) {
return {
key: key,
value: typeof stream.tags[key] === 'string'
? stream.tags[key]
: parseInt(stream.tags[key]),
type: typeof stream.tags[key] === 'string' ? 'string' : 'int64'
}
})
var tempo = {
traceID: stream.traceId,
spanID: stream.id,
name: stream.name,
references: stream.references || [],
startTime: stream.timestamp,
startTimeUnixNano: parseInt(stream.timestamp * 1000),
endTimeUnixNano: parseInt(stream.timestamp * 1000) + parseInt(stream.duration * 1000 || 1000),
duration: parseInt(stream.duration) || 1000,
tags: tags || [],
logs: [],
processID: stream.processID || 'p1',
warnings: null
}
if (stream.localEndpoint) tempo.localEndpoint = stream.localEndpoint // already in tags, deduplicate?
if (stream.parentId) tempo.parentSpanID = stream.parentId
req.log.debug(tempo)
tempo = JSON.parse(JSON.stringify(tempo))
} catch (err) { req.log.error({ err }); return };
// Store Tempo Object
const values = [
finger,
BigInt((stream.timestamp || new Date().getTime() * 1000) + '000'),
parseInt(stream.duration) || null,
JSON.stringify(tempo) || ''
]
req.log.debug({ finger, values }, 'store')
self.bulk.add([values])
})
}
res.code(204).send()
}
module.exports = handler