diff --git a/devenv/docker/blocks/loki/README.md b/devenv/docker/blocks/loki-promtail/README.md similarity index 100% rename from devenv/docker/blocks/loki/README.md rename to devenv/docker/blocks/loki-promtail/README.md diff --git a/devenv/docker/blocks/loki-promtail/docker-compose.yaml b/devenv/docker/blocks/loki-promtail/docker-compose.yaml new file mode 100644 index 00000000000..fbc947523d2 --- /dev/null +++ b/devenv/docker/blocks/loki-promtail/docker-compose.yaml @@ -0,0 +1,14 @@ + # datasource URL: http://localhost:3100/ + loki: + image: grafana/loki:latest + ports: + - "3100:3100" + command: -config.file=/etc/loki/local-config.yaml + promtail: + image: grafana/promtail:latest + volumes: + - ./docker/blocks/loki-promtail/promtail-config.yaml:/etc/promtail/docker-config.yaml + - /var/log:/var/log + - ../data/log:/var/log/grafana + command: + -config.file=/etc/promtail/docker-config.yaml diff --git a/devenv/docker/blocks/loki/promtail-config.yaml b/devenv/docker/blocks/loki-promtail/promtail-config.yaml similarity index 100% rename from devenv/docker/blocks/loki/promtail-config.yaml rename to devenv/docker/blocks/loki-promtail/promtail-config.yaml diff --git a/devenv/docker/blocks/loki/data/Dockerfile b/devenv/docker/blocks/loki/data/Dockerfile new file mode 100644 index 00000000000..ea13482e4d3 --- /dev/null +++ b/devenv/docker/blocks/loki/data/Dockerfile @@ -0,0 +1,3 @@ +FROM node:16-alpine + +COPY data.js /home/node/data.js \ No newline at end of file diff --git a/devenv/docker/blocks/loki/data/data.js b/devenv/docker/blocks/loki/data/data.js new file mode 100644 index 00000000000..38f0d9c2525 --- /dev/null +++ b/devenv/docker/blocks/loki/data/data.js @@ -0,0 +1,107 @@ +const http = require('http'); + +if (process.argv.length !== 3) { + throw new Error('invalid command line: use node sendLogs.js LOKIC_BASE_URL'); +} + +const LOKI_BASE_URL = process.argv[2]; + +// helper function, do a http request +async function jsonRequest(data, method, url, expectedStatusCode) { + return new Promise((resolve, reject) => { + const req = http.request( + { + protocol: url.protocol, + host: url.hostname, + port: url.port, + path: `${url.pathname}${url.search}`, + method, + headers: { 'content-type': 'application/json' }, + }, + (res) => { + if (res.statusCode !== expectedStatusCode) { + reject(new Error(`Invalid response: ${res.statusCode}`)); + } else { + resolve(); + } + } + ); + + req.on('error', (err) => reject(err)); + + req.write(JSON.stringify(data)); + req.end(); + }); +} + +// helper function, choose a random element from an array +function chooseRandomElement(items) { + const index = Math.trunc(Math.random() * items.length); + return items[index]; +} + +// helper function, sleep for a duration +async function sleep(duration) { + return new Promise((resolve) => { + setTimeout(resolve, duration); + }); +} + +async function lokiSendLogLine(timestampMs, line, tags) { + // we keep nanosecond-timestamp in a string because + // as a number it would be too large + const timestampNs = `${timestampMs}000000`; + const data = { + streams: [ + { + stream: tags, + values: [[timestampNs, line]], + }, + ], + }; + + const url = new URL(LOKI_BASE_URL); + url.pathname = '/loki/api/v1/push'; + + await jsonRequest(data, 'POST', url, 204); +} + +function getRandomLogLine(counter) { + const randomText = `${Math.trunc(Math.random() * 1000 * 1000 * 1000)}`; + const maybeAnsiText = Math.random() < 0.5 ? 'with ANSI \u001b[31mpart of the text\u001b[0m' : ''; + return JSON.stringify({ + _entry: `log text ${maybeAnsiText} [${randomText}]`, + counter: counter.toString(), + float: Math.random() > 0.2 ? (100 * Math.random()).toString() : 'NaN', + label: chooseRandomElement(['val1', 'val2', 'val3']), + level: chooseRandomElement(['info', 'info', 'error']), + }); +} + +const SLEEP_ANGLE_STEP = Math.PI / 200; +let sleepAngle = 0; +function getNextSineWaveSleepDuration() { + sleepAngle += SLEEP_ANGLE_STEP; + return Math.trunc(1000 * Math.abs(Math.sin(sleepAngle))); +} + +async function main() { + const tags = { + place: 'moon', + }; + + for (let step = 0; step < 300; step++) { + await sleep(getNextSineWaveSleepDuration()); + const timestampMs = new Date().getTime(); + const line = getRandomLogLine(step + 1); + lokiSendLogLine(timestampMs, line, tags); + } +} + +// when running in docker, we catch the needed stop-signal, to shutdown fast +process.on('SIGTERM', () => { + console.log('shutdown requested'); + process.exit(0); +}); + +main(); diff --git a/devenv/docker/blocks/loki/docker-compose.yaml b/devenv/docker/blocks/loki/docker-compose.yaml index 8b70449dcad..d6f306fbd99 100644 --- a/devenv/docker/blocks/loki/docker-compose.yaml +++ b/devenv/docker/blocks/loki/docker-compose.yaml @@ -1,14 +1,13 @@ - # datasource URL: http://localhost:3100/ loki: image: grafana/loki:latest ports: - "3100:3100" - command: -config.file=/etc/loki/local-config.yaml - promtail: - image: grafana/promtail:latest - volumes: - - ./docker/blocks/loki/promtail-config.yaml:/etc/promtail/docker-config.yaml - - /var/log:/var/log - - ../data/log:/var/log/grafana - command: - -config.file=/etc/promtail/docker-config.yaml + + data: + build: docker/blocks/loki/data + command: node /home/node/data.js http://loki:3100 + depends_on: + - loki + # when loki starts, there might be some time while it is not + # accepting requests, so we allow data.js to restart on failure. + restart: "on-failure" \ No newline at end of file