@naturalcycles/nodejs-lib 12.50.0 → 12.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,6 +13,7 @@ const colors_1 = require("../colors");
13
13
  * 3. Reasonable defaults(tm), e.g non-infinite Timeout
14
14
  */
15
15
  function getGot(opt = {}) {
16
+ opt.logger || (opt.logger = console);
16
17
  return got_1.default.extend({
17
18
  // Most-important is to set to anything non-empty (so, requests don't "hang" by default).
18
19
  // Should be long enough to handle for slow responses from scaled cloud APIs in times of spikes
@@ -93,7 +94,7 @@ function gotBeforeRequestHook(opt) {
93
94
  };
94
95
  if (opt.logStart) {
95
96
  const shortUrl = getShortUrl(opt, options.url, options.prefixUrl);
96
- console.log([(0, colors_1.dimGrey)(' >>'), (0, colors_1.dimGrey)(options.method), (0, colors_1.grey)(shortUrl)].join(' '));
97
+ opt.logger.log([(0, colors_1.dimGrey)(' >>'), (0, colors_1.dimGrey)(options.method), (0, colors_1.grey)(shortUrl)].join(' '));
97
98
  }
98
99
  };
99
100
  }
@@ -104,7 +105,7 @@ function gotAfterResponseHook(opt = {}) {
104
105
  const { started } = resp.request.options.context;
105
106
  const { url, prefixUrl, method } = resp.request.options;
106
107
  const shortUrl = getShortUrl(opt, url, prefixUrl);
107
- console.log([
108
+ opt.logger.log([
108
109
  (0, colors_1.dimGrey)(' <<'),
109
110
  coloredHttpCode(resp.statusCode),
110
111
  (0, colors_1.dimGrey)(method),
@@ -117,7 +118,7 @@ function gotAfterResponseHook(opt = {}) {
117
118
  }
118
119
  // Error responses are not logged, cause they're included in Error message already
119
120
  if (opt.logResponse && success) {
120
- console.log((0, __1.inspectAny)((0, js_lib_1._jsonParseIfPossible)(resp.body), { maxLen: opt.maxResponseLength }));
121
+ opt.logger.log((0, __1.inspectAny)(resp.body, { maxLen: opt.maxResponseLength }));
121
122
  }
122
123
  return resp;
123
124
  };
@@ -1,4 +1,4 @@
1
- import { AnyObject } from '@naturalcycles/js-lib';
1
+ import { AnyObject, CommonLogger } from '@naturalcycles/js-lib';
2
2
  import type { Options } from 'got';
3
3
  export interface GetGotOptions extends Options {
4
4
  /**
@@ -27,6 +27,10 @@ export interface GetGotOptions extends Options {
27
27
  * Set to false to strip searchParams from url when logging (both success and error)
28
28
  */
29
29
  logWithSearchParams?: boolean;
30
+ /**
31
+ * Defaults to `console`
32
+ */
33
+ logger?: CommonLogger;
30
34
  /**
31
35
  * Max length of response object before it's truncated.
32
36
  *
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ import { tableDiff, TableDiffOptions } from './diff/tableDiff';
7
7
  import { getGot } from './got/getGot';
8
8
  import { GetGotOptions } from './got/got.model';
9
9
  import { memoryUsage, memoryUsageFull, processSharedUtil } from './infra/process.util';
10
- import { Debug, DebugLogLevel, IDebug, IDebugger } from './log/debug';
10
+ import { Debug, IDebug, IDebugger } from './log/debug';
11
11
  import { base64ToBuffer, base64ToString, bufferToBase64, hash, md5, hashAsBuffer, md5AsBuffer, stringToBase64 } from './security/hash.util';
12
12
  import { ALPHABET_ALPHANUMERIC, ALPHABET_ALPHANUMERIC_LOWERCASE, ALPHABET_ALPHANUMERIC_UPPERCASE, ALPHABET_LOWERCASE, ALPHABET_NUMBER, ALPHABET_UPPERCASE, stringId, stringIdAsync, stringIdUnsafe } from './security/id.util';
13
13
  import { getSecretMap, loadSecretsFromEnv, loadSecretsFromJsonFile, removeSecretsFromEnv, secret, secretOptional, setSecretMap } from './security/secret.util';
@@ -51,7 +51,7 @@ import { writableForEach } from './stream/writable/writableForEach';
51
51
  import { writableFork } from './stream/writable/writableFork';
52
52
  import { writablePushToArray } from './stream/writable/writablePushToArray';
53
53
  import { writableVoid } from './stream/writable/writableVoid';
54
- import { inspectAny, InspectAnyOptions } from './string/inspectAny';
54
+ import { inspectAny, InspectAnyOptions, inspectAnyStringifyFn } from './string/inspectAny';
55
55
  import { requireEnvKeys, requireFileToExist } from './util/env.util';
56
56
  import { LRUMemoCache } from './util/lruMemoCache';
57
57
  import { gunzipBuffer, gunzipToString, gzipBuffer, gzipString, unzipBuffer, unzipToString, zipBuffer, zipString } from './util/zip.util';
@@ -65,5 +65,6 @@ import { anyObjectSchema, anySchema, arraySchema, oneOfSchema, binarySchema, boo
65
65
  import { JoiValidationError, JoiValidationErrorData } from './validation/joi/joi.validation.error';
66
66
  import { convert, getValidationResult, isValid, JoiValidationResult, undefinedIfInvalid, validate } from './validation/joi/joi.validation.util';
67
67
  import { sanitizeHTML, SanitizeHTMLOptions } from './validation/sanitize.util';
68
- export type { JoiValidationErrorData, JoiValidationResult, ValidationErrorItem, ExtendedJoi, SchemaTyped, AnySchema, AnySchemaTyped, ArraySchemaTyped, BooleanSchemaTyped, NumberSchemaTyped, ObjectSchemaTyped, StringSchemaTyped, IDebug, IDebugger, SlackServiceCfg, SlackMessage, SlackMessageProps, SlackApiBody, SlackMessagePrefixHook, ReadableTyped, WritableTyped, TransformTyped, PipelineFromNDJsonFileOptions, PipelineToNDJsonFileOptions, TransformJsonParseOptions, TransformToNDJsonOptions, TransformMapOptions, TransformMapSyncOptions, NDJSONStreamForEachOptions, TransformOptions, TransformLogProgressOptions, TransformMultiThreadedOptions, WorkerClassInterface, WorkerInput, WorkerOutput, TableDiffOptions, InspectAnyOptions, Got, GetGotOptions, AfterResponseHook, BeforeErrorHook, BeforeRequestHook, AjvValidationOptions, AjvSchemaCfg, AjvValidationErrorData, SanitizeHTMLOptions, };
69
- export { JoiValidationError, validate, getValidationResult, isValid, undefinedIfInvalid, convert, Joi, booleanSchema, booleanDefaultToFalseSchema, stringSchema, numberSchema, integerSchema, percentageSchema, dateStringSchema, arraySchema, binarySchema, objectSchema, oneOfSchema, anySchema, anyObjectSchema, baseDBEntitySchema, savedDBEntitySchema, idSchema, unixTimestampSchema, verSchema, emailSchema, SEM_VER_PATTERN, semVerSchema, userAgentSchema, utcOffsetSchema, ipAddressSchema, slugSchema, urlSchema, processSharedUtil, zipBuffer, gzipBuffer, unzipBuffer, gunzipBuffer, zipString, gzipString, unzipToString, gunzipToString, requireEnvKeys, requireFileToExist, LRUMemoCache, stringId, stringIdAsync, stringIdUnsafe, ALPHABET_NUMBER, ALPHABET_LOWERCASE, ALPHABET_UPPERCASE, ALPHABET_ALPHANUMERIC_LOWERCASE, ALPHABET_ALPHANUMERIC_UPPERCASE, ALPHABET_ALPHANUMERIC, md5, hash, hashAsBuffer, md5AsBuffer, stringToBase64, base64ToString, bufferToBase64, base64ToBuffer, Debug, DebugLogLevel, getSecretMap, setSecretMap, loadSecretsFromEnv, loadSecretsFromJsonFile, removeSecretsFromEnv, secret, secretOptional, memoryUsage, memoryUsageFull, SlackService, slackDefaultMessagePrefixHook, readableCreate, readableFrom, readableFromArray, readableToArray, readableForEach, readableForEachSync, readableMap, readableMapToArray, _pipeline, transformBuffer, ndjsonMap, ndJsonFileRead, ndJsonFileWrite, ndjsonStreamForEach, pipelineFromNDJsonFile, pipelineToNDJsonFile, NDJsonStats, streamToNDJsonFile, transformJsonParse, bufferReviver, transformToNDJson, transformFilter, transformFilterSync, transformMap, transformMapSync, transformMapSimple, transformNoOp, writableForEach, writablePushToArray, transformSplit, transformToString, transformToArray, transformTap, transformLogProgress, transformLimit, writableVoid, writableFork, transformMultiThreaded, BaseWorkerClass, tableDiff, inspectAny, getGot, HTTPError, TimeoutError, _chunkBuffer, Ajv, getAjv, AjvSchema, AjvValidationError, readJsonSchemas, readAjvSchemas, hasColors, sanitizeHTML, };
68
+ import { runScript, RunScriptOptions } from './script';
69
+ export type { RunScriptOptions, JoiValidationErrorData, JoiValidationResult, ValidationErrorItem, ExtendedJoi, SchemaTyped, AnySchema, AnySchemaTyped, ArraySchemaTyped, BooleanSchemaTyped, NumberSchemaTyped, ObjectSchemaTyped, StringSchemaTyped, IDebug, IDebugger, SlackServiceCfg, SlackMessage, SlackMessageProps, SlackApiBody, SlackMessagePrefixHook, ReadableTyped, WritableTyped, TransformTyped, PipelineFromNDJsonFileOptions, PipelineToNDJsonFileOptions, TransformJsonParseOptions, TransformToNDJsonOptions, TransformMapOptions, TransformMapSyncOptions, NDJSONStreamForEachOptions, TransformOptions, TransformLogProgressOptions, TransformMultiThreadedOptions, WorkerClassInterface, WorkerInput, WorkerOutput, TableDiffOptions, InspectAnyOptions, Got, GetGotOptions, AfterResponseHook, BeforeErrorHook, BeforeRequestHook, AjvValidationOptions, AjvSchemaCfg, AjvValidationErrorData, SanitizeHTMLOptions, };
70
+ export { JoiValidationError, validate, getValidationResult, isValid, undefinedIfInvalid, convert, Joi, booleanSchema, booleanDefaultToFalseSchema, stringSchema, numberSchema, integerSchema, percentageSchema, dateStringSchema, arraySchema, binarySchema, objectSchema, oneOfSchema, anySchema, anyObjectSchema, baseDBEntitySchema, savedDBEntitySchema, idSchema, unixTimestampSchema, verSchema, emailSchema, SEM_VER_PATTERN, semVerSchema, userAgentSchema, utcOffsetSchema, ipAddressSchema, slugSchema, urlSchema, processSharedUtil, zipBuffer, gzipBuffer, unzipBuffer, gunzipBuffer, zipString, gzipString, unzipToString, gunzipToString, requireEnvKeys, requireFileToExist, LRUMemoCache, stringId, stringIdAsync, stringIdUnsafe, ALPHABET_NUMBER, ALPHABET_LOWERCASE, ALPHABET_UPPERCASE, ALPHABET_ALPHANUMERIC_LOWERCASE, ALPHABET_ALPHANUMERIC_UPPERCASE, ALPHABET_ALPHANUMERIC, md5, hash, hashAsBuffer, md5AsBuffer, stringToBase64, base64ToString, bufferToBase64, base64ToBuffer, Debug, getSecretMap, setSecretMap, loadSecretsFromEnv, loadSecretsFromJsonFile, removeSecretsFromEnv, secret, secretOptional, memoryUsage, memoryUsageFull, SlackService, slackDefaultMessagePrefixHook, readableCreate, readableFrom, readableFromArray, readableToArray, readableForEach, readableForEachSync, readableMap, readableMapToArray, _pipeline, transformBuffer, ndjsonMap, ndJsonFileRead, ndJsonFileWrite, ndjsonStreamForEach, pipelineFromNDJsonFile, pipelineToNDJsonFile, NDJsonStats, streamToNDJsonFile, transformJsonParse, bufferReviver, transformToNDJson, transformFilter, transformFilterSync, transformMap, transformMapSync, transformMapSimple, transformNoOp, writableForEach, writablePushToArray, transformSplit, transformToString, transformToArray, transformTap, transformLogProgress, transformLimit, writableVoid, writableFork, transformMultiThreaded, BaseWorkerClass, tableDiff, inspectAny, inspectAnyStringifyFn, getGot, HTTPError, TimeoutError, _chunkBuffer, Ajv, getAjv, AjvSchema, AjvValidationError, readJsonSchemas, readAjvSchemas, hasColors, sanitizeHTML, runScript, };
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ALPHABET_LOWERCASE = exports.ALPHABET_NUMBER = exports.stringIdUnsafe = exports.stringIdAsync = exports.stringId = exports.LRUMemoCache = exports.requireFileToExist = exports.requireEnvKeys = exports.gunzipToString = exports.unzipToString = exports.gzipString = exports.zipString = exports.gunzipBuffer = exports.unzipBuffer = exports.gzipBuffer = exports.zipBuffer = exports.processSharedUtil = exports.urlSchema = exports.slugSchema = exports.ipAddressSchema = exports.utcOffsetSchema = exports.userAgentSchema = exports.semVerSchema = exports.SEM_VER_PATTERN = exports.emailSchema = exports.verSchema = exports.unixTimestampSchema = exports.idSchema = exports.savedDBEntitySchema = exports.baseDBEntitySchema = exports.anyObjectSchema = exports.anySchema = exports.oneOfSchema = exports.objectSchema = exports.binarySchema = exports.arraySchema = exports.dateStringSchema = exports.percentageSchema = exports.integerSchema = exports.numberSchema = exports.stringSchema = exports.booleanDefaultToFalseSchema = exports.booleanSchema = exports.Joi = exports.convert = exports.undefinedIfInvalid = exports.isValid = exports.getValidationResult = exports.validate = exports.JoiValidationError = void 0;
4
- exports.transformMapSync = exports.transformMap = exports.transformFilterSync = exports.transformFilter = exports.transformToNDJson = exports.bufferReviver = exports.transformJsonParse = exports.streamToNDJsonFile = exports.NDJsonStats = exports.pipelineToNDJsonFile = exports.pipelineFromNDJsonFile = exports.ndjsonStreamForEach = exports.ndJsonFileWrite = exports.ndJsonFileRead = exports.ndjsonMap = exports.transformBuffer = exports._pipeline = exports.readableMapToArray = exports.readableMap = exports.readableForEachSync = exports.readableForEach = exports.readableToArray = exports.readableFromArray = exports.readableFrom = exports.readableCreate = exports.slackDefaultMessagePrefixHook = exports.SlackService = exports.memoryUsageFull = exports.memoryUsage = exports.secretOptional = exports.secret = exports.removeSecretsFromEnv = exports.loadSecretsFromJsonFile = exports.loadSecretsFromEnv = exports.setSecretMap = exports.getSecretMap = exports.DebugLogLevel = exports.Debug = exports.base64ToBuffer = exports.bufferToBase64 = exports.base64ToString = exports.stringToBase64 = exports.md5AsBuffer = exports.hashAsBuffer = exports.hash = exports.md5 = exports.ALPHABET_ALPHANUMERIC = exports.ALPHABET_ALPHANUMERIC_UPPERCASE = exports.ALPHABET_ALPHANUMERIC_LOWERCASE = exports.ALPHABET_UPPERCASE = void 0;
5
- exports.sanitizeHTML = exports.hasColors = exports.readAjvSchemas = exports.readJsonSchemas = exports.AjvValidationError = exports.AjvSchema = exports.getAjv = exports.Ajv = exports._chunkBuffer = exports.TimeoutError = exports.HTTPError = exports.getGot = exports.inspectAny = exports.tableDiff = exports.BaseWorkerClass = exports.transformMultiThreaded = exports.writableFork = exports.writableVoid = exports.transformLimit = exports.transformLogProgress = exports.transformTap = exports.transformToArray = exports.transformToString = exports.transformSplit = exports.writablePushToArray = exports.writableForEach = exports.transformNoOp = exports.transformMapSimple = void 0;
4
+ exports.transformMapSimple = exports.transformMapSync = exports.transformMap = exports.transformFilterSync = exports.transformFilter = exports.transformToNDJson = exports.bufferReviver = exports.transformJsonParse = exports.streamToNDJsonFile = exports.NDJsonStats = exports.pipelineToNDJsonFile = exports.pipelineFromNDJsonFile = exports.ndjsonStreamForEach = exports.ndJsonFileWrite = exports.ndJsonFileRead = exports.ndjsonMap = exports.transformBuffer = exports._pipeline = exports.readableMapToArray = exports.readableMap = exports.readableForEachSync = exports.readableForEach = exports.readableToArray = exports.readableFromArray = exports.readableFrom = exports.readableCreate = exports.slackDefaultMessagePrefixHook = exports.SlackService = exports.memoryUsageFull = exports.memoryUsage = exports.secretOptional = exports.secret = exports.removeSecretsFromEnv = exports.loadSecretsFromJsonFile = exports.loadSecretsFromEnv = exports.setSecretMap = exports.getSecretMap = exports.Debug = exports.base64ToBuffer = exports.bufferToBase64 = exports.base64ToString = exports.stringToBase64 = exports.md5AsBuffer = exports.hashAsBuffer = exports.hash = exports.md5 = exports.ALPHABET_ALPHANUMERIC = exports.ALPHABET_ALPHANUMERIC_UPPERCASE = exports.ALPHABET_ALPHANUMERIC_LOWERCASE = exports.ALPHABET_UPPERCASE = void 0;
5
+ exports.runScript = exports.sanitizeHTML = exports.hasColors = exports.readAjvSchemas = exports.readJsonSchemas = exports.AjvValidationError = exports.AjvSchema = exports.getAjv = exports.Ajv = exports._chunkBuffer = exports.TimeoutError = exports.HTTPError = exports.getGot = exports.inspectAnyStringifyFn = exports.inspectAny = exports.tableDiff = exports.BaseWorkerClass = exports.transformMultiThreaded = exports.writableFork = exports.writableVoid = exports.transformLimit = exports.transformLogProgress = exports.transformTap = exports.transformToArray = exports.transformToString = exports.transformSplit = exports.writablePushToArray = exports.writableForEach = exports.transformNoOp = void 0;
6
6
  const ajv_1 = require("ajv");
7
7
  exports.Ajv = ajv_1.default;
8
8
  const got_1 = require("got");
@@ -20,7 +20,6 @@ Object.defineProperty(exports, "memoryUsageFull", { enumerable: true, get: funct
20
20
  Object.defineProperty(exports, "processSharedUtil", { enumerable: true, get: function () { return process_util_1.processSharedUtil; } });
21
21
  const debug_1 = require("./log/debug");
22
22
  Object.defineProperty(exports, "Debug", { enumerable: true, get: function () { return debug_1.Debug; } });
23
- Object.defineProperty(exports, "DebugLogLevel", { enumerable: true, get: function () { return debug_1.DebugLogLevel; } });
24
23
  const hash_util_1 = require("./security/hash.util");
25
24
  Object.defineProperty(exports, "base64ToBuffer", { enumerable: true, get: function () { return hash_util_1.base64ToBuffer; } });
26
25
  Object.defineProperty(exports, "base64ToString", { enumerable: true, get: function () { return hash_util_1.base64ToString; } });
@@ -129,6 +128,7 @@ const writableVoid_1 = require("./stream/writable/writableVoid");
129
128
  Object.defineProperty(exports, "writableVoid", { enumerable: true, get: function () { return writableVoid_1.writableVoid; } });
130
129
  const inspectAny_1 = require("./string/inspectAny");
131
130
  Object.defineProperty(exports, "inspectAny", { enumerable: true, get: function () { return inspectAny_1.inspectAny; } });
131
+ Object.defineProperty(exports, "inspectAnyStringifyFn", { enumerable: true, get: function () { return inspectAny_1.inspectAnyStringifyFn; } });
132
132
  const env_util_1 = require("./util/env.util");
133
133
  Object.defineProperty(exports, "requireEnvKeys", { enumerable: true, get: function () { return env_util_1.requireEnvKeys; } });
134
134
  Object.defineProperty(exports, "requireFileToExist", { enumerable: true, get: function () { return env_util_1.requireFileToExist; } });
@@ -191,3 +191,5 @@ Object.defineProperty(exports, "undefinedIfInvalid", { enumerable: true, get: fu
191
191
  Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return joi_validation_util_1.validate; } });
192
192
  const sanitize_util_1 = require("./validation/sanitize.util");
193
193
  Object.defineProperty(exports, "sanitizeHTML", { enumerable: true, get: function () { return sanitize_util_1.sanitizeHTML; } });
194
+ const script_1 = require("./script");
195
+ Object.defineProperty(exports, "runScript", { enumerable: true, get: function () { return script_1.runScript; } });
@@ -14,20 +14,10 @@ export interface DebugFormatters {
14
14
  }
15
15
  export interface IDebugger {
16
16
  (...args: any[]): void;
17
- debug: (...args: any[]) => void;
18
- info: (...args: any[]) => void;
19
- warn: (...args: any[]) => void;
20
- error: (...args: any[]) => void;
21
17
  color: string;
22
18
  enabled: boolean;
23
19
  log: (...args: any[]) => any;
24
20
  namespace: string;
25
21
  destroy: () => boolean;
26
22
  }
27
- export declare enum DebugLogLevel {
28
- debug = "debug",
29
- info = "info",
30
- warn = "warn",
31
- error = "error"
32
- }
33
23
  export declare const Debug: IDebug;
package/dist/log/debug.js CHANGED
@@ -1,28 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Debug = exports.DebugLogLevel = void 0;
4
- var DebugLogLevel;
5
- (function (DebugLogLevel) {
6
- DebugLogLevel["debug"] = "debug";
7
- DebugLogLevel["info"] = "info";
8
- DebugLogLevel["warn"] = "warn";
9
- DebugLogLevel["error"] = "error";
10
- })(DebugLogLevel = exports.DebugLogLevel || (exports.DebugLogLevel = {}));
3
+ exports.Debug = void 0;
11
4
  const originalDebug = require('debug');
12
5
  // eslint-disable-next-line @typescript-eslint/naming-convention
13
6
  exports.Debug = ((namespace) => {
14
7
  const instance = originalDebug(namespace);
15
8
  instance.log = console.log.bind(console); // this enables colors for objects
16
- instance.info = instance.bind(instance);
17
- const instanceDebug = originalDebug([namespace, 'debug'].join(':'));
18
- instanceDebug.log = console.debug.bind(console);
19
- instance.debug = instanceDebug.bind(instanceDebug);
20
- const instanceWarn = originalDebug([namespace, 'warn'].join(':'));
21
- instanceWarn.log = console.warn.bind(console);
22
- instance.warn = instanceWarn.bind(instanceWarn);
23
- const instanceError = originalDebug([namespace, 'error'].join(':'));
24
- instanceError.log = console.error.bind(console);
25
- instance.error = instanceError.bind(instanceError);
26
9
  return instance;
27
10
  });
28
11
  exports.Debug.coerce = originalDebug.coerce.bind(originalDebug);
@@ -1,3 +1,4 @@
1
+ import type { CommonLogger } from '@naturalcycles/js-lib';
1
2
  export interface RunScriptOptions {
2
3
  /**
3
4
  * @default false
@@ -5,6 +6,10 @@ export interface RunScriptOptions {
5
6
  * Currently it exists because of `jest --maxWorkers=1` behavior. To be investigated more..
6
7
  */
7
8
  noExit?: boolean;
9
+ /**
10
+ * Default to `console`
11
+ */
12
+ logger?: CommonLogger;
8
13
  }
9
14
  /**
10
15
  * Use it in your top-level scripts like this:
@@ -18,23 +18,24 @@ exports.runScript = void 0;
18
18
  * This function is kept light, dependency-free, exported separately.
19
19
  */
20
20
  function runScript(fn, opt = {}) {
21
+ const { logger = console, noExit } = opt;
21
22
  process.on('uncaughtException', err => {
22
- console.error('uncaughtException', err);
23
+ logger.error('uncaughtException:', err);
23
24
  });
24
25
  process.on('unhandledRejection', err => {
25
- console.error('unhandledRejection', err);
26
+ logger.error('unhandledRejection:', err);
26
27
  });
27
28
  void (async () => {
28
29
  try {
29
30
  await fn();
30
- if (!opt.noExit) {
31
+ if (!noExit) {
31
32
  setImmediate(() => process.exit(0));
32
33
  }
33
34
  }
34
35
  catch (err) {
35
- console.error('runScript failed:', err);
36
+ logger.error('runScript error:', err);
36
37
  process.exitCode = 1;
37
- if (!opt.noExit) {
38
+ if (!noExit) {
38
39
  setImmediate(() => process.exit(1));
39
40
  }
40
41
  }
@@ -5,8 +5,6 @@ const fs = require("fs");
5
5
  const __1 = require("..");
6
6
  const crypto_util_1 = require("./crypto.util");
7
7
  let loaded = false;
8
- // it's wrapped to be able to pipe console.* to Stackdriver
9
- const getLog = () => (0, __1.Debug)('nc:nodejs-lib:secret');
10
8
  const secretMap = {};
11
9
  /**
12
10
  * Loads plaintext secrets from process.env, removes them, stores locally.
@@ -25,7 +23,7 @@ function loadSecretsFromEnv() {
25
23
  delete process.env[k];
26
24
  });
27
25
  loaded = true;
28
- getLog()(`${Object.keys(secrets).length} secret(s) loaded from process.env: ${Object.keys(secrets).join(', ')}`);
26
+ console.log(`${Object.keys(secrets).length} secret(s) loaded from process.env: ${Object.keys(secrets).join(', ')}`);
29
27
  }
30
28
  exports.loadSecretsFromEnv = loadSecretsFromEnv;
31
29
  /**
@@ -58,7 +56,7 @@ function loadSecretsFromJsonFile(filePath, SECRET_ENCRYPTION_KEY) {
58
56
  }
59
57
  Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v));
60
58
  loaded = true;
61
- getLog()(`${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
59
+ console.log(`${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
62
60
  .map(s => s.toUpperCase())
63
61
  .join(', ')}`);
64
62
  }
@@ -91,7 +89,7 @@ exports.getSecretMap = getSecretMap;
91
89
  function setSecretMap(map) {
92
90
  Object.keys(secretMap).forEach(k => delete secretMap[k]);
93
91
  Object.entries(map).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v));
94
- getLog()(`setSecretMap set ${Object.keys(secretMap).length} secret(s): ${Object.keys(map)
92
+ console.log(`setSecretMap set ${Object.keys(secretMap).length} secret(s): ${Object.keys(map)
95
93
  .map(s => s.toUpperCase())
96
94
  .join(', ')}`);
97
95
  }
@@ -1,4 +1,4 @@
1
- import { StringMap } from '@naturalcycles/js-lib';
1
+ import { AnyObject, CommonLogger, CommonLogLevel } from '@naturalcycles/js-lib';
2
2
  import { SlackAttachmentField, SlackMessage, SlackServiceCfg } from './slack.service.model';
3
3
  /**
4
4
  * Has 2 main methods:
@@ -19,7 +19,16 @@ export declare class SlackService<CTX = any> {
19
19
  * Allows to "log" many things at once, similar to `console.log(one, two, three).
20
20
  */
21
21
  log(...items: any[]): Promise<void>;
22
- send(msg: SlackMessage<CTX> | string, ctx?: CTX): Promise<void>;
23
- kvToFields(kv: StringMap<any>): SlackAttachmentField[];
22
+ send(input: SlackMessage<CTX> | string, ctx?: CTX): Promise<void>;
23
+ kvToFields(kv: AnyObject): SlackAttachmentField[];
24
+ /**
25
+ * Returns a CommonLogger implementation based on this SlackService instance.
26
+ */
27
+ getCommonLogger(opt: {
28
+ minLogLevel: CommonLogLevel;
29
+ logChannel?: string;
30
+ warnChannel?: string;
31
+ errorChannel?: string;
32
+ }): CommonLogger;
24
33
  }
25
34
  export declare function slackDefaultMessagePrefixHook(msg: SlackMessage): string[];
@@ -1,22 +1,22 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.slackDefaultMessagePrefixHook = exports.SlackService = void 0;
4
+ const js_lib_1 = require("@naturalcycles/js-lib");
4
5
  const time_lib_1 = require("@naturalcycles/time-lib");
5
6
  const got_1 = require("got");
6
7
  const __1 = require("..");
7
8
  const GAE = !!process.env['GAE_INSTANCE'];
8
- const DEFAULTS = () => ({
9
+ const DEFAULTS = {
9
10
  username: 'bot',
10
11
  channel: '#log',
11
12
  icon_emoji: ':spider_web:',
12
13
  items: 'no text',
13
- });
14
+ };
14
15
  const INSPECT_OPT = {
15
16
  colors: false,
16
17
  includeErrorData: true,
17
18
  includeErrorStack: true,
18
19
  };
19
- const log = (0, __1.Debug)('nc:nodejs-lib:slack');
20
20
  /**
21
21
  * Has 2 main methods:
22
22
  *
@@ -33,7 +33,12 @@ class SlackService {
33
33
  constructor(cfg) {
34
34
  this.cfg = {
35
35
  messagePrefixHook: slackDefaultMessagePrefixHook,
36
+ logger: console,
36
37
  ...cfg,
38
+ inspectOptions: {
39
+ ...INSPECT_OPT,
40
+ ...cfg.inspectOptions,
41
+ },
37
42
  };
38
43
  }
39
44
  /**
@@ -45,38 +50,29 @@ class SlackService {
45
50
  items: items.length === 1 ? items[0] : items,
46
51
  });
47
52
  }
48
- async send(msg, ctx) {
49
- const { webhookUrl, messagePrefixHook } = this.cfg;
53
+ async send(input, ctx) {
54
+ const { webhookUrl, messagePrefixHook, inspectOptions } = this.cfg;
50
55
  // If String is passed as first argument - just transform it to a full SlackMessage
51
- if (typeof msg === 'string') {
52
- msg = {
53
- items: msg,
54
- };
55
- }
56
+ const msg = typeof input === 'string' ? { items: input } : input;
56
57
  if (ctx !== undefined) {
57
58
  Object.assign(msg, { ctx });
58
59
  }
59
- if (!msg.noLog) {
60
- log[msg.level || __1.DebugLogLevel.info](...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean));
61
- }
60
+ this.cfg.logger.log(...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean));
62
61
  if (!webhookUrl)
63
62
  return;
64
63
  // Transform msg.kv into msg.attachments
65
64
  if (msg.kv) {
66
- msg.attachments = [...(msg.attachments || []), { fields: this.kvToFields(msg.kv) }];
65
+ ;
66
+ (msg.attachments || (msg.attachments = [])).push({ fields: this.kvToFields(msg.kv) });
67
67
  delete msg.kv; // to not pass it all the way to Slack Api
68
68
  }
69
69
  let text;
70
- const inspectOpt = {
71
- ...INSPECT_OPT,
72
- ...msg.inspectOptions,
73
- };
74
70
  // Array has a special treatment here
75
71
  if (Array.isArray(msg.items)) {
76
- text = msg.items.map(t => (0, __1.inspectAny)(t, inspectOpt)).join('\n');
72
+ text = msg.items.map(t => (0, __1.inspectAny)(t, inspectOptions)).join('\n');
77
73
  }
78
74
  else {
79
- text = (0, __1.inspectAny)(msg.items, inspectOpt);
75
+ text = (0, __1.inspectAny)(msg.items, inspectOptions);
80
76
  }
81
77
  // Wrap in markdown-text-block if it's anything but plain String
82
78
  if (typeof msg.items !== 'string') {
@@ -88,22 +84,18 @@ class SlackService {
88
84
  const prefix = await messagePrefixHook(msg);
89
85
  if (prefix === null)
90
86
  return; // filtered out!
91
- const json = {
92
- ...DEFAULTS(),
87
+ const json = (0, js_lib_1._omit)({
88
+ ...DEFAULTS,
93
89
  ...this.cfg.defaults,
94
90
  ...msg,
95
91
  // Text with Prefix
96
92
  text: [prefix.join(': '), text].filter(Boolean).join('\n'),
97
- };
98
- // they're not needed in the json payload
99
- delete json['items'];
100
- delete json['ctx'];
101
- delete json['noLog'];
102
- json.channel = (this.cfg.channelByLevel || {})[msg.level] || json.channel;
93
+ }, ['items', 'ctx']);
103
94
  await got_1.default
104
95
  .post(webhookUrl, {
105
96
  json,
106
97
  responseType: 'text',
98
+ timeout: 90000,
107
99
  })
108
100
  .catch(err => {
109
101
  // ignore (unless throwOnError is set)
@@ -118,6 +110,21 @@ class SlackService {
118
110
  short: String(v).length < 80,
119
111
  }));
120
112
  }
113
+ /**
114
+ * Returns a CommonLogger implementation based on this SlackService instance.
115
+ */
116
+ getCommonLogger(opt) {
117
+ const { minLogLevel = 'log', logChannel, warnChannel, errorChannel } = opt;
118
+ const defaultChannel = this.cfg.defaults?.channel || DEFAULTS.channel;
119
+ const q = new js_lib_1.PQueue({
120
+ concurrency: 1,
121
+ });
122
+ return (0, js_lib_1.commonLoggerMinLevel)({
123
+ log: (...args) => q.push(() => this.send({ items: args, channel: logChannel || defaultChannel })),
124
+ warn: (...args) => q.push(() => this.send({ items: args, channel: warnChannel || defaultChannel })),
125
+ error: (...args) => q.push(() => this.send({ items: args, channel: errorChannel || defaultChannel })),
126
+ }, minLogLevel);
127
+ }
121
128
  }
122
129
  exports.SlackService = SlackService;
123
130
  function slackDefaultMessagePrefixHook(msg) {
@@ -1,5 +1,5 @@
1
- import { StringMap } from '@naturalcycles/js-lib';
2
- import { DebugLogLevel, InspectAnyOptions } from '..';
1
+ import { AnyObject, CommonLogger } from '@naturalcycles/js-lib';
2
+ import { InspectAnyOptions } from '..';
3
3
  /**
4
4
  * Properties that exists both in SlackApiBody (as per Slack API) and SlackMessage (our abstraction).
5
5
  */
@@ -36,31 +36,20 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
36
36
  * Optional "context object", to be used by `messagePrefixHook`.
37
37
  */
38
38
  ctx?: CTX;
39
- level?: DebugLogLevel;
40
39
  /**
41
40
  * Keys-values will be rendered as MessageAttachment with Fields
42
41
  */
43
- kv?: StringMap<any>;
42
+ kv?: AnyObject;
44
43
  /**
45
44
  * If specified - adds @name1, @name2 in the end of the message
46
45
  */
47
46
  mentions?: string[];
48
47
  /**
49
- * @default false
50
48
  * By default it ignores possible errors from slack
51
- */
52
- throwOnError?: boolean;
53
- /**
49
+ *
54
50
  * @default false
55
- * Skips logging message
56
- */
57
- noLog?: boolean;
58
- /**
59
- * Defaults to:
60
- * includeErrorData: true
61
- * includeErrorStack: true
62
51
  */
63
- inspectOptions?: InspectAnyOptions;
52
+ throwOnError?: boolean;
64
53
  }
65
54
  export interface SlackAttachmentField {
66
55
  title: string;
@@ -96,15 +85,21 @@ export interface SlackServiceCfg<CTX = any> {
96
85
  */
97
86
  webhookUrl?: string;
98
87
  defaults?: Partial<SlackMessage>;
99
- /**
100
- * Override channel when msg.level is set.
101
- * key: DebugLogLevel
102
- * value: channel name to send message to
103
- */
104
- channelByLevel?: StringMap;
105
88
  /**
106
89
  * Function to return an array of "prefix tokens" (will be joined by ': ').
107
90
  * Allows to skip (filter out) the message by returning `null`.
108
91
  */
109
92
  messagePrefixHook: SlackMessagePrefixHook<CTX>;
93
+ /**
94
+ * By default SlackService logs every message to console.log
95
+ * Pass another logger if needed.
96
+ * Pass `noopLogger` to suppress logging completely.
97
+ */
98
+ logger: CommonLogger;
99
+ /**
100
+ * Defaults to:
101
+ * includeErrorData: true
102
+ * includeErrorStack: true
103
+ */
104
+ inspectOptions: InspectAnyOptions;
110
105
  }
@@ -16,6 +16,10 @@ export interface TransformToNDJsonOptions {
16
16
  * @default `\n`
17
17
  */
18
18
  separator?: string;
19
+ /**
20
+ * @experimental
21
+ */
22
+ useFlatstr?: boolean;
19
23
  }
20
24
  /**
21
25
  * Transforms objects (objectMode=true) into chunks \n-terminated JSON strings (readableObjectMode=false).
@@ -7,7 +7,7 @@ const js_lib_1 = require("@naturalcycles/js-lib");
7
7
  * Transforms objects (objectMode=true) into chunks \n-terminated JSON strings (readableObjectMode=false).
8
8
  */
9
9
  function transformToNDJson(opt = {}) {
10
- const { strict = true, separator = '\n', sortObjects = false } = opt;
10
+ const { strict = true, separator = '\n', sortObjects = false, useFlatstr = false } = opt;
11
11
  return new stream_1.Transform({
12
12
  objectMode: true,
13
13
  readableObjectMode: false,
@@ -16,7 +16,12 @@ function transformToNDJson(opt = {}) {
16
16
  if (sortObjects) {
17
17
  chunk = (0, js_lib_1._sortObjectDeep)(chunk);
18
18
  }
19
- cb(null, JSON.stringify(chunk) + separator);
19
+ if (useFlatstr) {
20
+ cb(null, flatstr(JSON.stringify(chunk) + separator));
21
+ }
22
+ else {
23
+ cb(null, JSON.stringify(chunk) + separator);
24
+ }
20
25
  }
21
26
  catch (err) {
22
27
  console.error(err);
@@ -31,3 +36,11 @@ function transformToNDJson(opt = {}) {
31
36
  });
32
37
  }
33
38
  exports.transformToNDJson = transformToNDJson;
39
+ /**
40
+ * Based on: https://github.com/davidmarkclements/flatstr/blob/master/index.js
41
+ */
42
+ function flatstr(s) {
43
+ // eslint-disable-next-line
44
+ s | 0;
45
+ return s;
46
+ }
@@ -47,7 +47,7 @@ function transformLogProgress(opt = {}) {
47
47
  const mem = process.memoryUsage();
48
48
  const now = Date.now();
49
49
  const batchedProgress = progress * batchSize;
50
- const lastRPS = (processedLastSecond * batchedProgress) / ((now - lastSecondStarted) / 1000) || 0;
50
+ const lastRPS = (processedLastSecond * batchSize) / ((now - lastSecondStarted) / 1000) || 0;
51
51
  const rpsTotal = Math.round(batchedProgress / ((now - started) / 1000)) || 0;
52
52
  lastSecondStarted = now;
53
53
  processedLastSecond = 0;
@@ -1,8 +1,12 @@
1
1
  /// <reference types="node" />
2
2
  import { InspectOptions } from 'util';
3
- import { StringifyAnyOptions } from '@naturalcycles/js-lib';
3
+ import { StringifyAnyOptions, JsonStringifyFunction } from '@naturalcycles/js-lib';
4
4
  export interface InspectAnyOptions extends StringifyAnyOptions, InspectOptions {
5
5
  }
6
+ /**
7
+ * Just a convenience export of a const that fulfills the JsonStringifyFunction interface.
8
+ */
9
+ export declare const inspectAnyStringifyFn: JsonStringifyFunction;
6
10
  /**
7
11
  * Transforms ANY to human-readable string (via util.inspect mainly).
8
12
  * Safe (no error throwing).
@@ -1,12 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.inspectAny = void 0;
3
+ exports.inspectAny = exports.inspectAnyStringifyFn = void 0;
4
4
  const util_1 = require("util");
5
5
  const js_lib_1 = require("@naturalcycles/js-lib");
6
6
  const INSPECT_OPT = {
7
7
  breakLength: 80,
8
8
  depth: 10, // default: 2
9
9
  };
10
+ /**
11
+ * Just a convenience export of a const that fulfills the JsonStringifyFunction interface.
12
+ */
13
+ const inspectAnyStringifyFn = obj => inspectAny(obj);
14
+ exports.inspectAnyStringifyFn = inspectAnyStringifyFn;
10
15
  /**
11
16
  * Transforms ANY to human-readable string (via util.inspect mainly).
12
17
  * Safe (no error throwing).
@@ -1,4 +1,4 @@
1
- import { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib';
1
+ import { JsonSchema, JsonSchemaBuilder, CommonLogger } from '@naturalcycles/js-lib';
2
2
  import Ajv from 'ajv';
3
3
  import { AjvValidationError } from './ajvValidationError';
4
4
  export interface AjvValidationOptions {
@@ -37,6 +37,10 @@ export interface AjvSchemaCfg {
37
37
  * @default true
38
38
  */
39
39
  logErrors: boolean;
40
+ /**
41
+ * Default to `console`
42
+ */
43
+ logger: CommonLogger;
40
44
  /**
41
45
  * Option of Ajv.
42
46
  * If set to true - will mutate your input objects!
@@ -17,6 +17,7 @@ class AjvSchema {
17
17
  this.schema = schema;
18
18
  this.cfg = {
19
19
  logErrors: true,
20
+ logger: console,
20
21
  separator: '\n',
21
22
  ...cfg,
22
23
  ajv: cfg.ajv ||
@@ -89,7 +90,7 @@ class AjvSchema {
89
90
  const strValue = (0, index_1.inspectAny)(obj, { maxLen: 1000 });
90
91
  message = [message, 'Input: ' + strValue].join(separator);
91
92
  if (logErrors) {
92
- console.log(errors);
93
+ this.cfg.logger.error(errors);
93
94
  }
94
95
  return new ajvValidationError_1.AjvValidationError(message, (0, js_lib_1._filterNullishValues)({
95
96
  errors,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
- "version": "12.50.0",
3
+ "version": "12.54.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "docs-serve": "vuepress dev docs",
@@ -1,4 +1,4 @@
1
- import { StringMap, _truncate, AnyObject } from '@naturalcycles/js-lib'
1
+ import { _truncate, AnyObject } from '@naturalcycles/js-lib'
2
2
 
3
3
  export interface TableDiffOptions {
4
4
  /**
@@ -37,7 +37,7 @@ export interface TableDiffOptions {
37
37
  */
38
38
  export function tableDiff(a: AnyObject, b: AnyObject, opt: TableDiffOptions = {}): void {
39
39
  const { maxFieldLen, aTitle = 'a', bTitle = 'b' } = opt
40
- const diff: StringMap<any> = {}
40
+ const diff: AnyObject = {}
41
41
 
42
42
  if (a && b && a !== b) {
43
43
  new Set([...Object.keys(a), ...Object.keys(b)]).forEach(k => {
package/src/got/getGot.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { URL } from 'url'
2
- import { _jsonParseIfPossible, _since } from '@naturalcycles/js-lib'
2
+ import { _since } from '@naturalcycles/js-lib'
3
3
  import got, { AfterResponseHook, BeforeErrorHook, BeforeRequestHook, Got, HTTPError } from 'got'
4
4
  import { inspectAny } from '..'
5
5
  import { dimGrey, grey, red, yellow } from '../colors'
@@ -13,6 +13,8 @@ import { GetGotOptions, GotRequestContext } from './got.model'
13
13
  * 3. Reasonable defaults(tm), e.g non-infinite Timeout
14
14
  */
15
15
  export function getGot(opt: GetGotOptions = {}): Got {
16
+ opt.logger ||= console
17
+
16
18
  return got.extend({
17
19
  // Most-important is to set to anything non-empty (so, requests don't "hang" by default).
18
20
  // Should be long enough to handle for slow responses from scaled cloud APIs in times of spikes
@@ -99,7 +101,7 @@ function gotBeforeRequestHook(opt: GetGotOptions): BeforeRequestHook {
99
101
 
100
102
  if (opt.logStart) {
101
103
  const shortUrl = getShortUrl(opt, options.url, options.prefixUrl)
102
- console.log([dimGrey(' >>'), dimGrey(options.method), grey(shortUrl)].join(' '))
104
+ opt.logger!.log([dimGrey(' >>'), dimGrey(options.method), grey(shortUrl)].join(' '))
103
105
  }
104
106
  }
105
107
  }
@@ -113,7 +115,7 @@ function gotAfterResponseHook(opt: GetGotOptions = {}): AfterResponseHook {
113
115
  const { url, prefixUrl, method } = resp.request.options
114
116
  const shortUrl = getShortUrl(opt, url, prefixUrl)
115
117
 
116
- console.log(
118
+ opt.logger!.log(
117
119
  [
118
120
  dimGrey(' <<'),
119
121
  coloredHttpCode(resp.statusCode),
@@ -129,7 +131,7 @@ function gotAfterResponseHook(opt: GetGotOptions = {}): AfterResponseHook {
129
131
 
130
132
  // Error responses are not logged, cause they're included in Error message already
131
133
  if (opt.logResponse && success) {
132
- console.log(inspectAny(_jsonParseIfPossible(resp.body), { maxLen: opt.maxResponseLength }))
134
+ opt.logger!.log(inspectAny(resp.body, { maxLen: opt.maxResponseLength }))
133
135
  }
134
136
 
135
137
  return resp
@@ -1,4 +1,4 @@
1
- import { AnyObject } from '@naturalcycles/js-lib'
1
+ import { AnyObject, CommonLogger } from '@naturalcycles/js-lib'
2
2
  import type { Options } from 'got'
3
3
 
4
4
  export interface GetGotOptions extends Options {
@@ -33,6 +33,11 @@ export interface GetGotOptions extends Options {
33
33
  */
34
34
  logWithSearchParams?: boolean
35
35
 
36
+ /**
37
+ * Defaults to `console`
38
+ */
39
+ logger?: CommonLogger
40
+
36
41
  /**
37
42
  * Max length of response object before it's truncated.
38
43
  *
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ import { tableDiff, TableDiffOptions } from './diff/tableDiff'
7
7
  import { getGot } from './got/getGot'
8
8
  import { GetGotOptions } from './got/got.model'
9
9
  import { memoryUsage, memoryUsageFull, processSharedUtil } from './infra/process.util'
10
- import { Debug, DebugLogLevel, IDebug, IDebugger } from './log/debug'
10
+ import { Debug, IDebug, IDebugger } from './log/debug'
11
11
  import {
12
12
  base64ToBuffer,
13
13
  base64ToString,
@@ -108,7 +108,7 @@ import { writableForEach } from './stream/writable/writableForEach'
108
108
  import { writableFork } from './stream/writable/writableFork'
109
109
  import { writablePushToArray } from './stream/writable/writablePushToArray'
110
110
  import { writableVoid } from './stream/writable/writableVoid'
111
- import { inspectAny, InspectAnyOptions } from './string/inspectAny'
111
+ import { inspectAny, InspectAnyOptions, inspectAnyStringifyFn } from './string/inspectAny'
112
112
  import { requireEnvKeys, requireFileToExist } from './util/env.util'
113
113
  import { LRUMemoCache } from './util/lruMemoCache'
114
114
  import {
@@ -173,8 +173,10 @@ import {
173
173
  validate,
174
174
  } from './validation/joi/joi.validation.util'
175
175
  import { sanitizeHTML, SanitizeHTMLOptions } from './validation/sanitize.util'
176
+ import { runScript, RunScriptOptions } from './script'
176
177
 
177
178
  export type {
179
+ RunScriptOptions,
178
180
  JoiValidationErrorData,
179
181
  JoiValidationResult,
180
182
  ValidationErrorItem,
@@ -287,7 +289,6 @@ export {
287
289
  bufferToBase64,
288
290
  base64ToBuffer,
289
291
  Debug,
290
- DebugLogLevel,
291
292
  getSecretMap,
292
293
  setSecretMap,
293
294
  loadSecretsFromEnv,
@@ -340,6 +341,7 @@ export {
340
341
  BaseWorkerClass,
341
342
  tableDiff,
342
343
  inspectAny,
344
+ inspectAnyStringifyFn,
343
345
  getGot,
344
346
  HTTPError,
345
347
  TimeoutError,
@@ -352,4 +354,5 @@ export {
352
354
  readAjvSchemas,
353
355
  hasColors,
354
356
  sanitizeHTML,
357
+ runScript,
355
358
  }
package/src/log/debug.ts CHANGED
@@ -20,10 +20,6 @@ export interface DebugFormatters {
20
20
  export interface IDebugger {
21
21
  // (formatter: any, ...args: any[]): void;
22
22
  (...args: any[]): void
23
- debug: (...args: any[]) => void
24
- info: (...args: any[]) => void // alias to just log()
25
- warn: (...args: any[]) => void
26
- error: (...args: any[]) => void
27
23
 
28
24
  color: string
29
25
  enabled: boolean
@@ -33,35 +29,15 @@ export interface IDebugger {
33
29
  // extend: (namespace: string, delimiter?: string) => IDebugger
34
30
  }
35
31
 
36
- export enum DebugLogLevel {
37
- debug = 'debug',
38
- info = 'info',
39
- warn = 'warn',
40
- error = 'error',
41
- }
42
-
43
32
  const originalDebug = require('debug') as IDebug
44
33
 
45
34
  // eslint-disable-next-line @typescript-eslint/naming-convention
46
35
  export const Debug = ((namespace: string) => {
47
36
  const instance = originalDebug(namespace)
48
37
  instance.log = console.log.bind(console) // this enables colors for objects
49
- instance.info = instance.bind(instance)
50
-
51
- const instanceDebug = originalDebug([namespace, 'debug'].join(':'))
52
- instanceDebug.log = console.debug.bind(console)
53
- instance.debug = instanceDebug.bind(instanceDebug)
54
-
55
- const instanceWarn = originalDebug([namespace, 'warn'].join(':'))
56
- instanceWarn.log = console.warn.bind(console)
57
- instance.warn = instanceWarn.bind(instanceWarn)
58
-
59
- const instanceError = originalDebug([namespace, 'error'].join(':'))
60
- instanceError.log = console.error.bind(console)
61
- instance.error = instanceError.bind(instanceError)
62
-
63
38
  return instance
64
39
  }) as IDebug
40
+
65
41
  Debug.coerce = originalDebug.coerce.bind(originalDebug)
66
42
  Debug.disable = originalDebug.disable.bind(originalDebug)
67
43
  Debug.enable = originalDebug.enable.bind(originalDebug)
@@ -1,3 +1,5 @@
1
+ import type { CommonLogger } from '@naturalcycles/js-lib'
2
+
1
3
  export interface RunScriptOptions {
2
4
  /**
3
5
  * @default false
@@ -5,6 +7,11 @@ export interface RunScriptOptions {
5
7
  * Currently it exists because of `jest --maxWorkers=1` behavior. To be investigated more..
6
8
  */
7
9
  noExit?: boolean
10
+
11
+ /**
12
+ * Default to `console`
13
+ */
14
+ logger?: CommonLogger
8
15
  }
9
16
 
10
17
  /**
@@ -24,24 +31,26 @@ export interface RunScriptOptions {
24
31
  * This function is kept light, dependency-free, exported separately.
25
32
  */
26
33
  export function runScript(fn: (...args: any[]) => any, opt: RunScriptOptions = {}): void {
34
+ const { logger = console, noExit } = opt
35
+
27
36
  process.on('uncaughtException', err => {
28
- console.error('uncaughtException', err)
37
+ logger.error('uncaughtException:', err)
29
38
  })
30
39
  process.on('unhandledRejection', err => {
31
- console.error('unhandledRejection', err)
40
+ logger.error('unhandledRejection:', err)
32
41
  })
33
42
 
34
43
  void (async () => {
35
44
  try {
36
45
  await fn()
37
46
 
38
- if (!opt.noExit) {
47
+ if (!noExit) {
39
48
  setImmediate(() => process.exit(0))
40
49
  }
41
50
  } catch (err) {
42
- console.error('runScript failed:', err)
51
+ logger.error('runScript error:', err)
43
52
  process.exitCode = 1
44
- if (!opt.noExit) {
53
+ if (!noExit) {
45
54
  setImmediate(() => process.exit(1))
46
55
  }
47
56
  }
@@ -1,13 +1,10 @@
1
1
  import * as fs from 'fs'
2
2
  import { StringMap } from '@naturalcycles/js-lib'
3
- import { base64ToString, Debug } from '..'
3
+ import { base64ToString } from '..'
4
4
  import { decryptRandomIVBuffer } from './crypto.util'
5
5
 
6
6
  let loaded = false
7
7
 
8
- // it's wrapped to be able to pipe console.* to Stackdriver
9
- const getLog = () => Debug('nc:nodejs-lib:secret')
10
-
11
8
  const secretMap: StringMap = {}
12
9
 
13
10
  /**
@@ -29,7 +26,7 @@ export function loadSecretsFromEnv(): void {
29
26
  })
30
27
 
31
28
  loaded = true
32
- getLog()(
29
+ console.log(
33
30
  `${Object.keys(secrets).length} secret(s) loaded from process.env: ${Object.keys(secrets).join(
34
31
  ', ',
35
32
  )}`,
@@ -69,7 +66,7 @@ export function loadSecretsFromJsonFile(filePath: string, SECRET_ENCRYPTION_KEY?
69
66
  Object.entries(secrets).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
70
67
 
71
68
  loaded = true
72
- getLog()(
69
+ console.log(
73
70
  `${Object.keys(secrets).length} secret(s) loaded from ${filePath}: ${Object.keys(secrets)
74
71
  .map(s => s.toUpperCase())
75
72
  .join(', ')}`,
@@ -105,7 +102,7 @@ export function getSecretMap(): StringMap {
105
102
  export function setSecretMap(map: StringMap): void {
106
103
  Object.keys(secretMap).forEach(k => delete secretMap[k])
107
104
  Object.entries(map).forEach(([k, v]) => (secretMap[k.toUpperCase()] = v))
108
- getLog()(
105
+ console.log(
109
106
  `setSecretMap set ${Object.keys(secretMap).length} secret(s): ${Object.keys(map)
110
107
  .map(s => s.toUpperCase())
111
108
  .join(', ')}`,
@@ -1,5 +1,5 @@
1
- import { StringMap } from '@naturalcycles/js-lib'
2
- import { DebugLogLevel, InspectAnyOptions } from '..'
1
+ import { AnyObject, CommonLogger } from '@naturalcycles/js-lib'
2
+ import { InspectAnyOptions } from '..'
3
3
 
4
4
  /**
5
5
  * Properties that exists both in SlackApiBody (as per Slack API) and SlackMessage (our abstraction).
@@ -44,12 +44,10 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
44
44
  */
45
45
  ctx?: CTX
46
46
 
47
- level?: DebugLogLevel
48
-
49
47
  /**
50
48
  * Keys-values will be rendered as MessageAttachment with Fields
51
49
  */
52
- kv?: StringMap<any>
50
+ kv?: AnyObject
53
51
 
54
52
  /**
55
53
  * If specified - adds @name1, @name2 in the end of the message
@@ -57,23 +55,11 @@ export interface SlackMessage<CTX = any> extends SlackMessageProps {
57
55
  mentions?: string[]
58
56
 
59
57
  /**
60
- * @default false
61
58
  * By default it ignores possible errors from slack
62
- */
63
- throwOnError?: boolean
64
-
65
- /**
59
+ *
66
60
  * @default false
67
- * Skips logging message
68
- */
69
- noLog?: boolean
70
-
71
- /**
72
- * Defaults to:
73
- * includeErrorData: true
74
- * includeErrorStack: true
75
61
  */
76
- inspectOptions?: InspectAnyOptions
62
+ throwOnError?: boolean
77
63
  }
78
64
 
79
65
  export interface SlackAttachmentField {
@@ -120,16 +106,23 @@ export interface SlackServiceCfg<CTX = any> {
120
106
 
121
107
  defaults?: Partial<SlackMessage>
122
108
 
123
- /**
124
- * Override channel when msg.level is set.
125
- * key: DebugLogLevel
126
- * value: channel name to send message to
127
- */
128
- channelByLevel?: StringMap
129
-
130
109
  /**
131
110
  * Function to return an array of "prefix tokens" (will be joined by ': ').
132
111
  * Allows to skip (filter out) the message by returning `null`.
133
112
  */
134
113
  messagePrefixHook: SlackMessagePrefixHook<CTX>
114
+
115
+ /**
116
+ * By default SlackService logs every message to console.log
117
+ * Pass another logger if needed.
118
+ * Pass `noopLogger` to suppress logging completely.
119
+ */
120
+ logger: CommonLogger
121
+
122
+ /**
123
+ * Defaults to:
124
+ * includeErrorData: true
125
+ * includeErrorStack: true
126
+ */
127
+ inspectOptions: InspectAnyOptions
135
128
  }
@@ -1,7 +1,14 @@
1
- import { StringMap } from '@naturalcycles/js-lib'
1
+ import {
2
+ _omit,
3
+ AnyObject,
4
+ CommonLogger,
5
+ commonLoggerMinLevel,
6
+ CommonLogLevel,
7
+ PQueue,
8
+ } from '@naturalcycles/js-lib'
2
9
  import { dayjs } from '@naturalcycles/time-lib'
3
10
  import got from 'got'
4
- import { Debug, DebugLogLevel, inspectAny, InspectAnyOptions } from '..'
11
+ import { inspectAny, InspectAnyOptions } from '..'
5
12
  import {
6
13
  SlackApiBody,
7
14
  SlackAttachmentField,
@@ -11,12 +18,12 @@ import {
11
18
 
12
19
  const GAE = !!process.env['GAE_INSTANCE']
13
20
 
14
- const DEFAULTS = (): SlackMessage => ({
21
+ const DEFAULTS: SlackMessage = {
15
22
  username: 'bot',
16
23
  channel: '#log',
17
24
  icon_emoji: ':spider_web:',
18
25
  items: 'no text',
19
- })
26
+ }
20
27
 
21
28
  const INSPECT_OPT: InspectAnyOptions = {
22
29
  colors: false,
@@ -24,8 +31,6 @@ const INSPECT_OPT: InspectAnyOptions = {
24
31
  includeErrorStack: true,
25
32
  }
26
33
 
27
- const log = Debug('nc:nodejs-lib:slack')
28
-
29
34
  /**
30
35
  * Has 2 main methods:
31
36
  *
@@ -42,7 +47,12 @@ export class SlackService<CTX = any> {
42
47
  constructor(cfg: Partial<SlackServiceCfg<CTX>>) {
43
48
  this.cfg = {
44
49
  messagePrefixHook: slackDefaultMessagePrefixHook,
50
+ logger: console,
45
51
  ...cfg,
52
+ inspectOptions: {
53
+ ...INSPECT_OPT,
54
+ ...cfg.inspectOptions,
55
+ },
46
56
  }
47
57
  }
48
58
 
@@ -58,47 +68,34 @@ export class SlackService<CTX = any> {
58
68
  })
59
69
  }
60
70
 
61
- async send(msg: SlackMessage<CTX> | string, ctx?: CTX): Promise<void> {
62
- const { webhookUrl, messagePrefixHook } = this.cfg
71
+ async send(input: SlackMessage<CTX> | string, ctx?: CTX): Promise<void> {
72
+ const { webhookUrl, messagePrefixHook, inspectOptions } = this.cfg
63
73
 
64
74
  // If String is passed as first argument - just transform it to a full SlackMessage
65
- if (typeof msg === 'string') {
66
- msg = {
67
- items: msg,
68
- }
69
- }
75
+ const msg = typeof input === 'string' ? { items: input } : input
70
76
 
71
77
  if (ctx !== undefined) {
72
78
  Object.assign(msg, { ctx })
73
79
  }
74
80
 
75
- if (!msg.noLog) {
76
- log[msg.level || DebugLogLevel.info](
77
- ...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean),
78
- )
79
- }
81
+ this.cfg.logger.log(...[msg.items, msg.kv, msg.attachments, msg.mentions].filter(Boolean))
80
82
 
81
83
  if (!webhookUrl) return
82
84
 
83
85
  // Transform msg.kv into msg.attachments
84
86
  if (msg.kv) {
85
- msg.attachments = [...(msg.attachments || []), { fields: this.kvToFields(msg.kv) }]
87
+ ;(msg.attachments ||= []).push({ fields: this.kvToFields(msg.kv) })
86
88
 
87
89
  delete msg.kv // to not pass it all the way to Slack Api
88
90
  }
89
91
 
90
92
  let text: string
91
93
 
92
- const inspectOpt = {
93
- ...INSPECT_OPT,
94
- ...msg.inspectOptions,
95
- }
96
-
97
94
  // Array has a special treatment here
98
95
  if (Array.isArray(msg.items)) {
99
- text = msg.items.map(t => inspectAny(t, inspectOpt)).join('\n')
96
+ text = msg.items.map(t => inspectAny(t, inspectOptions)).join('\n')
100
97
  } else {
101
- text = inspectAny(msg.items, inspectOpt)
98
+ text = inspectAny(msg.items, inspectOptions)
102
99
  }
103
100
 
104
101
  // Wrap in markdown-text-block if it's anything but plain String
@@ -113,39 +110,65 @@ export class SlackService<CTX = any> {
113
110
  const prefix = await messagePrefixHook(msg)
114
111
  if (prefix === null) return // filtered out!
115
112
 
116
- const json: SlackApiBody = {
117
- ...DEFAULTS(),
118
- ...this.cfg.defaults,
119
- ...msg,
120
- // Text with Prefix
121
- text: [prefix.join(': '), text].filter(Boolean).join('\n'),
122
- }
123
-
124
- // they're not needed in the json payload
125
- delete json['items']
126
- delete json['ctx']
127
- delete json['noLog']
128
-
129
- json.channel = (this.cfg.channelByLevel || {})[msg.level!] || json.channel
113
+ const json: SlackApiBody = _omit(
114
+ {
115
+ ...DEFAULTS,
116
+ ...this.cfg.defaults,
117
+ ...msg,
118
+ // Text with Prefix
119
+ text: [prefix.join(': '), text].filter(Boolean).join('\n'),
120
+ },
121
+ ['items', 'ctx'],
122
+ )
130
123
 
131
124
  await got
132
125
  .post(webhookUrl, {
133
126
  json,
134
127
  responseType: 'text',
128
+ timeout: 90_000,
135
129
  })
136
130
  .catch(err => {
137
131
  // ignore (unless throwOnError is set)
138
- if ((msg as SlackMessage).throwOnError) throw err
132
+ if (msg.throwOnError) throw err
139
133
  })
140
134
  }
141
135
 
142
- kvToFields(kv: StringMap<any>): SlackAttachmentField[] {
136
+ kvToFields(kv: AnyObject): SlackAttachmentField[] {
143
137
  return Object.entries(kv).map(([k, v]) => ({
144
138
  title: k,
145
139
  value: String(v),
146
140
  short: String(v).length < 80,
147
141
  }))
148
142
  }
143
+
144
+ /**
145
+ * Returns a CommonLogger implementation based on this SlackService instance.
146
+ */
147
+ getCommonLogger(opt: {
148
+ minLogLevel: CommonLogLevel
149
+ logChannel?: string
150
+ warnChannel?: string
151
+ errorChannel?: string
152
+ }): CommonLogger {
153
+ const { minLogLevel = 'log', logChannel, warnChannel, errorChannel } = opt
154
+ const defaultChannel = this.cfg.defaults?.channel || DEFAULTS.channel!
155
+
156
+ const q = new PQueue({
157
+ concurrency: 1,
158
+ })
159
+
160
+ return commonLoggerMinLevel(
161
+ {
162
+ log: (...args) =>
163
+ q.push(() => this.send({ items: args, channel: logChannel || defaultChannel })),
164
+ warn: (...args) =>
165
+ q.push(() => this.send({ items: args, channel: warnChannel || defaultChannel })),
166
+ error: (...args) =>
167
+ q.push(() => this.send({ items: args, channel: errorChannel || defaultChannel })),
168
+ },
169
+ minLogLevel,
170
+ )
171
+ }
149
172
  }
150
173
 
151
174
  export function slackDefaultMessagePrefixHook(msg: SlackMessage): string[] {
@@ -21,6 +21,11 @@ export interface TransformToNDJsonOptions {
21
21
  * @default `\n`
22
22
  */
23
23
  separator?: string
24
+
25
+ /**
26
+ * @experimental
27
+ */
28
+ useFlatstr?: boolean
24
29
  }
25
30
 
26
31
  /**
@@ -29,7 +34,7 @@ export interface TransformToNDJsonOptions {
29
34
  export function transformToNDJson<IN = any>(
30
35
  opt: TransformToNDJsonOptions = {},
31
36
  ): TransformTyped<IN, string> {
32
- const { strict = true, separator = '\n', sortObjects = false } = opt
37
+ const { strict = true, separator = '\n', sortObjects = false, useFlatstr = false } = opt
33
38
 
34
39
  return new Transform({
35
40
  objectMode: true,
@@ -39,7 +44,12 @@ export function transformToNDJson<IN = any>(
39
44
  if (sortObjects) {
40
45
  chunk = _sortObjectDeep(chunk as any)
41
46
  }
42
- cb(null, JSON.stringify(chunk) + separator)
47
+
48
+ if (useFlatstr) {
49
+ cb(null, flatstr(JSON.stringify(chunk) + separator))
50
+ } else {
51
+ cb(null, JSON.stringify(chunk) + separator)
52
+ }
43
53
  } catch (err) {
44
54
  console.error(err)
45
55
 
@@ -52,3 +62,12 @@ export function transformToNDJson<IN = any>(
52
62
  },
53
63
  })
54
64
  }
65
+
66
+ /**
67
+ * Based on: https://github.com/davidmarkclements/flatstr/blob/master/index.js
68
+ */
69
+ function flatstr(s: any): string {
70
+ // eslint-disable-next-line
71
+ s | 0
72
+ return s
73
+ }
@@ -164,8 +164,7 @@ export function transformLogProgress<IN = any>(
164
164
 
165
165
  const now = Date.now()
166
166
  const batchedProgress = progress * batchSize
167
- const lastRPS =
168
- (processedLastSecond * batchedProgress) / ((now - lastSecondStarted) / 1000) || 0
167
+ const lastRPS = (processedLastSecond * batchSize) / ((now - lastSecondStarted) / 1000) || 0
169
168
  const rpsTotal = Math.round(batchedProgress / ((now - started) / 1000)) || 0
170
169
  lastSecondStarted = now
171
170
  processedLastSecond = 0
@@ -1,5 +1,5 @@
1
1
  import { inspect, InspectOptions } from 'util'
2
- import { StringifyAnyOptions, _stringifyAny } from '@naturalcycles/js-lib'
2
+ import { StringifyAnyOptions, _stringifyAny, JsonStringifyFunction } from '@naturalcycles/js-lib'
3
3
 
4
4
  export interface InspectAnyOptions extends StringifyAnyOptions, InspectOptions {}
5
5
 
@@ -8,6 +8,11 @@ const INSPECT_OPT: InspectOptions = {
8
8
  depth: 10, // default: 2
9
9
  }
10
10
 
11
+ /**
12
+ * Just a convenience export of a const that fulfills the JsonStringifyFunction interface.
13
+ */
14
+ export const inspectAnyStringifyFn: JsonStringifyFunction = obj => inspectAny(obj)
15
+
11
16
  /**
12
17
  * Transforms ANY to human-readable string (via util.inspect mainly).
13
18
  * Safe (no error throwing).
@@ -6,6 +6,7 @@ import {
6
6
  _filterNullishValues,
7
7
  _isObject,
8
8
  _substringBefore,
9
+ CommonLogger,
9
10
  } from '@naturalcycles/js-lib'
10
11
  import Ajv, { ValidateFunction } from 'ajv'
11
12
  import { inspectAny, requireFileToExist } from '../../index'
@@ -56,6 +57,11 @@ export interface AjvSchemaCfg {
56
57
  */
57
58
  logErrors: boolean
58
59
 
60
+ /**
61
+ * Default to `console`
62
+ */
63
+ logger: CommonLogger
64
+
59
65
  /**
60
66
  * Option of Ajv.
61
67
  * If set to true - will mutate your input objects!
@@ -76,6 +82,7 @@ export class AjvSchema<T = unknown> {
76
82
  private constructor(public schema: JsonSchema<T>, cfg: Partial<AjvSchemaCfg> = {}) {
77
83
  this.cfg = {
78
84
  logErrors: true,
85
+ logger: console,
79
86
  separator: '\n',
80
87
  ...cfg,
81
88
  ajv:
@@ -168,7 +175,7 @@ export class AjvSchema<T = unknown> {
168
175
  message = [message, 'Input: ' + strValue].join(separator)
169
176
 
170
177
  if (logErrors) {
171
- console.log(errors)
178
+ this.cfg.logger.error(errors)
172
179
  }
173
180
 
174
181
  return new AjvValidationError(