gristlabs_grist-core/app/server/lib/sandboxUtil.ts
Dmitry S 534615dd50 (core) Update logging in sandbox code, and log tracebacks as single log messages.
Summary:
- Replace logger module by the standard module 'logging'.
- When a log message from the sandbox includes newlines (e.g. for tracebacks),
  keep those lines together in the Node log message.

  Previously each line was a different message, making it difficult to view
  tracebacks, particularly in prod where each line becomes a separate message
  object.

- Fix assorted lint errors.

Test Plan: Added a test for the log-line splitting and escaping logic.

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3956
2023-07-18 11:21:25 -04:00

66 lines
2.4 KiB
TypeScript

/**
* Various utilities and constants for communicating with the python sandbox.
*/
import * as MemBuffer from 'app/common/MemBuffer';
import log from 'app/server/lib/log';
/**
* SandboxError is an error type for reporting errors forwarded from the sandbox.
*/
export class SandboxError extends Error {
constructor(message: string) {
super("[Sandbox] " + (message || 'Python reported an error'));
}
}
/**
* Special msgCode values that precede msgBody to indicate what kind of message it is.
* These all cost one byte. If we needed more, we should probably switch to a number (5 bytes)
* CALL = call to the other side. The data must be an array of [func_name, arguments...]
* DATA = data must be a value to return to a call from the other side
* EXC = data must be an exception to return to a call from the other side
*/
export const CALL = null;
export const DATA = true;
export const EXC = false;
/**
* Returns a function that takes data buffers and logs them to log.info() with the given prefix.
* The logged output is line-oriented, so that the prefix is only inserted at the start of a line.
* Binary data is encoded as with JSON.stringify.
*/
export function makeLinePrefixer(prefix: string, logMeta: object) {
return _makeLinePrefixer(prefix, logMeta, text => text.indexOf('\n'));
}
/**
* Same as makeLinePrefixer, but avoids splitting lines except when a line starts with '[', since
* the sandbox prefixes all log messages with "[LEVEL]" prefix.
*/
export function makeLogLinePrefixer(prefix: string, logMeta: object) {
return _makeLinePrefixer(prefix, logMeta, text => {
const newline = text.indexOf("\n[");
// If no next log message, split at the last newline. Any earlier newlines would be included.
return (newline !== -1) ? newline : text.lastIndexOf("\n");
});
}
function _makeLinePrefixer(prefix: string, logMeta: object, findLineEnd: (text: string) => number) {
let partial = '';
return (data: Uint8Array) => {
partial += MemBuffer.arrayToString(data);
let newline;
while (partial && (newline = findLineEnd(partial)) !== -1) {
const line = partial.slice(0, newline);
partial = partial.slice(newline + 1);
// Escape some parts of the string by serializing it to JSON (without the quotes).
log.origLog('info', "%s%s", prefix,
JSON.stringify(line).slice(1, -1).replace(/\\(['"\\])/g, '$1').replace(/\\n/g, '\n'),
logMeta);
}
};
}