2021-08-20 21:31:05 +02:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
2018-12-26 15:06:39 +01:00
|
|
|
/*
|
|
|
|
* Loki API to Clickhouse Gateway
|
2021-01-22 23:23:39 +01:00
|
|
|
* (C) 2018-2021 QXIP BV
|
2018-12-26 15:06:39 +01:00
|
|
|
*/
|
|
|
|
|
2021-11-08 11:43:59 -06:00
|
|
|
this.debug = process.env.DEBUG || false
|
2021-11-09 12:10:20 -06:00
|
|
|
// const debug = this.debug
|
2018-12-27 13:26:15 +01:00
|
|
|
|
2021-11-08 11:43:59 -06:00
|
|
|
this.readonly = process.env.READONLY || false
|
|
|
|
this.http_user = process.env.CLOKI_LOGIN || false
|
|
|
|
this.http_password = process.env.CLOKI_PASSWORD || false
|
2018-12-26 15:06:39 +01:00
|
|
|
|
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
|
|
|
|
2018-12-27 03:22:06 +01:00
|
|
|
/* ProtoBuf Helper */
|
2021-11-08 11:43:59 -06:00
|
|
|
const fs = require('fs')
|
|
|
|
const protoBuff = require('protocol-buffers')
|
2021-11-25 20:46:06 +02:00
|
|
|
const { startAlerting } = require('./lib/db/alerting')
|
2021-11-08 11:43:59 -06:00
|
|
|
const messages = protoBuff(fs.readFileSync('lib/loki.proto'))
|
2021-12-11 23:12:37 +02:00
|
|
|
const yaml = require('yaml')
|
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
|
2021-11-22 17:11:49 +02:00
|
|
|
this.instantQueryScan = DATABASE.instantQueryScan
|
2021-11-08 11:43:59 -06:00
|
|
|
this.scanMetricFingerprints = DATABASE.scanMetricFingerprints
|
2021-11-30 13:09:49 +02:00
|
|
|
this.scanClickhouse = DATABASE.scanClickhouse;
|
|
|
|
(async () => {
|
|
|
|
if (!this.readonly) await init(process.env.CLICKHOUSE_DB || 'cloki')
|
|
|
|
await startAlerting()
|
|
|
|
})().catch((err) => {
|
2021-11-25 20:46:06 +02:00
|
|
|
console.log(err)
|
|
|
|
process.exit(1)
|
|
|
|
})
|
2018-12-26 15:06:39 +01:00
|
|
|
|
|
|
|
/* Fastify Helper */
|
2021-11-08 11:43:59 -06:00
|
|
|
const fastify = require('fastify')({
|
2021-12-09 16:24:12 +02:00
|
|
|
|
2021-12-13 10:17:12 +02:00
|
|
|
logger: false,
|
2021-11-29 13:57:54 +01:00
|
|
|
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
|
2021-11-08 11:43:59 -06:00
|
|
|
})
|
2018-12-26 15:06:39 +01:00
|
|
|
|
2021-11-08 11:43:59 -06:00
|
|
|
fastify.register(require('fastify-url-data'))
|
|
|
|
fastify.register(require('fastify-websocket'))
|
2021-06-26 21:50:56 +03:00
|
|
|
|
|
|
|
fastify.after((err) => {
|
2021-11-08 11:43:59 -06:00
|
|
|
if (err) throw err
|
|
|
|
})
|
2018-12-27 01:02:51 +01:00
|
|
|
|
2019-05-30 22:08:07 +02:00
|
|
|
/* Enable Simple Authentication */
|
2021-10-31 19:46:46 +01:00
|
|
|
if (this.http_user && this.http_password) {
|
2021-11-08 11:43:59 -06:00
|
|
|
function checkAuth (username, password, req, reply, done) {
|
|
|
|
if (username === this.http_user && password === this.http_password) {
|
|
|
|
done()
|
|
|
|
} else {
|
|
|
|
done(new Error('Unauthorized!: Wrong username/password.'))
|
2021-10-31 19:46:46 +01:00
|
|
|
}
|
2021-11-08 11:43:59 -06:00
|
|
|
}
|
|
|
|
const validate = checkAuth.bind(this)
|
|
|
|
|
|
|
|
fastify.register(require('fastify-basic-auth'), {
|
|
|
|
validate
|
|
|
|
})
|
|
|
|
fastify.after(() => {
|
|
|
|
fastify.addHook('preHandler', fastify.basicAuth)
|
|
|
|
})
|
2019-05-30 22:08:07 +02:00
|
|
|
}
|
2019-05-30 23:03:12 +03:00
|
|
|
|
2021-11-08 11:43:59 -06:00
|
|
|
fastify.addContentTypeParser('text/plain', {
|
|
|
|
parseAs: 'string'
|
|
|
|
}, function (req, body, done) {
|
|
|
|
try {
|
|
|
|
const json = JSON.parse(body)
|
|
|
|
done(null, json)
|
|
|
|
} catch (err) {
|
|
|
|
err.statusCode = 400
|
|
|
|
done(err, undefined)
|
|
|
|
}
|
|
|
|
})
|
2018-12-26 15:06:39 +01:00
|
|
|
|
2021-12-11 23:12:37 +02:00
|
|
|
fastify.addContentTypeParser('application/yaml', {
|
|
|
|
parseAs: 'string'
|
|
|
|
}, function (req, body, done) {
|
|
|
|
try {
|
|
|
|
const json = yaml.parse(body)
|
|
|
|
done(null, json)
|
|
|
|
} catch (err) {
|
|
|
|
err.statusCode = 400
|
|
|
|
done(err, undefined)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-11-19 10:55:25 +02:00
|
|
|
try {
|
2021-11-20 14:55:10 -06:00
|
|
|
const snappy = require('snappyjs')
|
2021-11-18 16:19:41 +02:00
|
|
|
/* Protobuf Handler */
|
|
|
|
fastify.addContentTypeParser('application/x-protobuf', { parseAs: 'buffer' },
|
|
|
|
async function (req, body, done) {
|
|
|
|
let _data = await snappy.uncompress(body)
|
|
|
|
_data = messages.PushRequest.decode(_data)
|
|
|
|
_data.streams = _data.streams.map(s => ({
|
|
|
|
...s,
|
|
|
|
entries: s.entries.map(e => {
|
|
|
|
const millis = Math.floor(e.timestamp.nanos / 1000000)
|
|
|
|
return {
|
|
|
|
...e,
|
|
|
|
timestamp: e.timestamp.seconds * 1000 + millis
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}))
|
|
|
|
return _data.streams
|
|
|
|
})
|
2021-11-19 10:55:25 +02:00
|
|
|
} catch (e) {
|
|
|
|
console.log(e)
|
|
|
|
console.log('Protobuf ingesting is unsupported')
|
2021-11-18 16:19:41 +02:00
|
|
|
}
|
2018-12-29 02:30:14 +01:00
|
|
|
|
2021-11-12 18:27:24 +02:00
|
|
|
fastify.addContentTypeParser('*', function (request, payload, done) {
|
|
|
|
if (request.headers['content-type']) {
|
|
|
|
done(payload)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let data = ''
|
|
|
|
payload.on('data', chunk => { data += chunk })
|
|
|
|
payload.on('end', () => {
|
|
|
|
done(null, data)
|
|
|
|
})
|
|
|
|
})
|
2021-11-17 12:42:30 +02:00
|
|
|
fastify.addSchema({
|
|
|
|
$id: 'http://cloki/alertRule.json',
|
|
|
|
type: 'object',
|
|
|
|
properties: {
|
|
|
|
name: {
|
|
|
|
type: 'string'
|
|
|
|
},
|
|
|
|
request: {
|
|
|
|
type: 'string'
|
|
|
|
},
|
|
|
|
labels: {
|
|
|
|
type: 'object',
|
|
|
|
additionalProperties: {
|
|
|
|
type: 'string'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
required: ['name', 'request']
|
|
|
|
})
|
2021-06-15 00:28:51 +02:00
|
|
|
/* 404 Handler */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handler404 = require('./lib/handlers/404.js').bind(this)
|
|
|
|
fastify.setNotFoundHandler(handler404)
|
2021-11-17 12:42:30 +02:00
|
|
|
fastify.setErrorHandler(require('./lib/handlers/errors').handler.bind(this))
|
2018-12-29 02:30:14 +01:00
|
|
|
|
2021-06-15 00:28:51 +02:00
|
|
|
/* Hello cloki test API */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerHello = require('./lib/handlers/ready').bind(this)
|
|
|
|
fastify.get('/hello', handlerHello)
|
|
|
|
fastify.get('/ready', handlerHello)
|
2021-02-04 21:42:15 +01:00
|
|
|
|
2021-06-15 00:28:51 +02:00
|
|
|
/* Write Handler */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerPush = require('./lib/handlers/push.js').bind(this)
|
|
|
|
fastify.post('/loki/api/v1/push', handlerPush)
|
2020-11-23 15:26:14 +01:00
|
|
|
|
|
|
|
/* Telegraf HTTP Bulk handler */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerTelegraf = require('./lib/handlers/telegraf.js').bind(this)
|
|
|
|
fastify.post('/telegraf', handlerTelegraf)
|
2018-12-26 15:06:39 +01:00
|
|
|
|
|
|
|
/* Query Handler */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerQueryRange = require('./lib/handlers/query_range.js').bind(this)
|
|
|
|
fastify.get('/loki/api/v1/query_range', handlerQueryRange)
|
2018-12-26 15:06:39 +01:00
|
|
|
|
|
|
|
/* Label Handlers */
|
2020-10-13 19:26:46 +02:00
|
|
|
/* Label Value Handler via query (test) */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerQuery = require('./lib/handlers/query.js').bind(this)
|
|
|
|
fastify.get('/loki/api/v1/query', handlerQuery)
|
2020-10-13 19:26:46 +02:00
|
|
|
|
2021-06-15 00:28:51 +02:00
|
|
|
/* Label Handlers */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerLabel = require('./lib/handlers/label.js').bind(this)
|
|
|
|
fastify.get('/loki/api/v1/label', handlerLabel)
|
|
|
|
fastify.get('/loki/api/v1/labels', handlerLabel)
|
2018-12-26 15:06:39 +01:00
|
|
|
|
|
|
|
/* Label Value Handler */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerLabelValues = require('./lib/handlers/label_values.js').bind(this)
|
|
|
|
fastify.get('/loki/api/v1/label/:name/values', handlerLabelValues)
|
2018-12-26 15:06:39 +01:00
|
|
|
|
2020-10-13 19:26:46 +02:00
|
|
|
/* Series Placeholder - we do not track this as of yet */
|
2021-11-09 12:10:20 -06:00
|
|
|
const handlerSeries = require('./lib/handlers/series.js').bind(this)
|
|
|
|
fastify.get('/loki/api/v1/series', handlerSeries)
|
2020-10-13 19:26:46 +02:00
|
|
|
|
2021-11-08 11:43:59 -06:00
|
|
|
fastify.get('/loki/api/v1/tail', { websocket: true }, require('./lib/handlers/tail').bind(this))
|
2021-11-01 11:36:48 +02:00
|
|
|
|
2021-12-09 16:24:12 +02:00
|
|
|
fastify.get('/api/prom/rules', require('./lib/handlers/alerts/get_rules').bind(this))
|
2021-12-11 23:12:37 +02:00
|
|
|
fastify.get('/api/prom/rules/:ns/:group', require('./lib/handlers/alerts/get_group').bind(this))
|
|
|
|
fastify.post('/api/prom/rules/:ns', {
|
|
|
|
handler: require('./lib/handlers/alerts/post_group').bind(this)/* ,
|
2021-11-17 12:42:30 +02:00
|
|
|
schema: {
|
|
|
|
body: {
|
|
|
|
$ref: 'http://cloki/alertRule.json#'
|
|
|
|
}
|
2021-12-11 23:12:37 +02:00
|
|
|
} */
|
2021-11-17 12:42:30 +02:00
|
|
|
})
|
2021-12-11 23:12:37 +02:00
|
|
|
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))
|
2021-12-13 10:17:12 +02:00
|
|
|
fastify.get('/prometheus/api/v1/rules', require('./lib/handlers/alerts/prom_get_rules').bind(this))
|
2021-11-17 12:42:30 +02:00
|
|
|
|
2021-11-22 10:44:52 +02:00
|
|
|
fastify.register(require('fastify-serve-swagger-ui'), {
|
|
|
|
// swagger specification which should be exposed
|
|
|
|
specification: {
|
|
|
|
type: 'file',
|
|
|
|
path: 'cLoki_config_api.yaml'
|
|
|
|
},
|
|
|
|
// path under which swagger-ui will be available
|
|
|
|
path: 'swagger'
|
|
|
|
})
|
|
|
|
|
2018-12-27 02:26:49 +01:00
|
|
|
// Run API Service
|
2021-02-05 13:47:49 -05:00
|
|
|
fastify.listen(
|
2021-11-08 11:43:59 -06:00
|
|
|
process.env.PORT || 3100,
|
|
|
|
process.env.HOST || '0.0.0.0',
|
|
|
|
(err, address) => {
|
|
|
|
if (err) throw err
|
|
|
|
console.log('cLoki API up')
|
|
|
|
fastify.log.info(`cloki API listening on ${address}`)
|
|
|
|
}
|
|
|
|
)
|
2021-06-26 21:50:56 +03:00
|
|
|
|
|
|
|
module.exports.stop = () => {
|
2021-11-08 11:43:59 -06:00
|
|
|
fastify.close()
|
|
|
|
DATABASE.stop()
|
|
|
|
}
|