qryn/qryn_node.js

483 lines
17 KiB
JavaScript
Raw Normal View History

2021-08-20 21:31:05 +02:00
#!/usr/bin/env node
2018-12-26 15:06:39 +01:00
/*
* qryn: polyglot observability API
* (C) 2018-2024 QXIP BV
2018-12-26 15:06:39 +01:00
*/
2024-03-19 13:29:28 +02:00
const { boolEnv } = require('./common')
2018-12-26 15:06:39 +01:00
2024-03-19 13:29:28 +02:00
this.readonly = boolEnv('READONLY')
2022-06-12 11:20:32 +00:00
this.http_user = process.env.QRYN_LOGIN || process.env.CLOKI_LOGIN || undefined
this.http_password = process.env.QRYN_PASSWORD || process.env.CLOKI_PASSWORD || undefined
2018-12-26 15:06:39 +01:00
2022-08-23 23:03:39 +02:00
this.maxListeners = process.env.MAXLISTENERS || 0;
2023-03-14 16:23:34 +02:00
2022-08-23 23:03:39 +02:00
process.setMaxListeners(this.maxListeners)
2021-11-08 11:43:59 -06:00
require('./plugins/engine')
2021-10-19 16:18:25 +03:00
2021-11-08 11:43:59 -06:00
const DATABASE = require('./lib/db/clickhouse')
const UTILS = require('./lib/utils')
2018-12-26 15:06:39 +01:00
2022-01-09 21:44:47 +01:00
/* ProtoBuf Helpers */
2021-11-08 11:43:59 -06:00
const fs = require('fs')
const path = require('path')
2022-08-23 23:03:39 +02:00
const logger = require('./lib/logger')
2022-01-09 21:44:47 +01:00
/* Alerting */
const { startAlerting, stop } = require('./lib/db/alerting')
2018-12-27 03:22:06 +01:00
2018-12-26 15:06:39 +01:00
/* Fingerprinting */
2021-11-08 11:43:59 -06:00
this.fingerPrint = UTILS.fingerPrint
this.toJSON = UTILS.toJSON
2018-12-26 16:55:13 +01:00
2021-06-15 00:28:51 +02:00
/* Database this.bulk Helpers */
2021-11-08 11:43:59 -06:00
this.bulk = DATABASE.cache.bulk // samples
this.bulk_labels = DATABASE.cache.bulk_labels // labels
this.labels = DATABASE.cache.labels // in-memory labels
2018-12-26 15:06:39 +01:00
/* Function Helpers */
2021-11-08 11:43:59 -06:00
this.labelParser = UTILS.labelParser
2018-12-28 02:12:45 +01:00
2021-11-08 11:43:59 -06:00
const init = DATABASE.init
this.reloadFingerprints = DATABASE.reloadFingerprints
this.scanFingerprints = DATABASE.scanFingerprints
this.scanTempo = DATABASE.scanTempo
this.instantQueryScan = DATABASE.instantQueryScan
2021-12-30 14:37:38 +00:00
this.tempoQueryScan = DATABASE.tempoQueryScan
2021-11-08 11:43:59 -06:00
this.scanMetricFingerprints = DATABASE.scanMetricFingerprints
2021-12-29 10:23:03 +02:00
this.tempoQueryScan = DATABASE.tempoQueryScan
this.scanClickhouse = DATABASE.scanClickhouse
this.pushZipkin = DATABASE.pushZipkin
2022-10-17 23:14:16 +03:00
this.pushOTLP = DATABASE.pushOTLP
this.queryTempoTags = DATABASE.queryTempoTags
this.queryTempoValues = DATABASE.queryTempoValues
let profiler = null
2022-10-17 23:14:16 +03:00
const {
shaper,
parsers,
lokiPushJSONParser, lokiPushProtoParser, jsonParser, rawStringParser, tempoPushParser, tempoPushNDJSONParser,
yamlParser, prometheusPushProtoParser, combinedParser, otlpPushProtoParser, wwwFormParser
2022-12-10 23:40:25 +02:00
} = require('./parsers')
2023-03-14 16:23:34 +02:00
2023-01-26 14:00:48 +02:00
const fastifyPlugin = require('fastify-plugin')
2023-03-14 16:23:34 +02:00
2022-10-17 23:14:16 +03:00
let fastify = require('fastify')({
logger,
bodyLimit: parseInt(process.env.FASTIFY_BODYLIMIT) || 5242880,
2021-12-09 09:11:20 +02:00
requestTimeout: parseInt(process.env.FASTIFY_REQUESTTIMEOUT) || 0,
2021-11-29 13:57:54 +01:00
maxRequestsPerSocket: parseInt(process.env.FASTIFY_MAXREQUESTS) || 0
2022-12-10 23:40:25 +02:00
});
(async () => {
try {
await init(process.env.CLICKHOUSE_DB || 'cloki')
2024-06-11 18:29:40 +03:00
if (process.env.MODE === 'init_only') {
process.exit(0)
}
} catch (err) {
logger.error(err, 'Error starting qryn')
process.exit(1)
}
try {
2022-12-10 23:40:25 +02:00
if (!this.readonly) {
await startAlerting()
}
2023-03-01 16:40:20 +02:00
await DATABASE.checkDB()
2022-12-10 23:40:25 +02:00
if (!this.readonly && process.env.PROFILE) {
const tag = JSON.stringify({ profiler_id: process.env.PROFILE, label: 'RAM usage' })
const fp = this.fingerPrint(tag)
profiler = setInterval(() => {
this.bulk_labels.add([[new Date().toISOString().split('T')[0], fp, tag, '']])
this.bulk.add([[fp,
[['label', 'RAM usage'], ['profiler_id', process.env.PROFILE]],
BigInt(Date.now()) * BigInt(1000000),
process.memoryUsage().rss / 1024 / 1024, ''
]])
}, 1000)
}
} catch (err) {
logger.error(err, 'Error starting qryn')
process.exit(1)
}
2021-06-26 21:50:56 +03:00
2023-01-26 14:00:48 +02:00
await fastify.register(fastifyPlugin((fastify, opts, done) => {
const snappyPaths = [
'/api/v1/prom/remote/write',
'/api/prom/remote/write',
'/prom/remote/write',
2023-11-08 12:58:09 +02:00
'/loki/api/v1/push',
'/api/v1/write',
'/api/prom/push'
2023-01-26 14:00:48 +02:00
]
fastify.addHook('preParsing', (request, reply, payload, done) => {
2023-11-09 12:44:15 +02:00
if (snappyPaths.indexOf(request.routeOptions.url) !== -1) {
2023-01-26 14:00:48 +02:00
if (request.headers['content-encoding'] === 'snappy') {
delete request.headers['content-encoding']
}
}
done(null, payload)
})
done()
}))
2022-12-10 23:40:25 +02:00
await fastify.register(require('@fastify/compress'))
2023-11-09 12:44:15 +02:00
await fastify.register(require('@fastify/url-data'))
2022-12-10 23:40:25 +02:00
await fastify.register(require('@fastify/websocket'))
2021-06-26 21:50:56 +03:00
2022-12-10 23:40:25 +02:00
/* Fastify local metrics exporter */
2024-03-19 13:29:28 +02:00
if (boolEnv('FASTIFY_METRICS')) {
2022-12-10 23:40:25 +02:00
const metricsPlugin = require('fastify-metrics')
fastify.register(metricsPlugin, { endpoint: '/metrics' })
2023-10-06 14:49:55 +03:00
} else {
fastify.get('/metrics', () => 'not supported')
}
2023-10-06 14:49:55 +03:00
fastify.get('/config', () => 'not supported')
fastify.get('/influx/api/v2/write/health', () => 'ok')
2022-12-10 23:40:25 +02:00
/* CORS Helper */
const CORS = process.env.CORS_ALLOW_ORIGIN || '*'
fastify.register(require('@fastify/cors'), {
origin: CORS
})
fastify.after((err) => {
if (err) {
logger.error({ err }, 'Error creating http response')
throw err
}
})
2018-12-27 01:02:51 +01:00
2022-12-10 23:40:25 +02:00
fastify.__post = fastify.post
fastify.post = (route, handler, _parsers) => {
if (_parsers) {
for (const t of Object.keys(_parsers)) {
parsers.register('post', route, t, _parsers[t])
}
2022-10-17 23:14:16 +03:00
}
2022-12-10 23:40:25 +02:00
return fastify.__post(route, handler)
2022-10-17 23:14:16 +03:00
}
2022-12-10 23:40:25 +02:00
fastify.__put = fastify.put
fastify.put = (route, handler, _parsers) => {
const __parsers = handler.parsers || _parsers
if (__parsers) {
for (const t of Object.keys(__parsers)) {
parsers.register('put', route, t, __parsers[t])
}
2022-10-17 23:14:16 +03:00
}
2022-12-10 23:40:25 +02:00
return fastify.__put(route, handler)
2022-10-17 23:14:16 +03:00
}
2022-12-10 23:40:25 +02:00
fastify.__all = fastify.all
fastify.all = (route, handler, _parsers) => {
const __parsers = handler.parsers || _parsers
if (__parsers) {
for (const t of Object.keys(__parsers)) {
parsers.register('post', route, t, __parsers[t])
parsers.register('put', route, t, __parsers[t])
}
2022-10-17 23:14:16 +03:00
}
2022-12-10 23:40:25 +02:00
return fastify.__all(route, handler)
2022-10-17 23:14:16 +03:00
}
2022-12-10 23:40:25 +02:00
/* Enable Simple Authentication */
if (this.http_user && this.http_password) {
function checkAuth (username, password, req, reply, done) {
if (username === this.http_user && password === this.http_password) {
done()
} else {
2023-10-31 14:21:07 +02:00
done(new (require('http-errors').Unauthorized)('Unauthorized!: Wrong username/password.'))
2022-12-10 23:40:25 +02:00
}
2021-10-31 19:46:46 +01:00
}
2022-12-10 23:40:25 +02:00
const validate = checkAuth.bind(this)
2023-11-10 12:04:35 +02:00
fastify.register(require('@fastify/basic-auth'), {
2022-12-10 23:40:25 +02:00
validate,
authenticate: true
})
fastify.after(() => {
fastify.addHook('preHandler', fastify.basicAuth)
})
2021-11-08 11:43:59 -06:00
}
2022-12-10 23:40:25 +02:00
/* 404 Handler */
const handler404 = require('./lib/handlers/404.js').bind(this)
fastify.setNotFoundHandler(handler404)
fastify.setErrorHandler(require('./lib/handlers/errors').handler.bind(this))
/* Hello qryn test API */
const handlerHello = require('./lib/handlers/ready').bind(this)
fastify.get('/hello', handlerHello)
fastify.get('/ready', handlerHello)
/* Write Handler */
const handlerPush = require('./lib/handlers/push.js').bind(this)
fastify.post('/loki/api/v1/push', handlerPush, {
'application/json': lokiPushJSONParser,
'application/x-protobuf': lokiPushProtoParser,
'*': lokiPushJSONParser
})
/* Elastic Write Handler */
const handlerElasticPush = require('./lib/handlers/elastic_index.js').bind(this)
fastify.post('/:target/_doc', handlerElasticPush, {
'application/json': jsonParser,
'*': rawStringParser
2021-11-08 11:43:59 -06:00
})
2022-12-10 23:40:25 +02:00
fastify.post('/:target/_create/:id', handlerElasticPush, {
'application/json': jsonParser,
'*': rawStringParser
})
fastify.put('/:target/_doc/:id', handlerElasticPush, {
'application/json': jsonParser,
'*': rawStringParser
})
fastify.put('/:target/_create/:id', handlerElasticPush, {
'application/json': jsonParser,
'*': rawStringParser
})
const handlerElasticBulk = require('./lib/handlers/elastic_bulk.js').bind(this)
fastify.post('/_bulk', handlerElasticBulk, {
2022-12-27 13:03:24 -06:00
'application/json': jsonParser,
2022-12-10 23:40:25 +02:00
'*': rawStringParser
})
fastify.post('/:target/_bulk', handlerElasticBulk, {
2022-12-27 13:03:24 -06:00
'application/json': jsonParser,
2022-12-10 23:40:25 +02:00
'*': rawStringParser
2021-11-08 11:43:59 -06:00
})
2022-12-10 23:40:25 +02:00
/* Tempo Write Handler */
2024-03-19 13:29:28 +02:00
this.tempo_tagtrace = boolEnv('TEMPO_TAGTRACE')
2022-12-10 23:40:25 +02:00
const handlerTempoPush = require('./lib/handlers/tempo_push.js').bind(this)
fastify.post('/tempo/api/push', handlerTempoPush, {
'application/json': tempoPushParser,
'application/x-ndjson': tempoPushNDJSONParser,
'*': tempoPushParser
})
2022-12-10 23:40:25 +02:00
fastify.post('/tempo/spans', handlerTempoPush, {
'application/json': tempoPushParser,
'application/x-ndjson': tempoPushNDJSONParser,
'*': tempoPushParser
})
fastify.post('/api/v2/spans', handlerTempoPush, {
'application/json': tempoPushParser,
'application/x-ndjson': tempoPushNDJSONParser,
'*': tempoPushParser
})
/* Tempo Traces Query Handler */
this.tempo_span = process.env.TEMPO_SPAN || 24
const handlerTempoTraces = require('./lib/handlers/tempo_traces.js').bind(this)
fastify.get('/api/traces/:traceId', handlerTempoTraces)
fastify.get('/api/traces/:traceId/:json', handlerTempoTraces)
2023-01-27 17:51:24 +02:00
fastify.get('/tempo/api/traces/:traceId', handlerTempoTraces)
fastify.get('/tempo/api/traces/:traceId/:json', handlerTempoTraces)
2022-12-10 23:40:25 +02:00
/* Tempo Tag Handlers */
const handlerTempoLabel = require('./lib/handlers/tempo_tags').bind(this)
fastify.get('/api/search/tags', handlerTempoLabel)
2023-01-27 17:51:24 +02:00
fastify.get('/tempo/api/search/tags', handlerTempoLabel)
2022-12-10 23:40:25 +02:00
2023-10-26 17:47:41 +03:00
const handlerTempoLabelV2 = require('./lib/handlers/tempo_v2_tags').bind(this)
fastify.get('/api/v2/search/tags', handlerTempoLabelV2)
fastify.get('/tempo/api/v2/search/tags', handlerTempoLabelV2)
2022-12-10 23:40:25 +02:00
/* Tempo Tag Value Handler */
const handlerTempoLabelValues = require('./lib/handlers/tempo_values').bind(this)
fastify.get('/api/search/tag/:name/values', handlerTempoLabelValues)
2023-01-27 17:51:24 +02:00
fastify.get('/tempo/api/search/tag/:name/values', handlerTempoLabelValues)
2022-12-10 23:40:25 +02:00
2023-10-26 17:47:41 +03:00
const handlerTempoLabelV2Values = require('./lib/handlers/tempo_v2_values').bind(this)
fastify.get('/api/v2/search/tag/:name/values', handlerTempoLabelV2Values)
fastify.get('/tempo/api/v2/search/tag/:name/values', handlerTempoLabelV2Values)
2022-12-10 23:40:25 +02:00
/* Tempo Traces Query Handler */
const handlerTempoSearch = require('./lib/handlers/tempo_search.js').bind(this)
fastify.get('/api/search', handlerTempoSearch)
2023-01-27 17:51:24 +02:00
fastify.get('/tempo/api/search', handlerTempoSearch)
2022-12-10 23:40:25 +02:00
/* Tempo Echo Handler */
const handlerTempoEcho = require('./lib/handlers/echo.js').bind(this)
fastify.get('/api/echo', handlerTempoEcho)
2023-01-27 17:51:24 +02:00
fastify.get('/tempo/api/echo', handlerTempoEcho)
2022-12-10 23:40:25 +02:00
/* Telegraf HTTP Bulk handler */
const handlerTelegraf = require('./lib/handlers/telegraf.js').bind(this)
fastify.post('/telegraf', handlerTelegraf, {
'*': jsonParser
})
/* Datadog Log Push Handler */
const handlerDatadogLogPush = require('./lib/handlers/datadog_log_push.js').bind(this)
fastify.post('/api/v2/logs', handlerDatadogLogPush, {
'application/json': jsonParser,
'*': rawStringParser
})
/* Datadog Series Push Handler */
const handlerDatadogSeriesPush = require('./lib/handlers/datadog_series_push.js').bind(this)
fastify.post('/api/v2/series', handlerDatadogSeriesPush, {
'application/json': jsonParser,
'*': rawStringParser
})
2022-12-10 23:40:25 +02:00
/* Query Handler */
const handlerQueryRange = require('./lib/handlers/query_range.js').bind(this)
fastify.get('/loki/api/v1/query_range', handlerQueryRange)
/* Label Handlers */
/* Label Value Handler via query (test) */
const handlerQuery = require('./lib/handlers/query.js').bind(this)
fastify.get('/loki/api/v1/query', handlerQuery)
/* Label Handlers */
const handlerLabel = require('./lib/handlers/label.js').bind(this)
fastify.get('/loki/api/v1/label', handlerLabel)
fastify.get('/loki/api/v1/labels', handlerLabel)
/* Label Value Handler */
const handlerLabelValues = require('./lib/handlers/label_values.js').bind(this)
fastify.get('/loki/api/v1/label/:name/values', handlerLabelValues)
/* Series Handler - experimental support for both Loki and Prometheus */
const handlerSeries = require('./lib/handlers/series.js').bind(this)
fastify.get('/loki/api/v1/series', handlerSeries)
const handlerPromSeries = require('./lib/handlers/prom_series.js').bind(this)
fastify.get('/api/v1/series', handlerPromSeries)
2023-01-03 15:59:35 -06:00
fastify.post('/api/v1/series', handlerPromSeries, {
'application/x-www-form-urlencoded': wwwFormParser
})
2022-12-10 23:40:25 +02:00
fastify.register(async (fastify) => {
fastify.get('/loki/api/v1/tail', { websocket: true }, require('./lib/handlers/tail').bind(this))
})
2022-12-10 23:40:25 +02:00
/* ALERT MANAGER Handlers */
fastify.get('/api/prom/rules', require('./lib/handlers/alerts/get_rules').bind(this))
fastify.get('/api/prom/rules/:ns/:group', require('./lib/handlers/alerts/get_group').bind(this))
fastify.post('/api/prom/rules/:ns', require('./lib/handlers/alerts/post_group').bind(this), {
'*': yamlParser
})
fastify.delete('/api/prom/rules/:ns/:group', require('./lib/handlers/alerts/del_group').bind(this))
fastify.delete('/api/prom/rules/:ns', require('./lib/handlers/alerts/del_ns').bind(this))
fastify.get('/prometheus/api/v1/rules', require('./lib/handlers/alerts/prom_get_rules').bind(this))
/* PROMETHEUS REMOTE WRITE Handlers */
const promWriteHandler = require('./lib/handlers/prom_push.js').bind(this)
2023-11-08 12:58:09 +02:00
const remoteWritePaths = [
'/api/v1/prom/remote/write',
'/api/prom/remote/write',
'/prom/remote/write',
'/api/v1/write'
]
for (const path of remoteWritePaths) {
fastify.post(path, promWriteHandler, {
'application/x-protobuf': prometheusPushProtoParser,
'application/json': jsonParser,
'*': combinedParser(prometheusPushProtoParser, jsonParser)
})
fastify.get(path, handlerTempoEcho)
}
2022-12-10 23:40:25 +02:00
/* PROMQETHEUS API EMULATION */
const handlerPromQueryRange = require('./lib/handlers/prom_query_range.js').bind(this)
fastify.post('/api/v1/query_range', handlerPromQueryRange, {
'application/x-www-form-urlencoded': wwwFormParser
})
fastify.get('/api/v1/query_range', handlerPromQueryRange)
const handlerPromQuery = require('./lib/handlers/prom_query.js').bind(this)
fastify.post('/api/v1/query', handlerPromQuery, {
'application/x-www-form-urlencoded': wwwFormParser
})
fastify.get('/api/v1/query', handlerPromQuery)
const handlerPromLabel = require('./lib/handlers/promlabel.js').bind(this)
const handlerPromLabelValues = require('./lib/handlers/promlabel_values.js').bind(this)
fastify.get('/api/v1/labels', handlerPromLabel) // piggyback on qryn labels
fastify.get('/api/v1/label/:name/values', handlerPromLabelValues) // piggyback on qryn values
fastify.post('/api/v1/labels', handlerPromLabel, {
'*': rawStringParser
}) // piggyback on qryn labels
fastify.post('/api/v1/label/:name/values', handlerPromLabelValues, {
'*': rawStringParser
}) // piggyback on qryn values
const handlerPromDefault = require('./lib/handlers/prom_default.js')
fastify.get('/api/v1/metadata', handlerPromDefault.misc.bind(this)) // default handler TBD
fastify.get('/api/v1/rules', handlerPromDefault.rules.bind(this)) // default handler TBD
fastify.get('/api/v1/query_exemplars', handlerPromDefault.misc.bind(this)) // default handler TBD
2023-01-03 15:59:35 -06:00
fastify.post('/api/v1/query_exemplars', handlerPromDefault.misc.bind(this), {
'application/x-www-form-urlencoded': wwwFormParser
}) // default handler TBD
fastify.get('/api/v1/format_query', handlerPromDefault.misc.bind(this)) // default handler TBD
fastify.post('/api/v1/format_query', handlerPromDefault.misc.bind(this), {
'application/x-www-form-urlencoded': wwwFormParser
}) // default handler TBD
2022-12-10 23:40:25 +02:00
fastify.get('/api/v1/status/buildinfo', handlerPromDefault.buildinfo.bind(this)) // default handler TBD
/* NewRelic Log Handler */
const handlerNewrelicLogPush = require('./lib/handlers/newrelic_log_push.js').bind(this)
fastify.post('/log/v1', handlerNewrelicLogPush, {
'text/plain': jsonParser,
'*': jsonParser
})
/* INFLUX WRITE Handlers */
const handlerInfluxWrite = require('./lib/handlers/influx_write.js').bind(this)
fastify.post('/write', handlerInfluxWrite, {
'*': rawStringParser
})
fastify.post('/influx/api/v2/write', handlerInfluxWrite, {
'*': rawStringParser
})
/* INFLUX HEALTH Handlers */
const handlerInfluxHealth = require('./lib/handlers/influx_health.js').bind(this)
fastify.get('/health', handlerInfluxHealth)
fastify.get('/influx/health', handlerInfluxHealth)
const handlerOTLPPush = require('./lib/handlers/otlp_push').bind(this)
fastify.post('/v1/traces', handlerOTLPPush, {
'*': otlpPushProtoParser
})
fastify = parsers.init(fastify)
/* QRYN-VIEW Optional Handler */
if (fs.existsSync(path.join(__dirname, 'view/index.html'))) {
fastify.register(require('@fastify/static'), {
root: path.join(__dirname, 'view'),
prefix: '/'
})
const idx = fs.readFileSync(path.join(__dirname, 'view/index.html'), 'utf8')
2023-12-01 18:16:22 +02:00
for (const fakePath of ['/plugins', '/users', '/datasources', '/datasources/:ds']) {
fastify.get(fakePath,
(req, reply) =>
reply.code(200).header('Content-Type', 'text/html').send(idx))
}
2021-11-08 11:43:59 -06:00
}
2022-12-10 23:40:25 +02:00
2024-01-24 12:20:00 +02:00
require('./pyroscope/pyroscope').init(fastify)
2022-12-10 23:40:25 +02:00
// Run API Service
fastify.listen(
{
port: process.env.PORT || 3100,
host: process.env.HOST || '0.0.0.0'
},
(err, address) => {
if (err) throw err
logger.info('Qryn API up')
fastify.log.info(`Qryn API listening on ${address}`)
}
)
})()
2021-06-26 21:50:56 +03:00
module.exports.stop = () => {
shaper.stop()
profiler && clearInterval(profiler)
2021-11-08 11:43:59 -06:00
fastify.close()
DATABASE.stop()
2021-12-20 12:05:44 +02:00
require('./parser/transpiler').stop()
2021-12-13 11:31:49 +02:00
stop()
2021-11-08 11:43:59 -06:00
}