UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

232 lines 7.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Logs = void 0; const Websocket = require("ws"); const _ = require("lodash"); const querystring = require("querystring"); const client_1 = require("../session/client"); const time_1 = require("../util/time"); const options_1 = require("./options"); const command_1 = require("../cli/command"); const functions_1 = require("../util/functions"); const PING_INTERVAL_MS = 30 * 1000; const PONG_DEADLINE_MS = 10 * 1000; class Logs extends command_1.Command { constructor() { super(); this.command = 'logs <query>'; this.describe = 'Show logs'; } withLogsOptions(yargs) { return yargs .options({ tail: { alias: ['t', 'f'], describe: 'Tail the logs (follow)', boolean: true, }, limit: { requiresArg: true, describe: 'Limit on number of entries to show', default: '30', }, 'delay-for': { describe: 'Delay in tailing by number of seconds to accumulate logs for re-ordering', number: true, default: '0', }, since: { describe: 'Lookback window', default: '1h', }, from: { describe: 'Start looking for logs at this absolute time (inclusive)', requiresArg: true, }, to: { describe: 'Stop looking for logs at this absolute time (exclusive)', requiresArg: true, }, output: { alias: 'o', describe: 'Specify output mode. raw suppresses log labels and timestamp', requiresArg: true, choices: ['default', 'raw', 'jsonl'], default: 'default', }, direction: { describe: 'Sort order of logs', requiresArg: true, default: 'forward', choices: ['forward', 'backward'], }, }) .positional('query', { description: 'LogQL query', }); } builder(yargs) { return (0, functions_1.pipe)( // this.withLogsOptions, options_1.withProfileOptions, options_1.withOrgOptions, options_1.withRequestOptions, options_1.withDebugOptions)(yargs); } async handle(args) { const now = Date.now(); const query = { //delay_for=0&from=1613133203899847515&limit=30&query=%7Bgvc%3D%22demo%22%2C+workload%3D%22cat%22%7D delay_for: args.delayFor, limit: args.limit, query: args.query, direction: args.direction, start: (0, time_1.toNanos)(now, args.since, args.from), }; if (args.to) { query.end = (0, time_1.toNanos)(now, args.since, args.to); } if (args.tail) { await this.tail(args, query); } else { await this.query_range(args, query); } } async tail(args, query) { const discovery = await this.session.discovery; // refresh token await this.client.get(`/org/${this.session.context.org}`).catch(); let url = `${discovery.endpoints.logs}/logs/org/${this.session.context.org}/loki/api/v1/tail?${querystring.stringify(query)}`; if (url.startsWith('http')) { url = 'ws' + url.substring(4); } client_1.wire.debug('>>>>>> WS Try Open with Token: ' + (0, client_1.convertAuthTokenForDebug)(this.session.request.token)); const socket = new Websocket(url, { headers: { authorization: this.session.request.token, }, }); let terminateTimeout = null; const pingInterval = setInterval(() => { socket.ping(); client_1.wire.debug('>>>>>>> WS Ping Sent'); terminateTimeout = setTimeout(() => { socket.terminate(); }, PONG_DEADLINE_MS); }, PING_INTERVAL_MS); socket.on('pong', () => { client_1.wire.debug('<<<<<<< WS Pong Received'); if (terminateTimeout) { clearTimeout(terminateTimeout); terminateTimeout = null; } }); socket.on('message', (msg) => { const obj = JSON.parse(msg); const events = makeEvents(obj.streams); if (events.length > 0) { // get the timestamp of the last event received const ts = events[events.length - 1].timestamp; query.from = ts + 1; } for (let e of events) { this.session.out(formatEvent(e, args.output)); } }); socket.on('error', (err) => { if (pingInterval) { clearInterval(pingInterval); } // if token expired or invalid, exit immediately if (err.message.includes('Unexpected server response: 401')) { this.session.abort({ message: 'Stream error: ' + err.message, error: err, exitCode: 1, }); } // continuing to close() }); socket.on('close', async (code, reason) => { client_1.wire.debug('<<<<<<< WS Close Event info'); if (pingInterval) { clearInterval(pingInterval); } this.session.err(`Reconnecting: ${reason}`); await (0, time_1.sleep)(3000); this.tail(args, query); }); } async query_range(args, query) { const discovery = await this.session.discovery; let url = `/logs/org/${this.session.context.org}/loki/api/v1/query_range`; const res = await this.client.get(url, { params: query, baseURL: discovery.endpoints.logs, maxContentLength: -1, }); const events = makeEvents(res.data.result); for (let e of events) { this.session.out(formatEvent(e, args.output)); } } } exports.Logs = Logs; function makeEvents(streams) { const handler = { get: function (target, prop) { const stream = streams[target.si]; switch (prop) { case 'timestamp': return stream.values[target.vi][0]; case 'labels': return stream.stream; case 'line': return stream.values[target.vi][1]; default: return undefined; } }, }; const events = []; for (let si = 0; si < streams.length; si++) { for (let vi = 0; vi < streams[si].values.length; vi++) { events.push(new Proxy({ si, vi, }, handler)); } } _.sortBy(events, 'timestamp'); return events; } function formatEvent(e, format) { const timestamp = new Date(e.timestamp / 1000000); if (format == 'jsonl') { return JSON.stringify({ timestamp: timestamp, labels: e.labels, line: e.line, }); } if (format == 'default') { return `${timestamp.toISOString()} ${formatLabels(e.labels)} ${e.line}`; } if (format == 'raw') { return e.line; } // unlnown format type return e.line; } function formatLabels(labels) { if (!labels) { return '{}'; } let res = '{'; for (let prop in labels) { res += `${prop}=${JSON.stringify(labels[prop])}, `; } if (res.length >= 2) { res = res.slice(0, -2); } return res + '}'; } //# sourceMappingURL=logs.js.map