jobs/logs.js

'use strict';

var method = require('./../method');
var projectConfig = require('./../projectConfig');
var assign = require('lodash.assign');

/**
 * @memberof jobs
 * @method logs
 * @description Stream the logs for the job with the given id, while the job is running or after it has stopped.
 * @param {object} params - Job logs parameters
 * @param {string} params.jobId - Id of the job to logs
 * @param {boolean} [params.tail] - Optional; if tail is specified logs will be streamed until the job stops.
 * @param {number} [params.line] - Optional; if line is specified logs only logs after that line will be returned (up to limit).
 * @param {number} [params.limit] - Optional; number of log lines to retrieve on each request; default limit is 2000.
 * @param {boolean} [params.json] - Optional; return JSON object instead of writing to standard out.  '--json' with no value is equivalent to true.
 * @param {function} cb - Node-style error-first callback function
 * @returns logs - The job logs
 * @example
 * paperspace.jobs.logs({
 *   jobId: 'j123abc',
 * }, function(err, res) {
 *   // handle error or result
 * });
 * @example
 * $ paperspace jobs logs \
 *     --jobId "j123abc"
 * @example
 * # HTTP request:
 * https://logs.paperspace.io
 * GET /jobs/logs?jobId=j123abc
 * x-api-key: 1ba4f98e7c0...
 * # Returns 200 on success
 * @example
 * //Example output:
 * Hello Paperspace
 * Creating file /artifacts/myoutput1.txt
 * Creating file /artifacts/myoutput2.txt
 * Finished; returning exit code 0
 */

function parseLogs(data, allLogs, json) {
	var line = 0;
	var eof = false;
	if (data.length) {
		data.forEach(function itemFunc(item) {
		if (typeof item.message === 'string') {
			if (item.message.endsWith('\r')) item.message = item.message.slice(0, -1);
			if (item.message === 'PSEOF') eof = true;
			if (item.line) line = item.line;
			if (global.paperspace_cli && !json && !eof) {
				console.log(item.message);
			} else if (allLogs) allLogs.push(item);
			} else if (item.line) line = item.line;
		});
	}
	return { count: data.length + 0, line: line, eof: eof };
}

function logs(params, cb) {
	params.jobId = projectConfig.getLastJobId(null, params.jobId);
	var tail = false;
	if (params.tail) {
		tail = true;
		delete params.tail;
	}
	var json = false;
	if (params.json) {
		json = true;
		delete params.json;
	}
	var singleChunk = false;
	if (typeof params.line === 'undefined') params.line = 0;
	else singleChunk = true; // if line is specified only a limited chunk will be returned; params.limit defines the limit; default is 2000
	var backoff = 0;
	var backoff_repeat = 0;
	var allLogs;
	if (!global.paperspace_cli || json) allLogs = [];

	var MAX_BACKOFF_SECS = 5;
	var BACKOFF_REPEATS = 10;

	function _logs() {
		return method(logs, params, function logsCb(err, data) {
			if (err) cb(err);
			var result = parseLogs(data, allLogs, json);
			if (result.count > 0) {
				// store new last line in method params
				params.line = result.line;
			}
			if ((result.count > 0 || tail) && !result.eof && !singleChunk) {
				if (result.count > 0) {
					backoff = 0;
					backoff_repeat = 0;
				}
				else if (backoff < MAX_BACKOFF_SECS) {
					// increase backoff geometrically
					if (backoff === 0) {
						backoff = 0.2;
						backoff_repeat = 0;
					}
					else if (backoff_repeat < BACKOFF_REPEATS) {
						backoff_repeat += 1;
					}
					else {
						backoff_repeat = 0;
						if (backoff > 1 && backoff < 2) backoff = 1;
						backoff *= 2;
					}
				}
				else backoff = MAX_BACKOFF_SECS; // cap backoff at this

				if (backoff > 0) {
					// sleep for backoff secs then query logs again
					return setTimeout(function _interval() {
						return _logs();
					}, backoff * 1000);
				}
				else {
					// querey logs immediately
					return _logs();
				}
			}
			if (global.paperspace_cli && !json) {
				return cb();
			}
			return cb(null, allLogs);
		});
	}

	return _logs();
}


assign(logs, {
	auth: true,
	group: 'jobs',
	name: 'logs',
	method: 'get',
	route: '/jobs/logs',
	requires: {
		jobId: 'string',
	},
	returns: {},
});

module.exports = logs;