@controlplane/cli
Version:
Control Plane Corporation CLI
232 lines • 7.92 kB
JavaScript
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
;