@allthings/cloud-toolkit 5.4.0 → 5.5.0-beta.1

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.
package/README.md CHANGED
@@ -93,6 +93,16 @@ negative — so leave it off for anything that has to arrive.
93
93
 
94
94
  A winston instance will be created with default log levels https://github.com/winstonjs/winston?tab=readme-ov-file#logging
95
95
 
96
+ With a `datadogApiKey`, entries are POSTed to the Datadog intake without waiting for the response. In a Lambda, call `flushLogger(logger)` before the handler returns. It waits until those requests are delivered (at most `timeoutMs`, default 1000) and resolves immediately when nothing is in flight. Without it, the runtime freezes with the request open, so the entry arrives on the next invocation or is lost.
97
+
98
+ ```ts
99
+ import { flushLogger, logger } from '@allthings/cloud-toolkit/logger'
100
+
101
+ const log = logger({ datadogApiKey, deploymentStage, service: 'accounts' })
102
+ // ... handle the request, log.error(...) ...
103
+ await flushLogger(log)
104
+ ```
105
+
96
106
  ### tracing
97
107
 
98
108
  OpenTelemetry tracing for Lambda with the AWS X-Ray backend (replaces the deprecated `aws-xray-sdk-*`). Import it from its own subpath — it is not part of the main entry, so services that don't trace bundle none of `@opentelemetry/*`:
package/dist/lib.cjs.js CHANGED
@@ -3,6 +3,7 @@
3
3
  var querystring = require('querystring');
4
4
  var zlib = require('zlib');
5
5
  var crypto = require('crypto');
6
+ var https = require('https');
6
7
  var winston = require('winston');
7
8
  var clientSsm = require('@aws-sdk/client-ssm');
8
9
  var clientSesv2 = require('@aws-sdk/client-sesv2');
@@ -1196,6 +1197,29 @@ const sqs = (userConfig) => {
1196
1197
  const btoa = (stringToEncode) => Buffer.from(stringToEncode).toString('base64');
1197
1198
  const atob = (stringToDecode) => Buffer.from(stringToDecode, 'base64').toString('utf8');
1198
1199
 
1200
+ const DEFAULT_FLUSH_TIMEOUT_MS = 1000;
1201
+ const datadogAgent = new https.Agent({ keepAlive: true });
1202
+ const pendingByLogger = new WeakMap();
1203
+ const trackRequests = (transport) => {
1204
+ const pending = { count: 0, waiters: new Set() };
1205
+ const send = transport.log?.bind(transport);
1206
+ const settle = () => {
1207
+ pending.count = Math.max(0, pending.count - 1);
1208
+ if (pending.count === 0) {
1209
+ for (const wake of pending.waiters) {
1210
+ wake();
1211
+ }
1212
+ pending.waiters.clear();
1213
+ }
1214
+ };
1215
+ transport.log = (info, next) => {
1216
+ pending.count += 1;
1217
+ send?.(info, next);
1218
+ };
1219
+ transport.on('logged', settle);
1220
+ transport.on('warn', settle);
1221
+ return pending;
1222
+ };
1199
1223
  const logger = (config) => {
1200
1224
  const loggerConfig = {
1201
1225
  defaultMeta: {
@@ -1209,22 +1233,42 @@ const logger = (config) => {
1209
1233
  level: config.level || 'info',
1210
1234
  };
1211
1235
  if (config.datadogApiKey) {
1212
- const httpOptions = {
1236
+ const transport = new winston.transports.Http({
1237
+ agent: datadogAgent,
1213
1238
  host: 'http-intake.logs.datadoghq.eu',
1214
1239
  path: `/api/v2/logs?dd-api-key=${config.datadogApiKey}&ddsource=nodejs&service=${config.service}`,
1215
1240
  port: 443,
1216
1241
  ssl: true,
1217
- };
1218
- return winston.createLogger({
1242
+ });
1243
+ const datadogLogger = winston.createLogger({
1219
1244
  ...loggerConfig,
1220
- transports: [new winston.transports.Http(httpOptions)],
1245
+ transports: [transport],
1221
1246
  });
1247
+ pendingByLogger.set(datadogLogger, trackRequests(transport));
1248
+ return datadogLogger;
1222
1249
  }
1223
1250
  return winston.createLogger({
1224
1251
  ...loggerConfig,
1225
1252
  transports: [new winston.transports.Console({ forceConsole: true })],
1226
1253
  });
1227
1254
  };
1255
+ const flushLogger = (loggerToFlush, timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) => {
1256
+ const pending = loggerToFlush && pendingByLogger.get(loggerToFlush);
1257
+ if (!pending || pending.count === 0) {
1258
+ return Promise.resolve();
1259
+ }
1260
+ const drained = new Promise((resolve) => {
1261
+ pending.waiters.add(resolve);
1262
+ });
1263
+ return new Promise((resolve) => {
1264
+ const timer = setTimeout(resolve, timeoutMs);
1265
+ timer.unref();
1266
+ void drained.then(() => {
1267
+ clearTimeout(timer);
1268
+ resolve();
1269
+ });
1270
+ });
1271
+ };
1228
1272
 
1229
1273
  Object.defineProperty(exports, "Logger", {
1230
1274
  enumerable: true,
@@ -1243,6 +1287,7 @@ exports.deepFlatten = deepFlatten;
1243
1287
  exports.dynamodb = dynamodb;
1244
1288
  exports.emailHeaders = emailHeaders;
1245
1289
  exports.flatten = flatten;
1290
+ exports.flushLogger = flushLogger;
1246
1291
  exports.lambda = lambda;
1247
1292
  exports.logger = logger;
1248
1293
  exports.parameterStore = parameterStore;
package/dist/lib.esm.mjs CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as querystring from 'querystring';
2
2
  import * as zlib from 'zlib';
3
3
  import * as crypto from 'crypto';
4
- import { format, createLogger, transports } from 'winston';
4
+ import { Agent } from 'https';
5
+ import { format, transports, createLogger } from 'winston';
5
6
  export { Logger } from 'winston';
6
7
  import { SSMClient, GetParametersCommand, GetParameterCommand } from '@aws-sdk/client-ssm';
7
8
  import { SESv2Client, SendEmailCommand } from '@aws-sdk/client-sesv2';
@@ -1174,6 +1175,29 @@ const sqs = (userConfig) => {
1174
1175
  const btoa = (stringToEncode) => Buffer.from(stringToEncode).toString('base64');
1175
1176
  const atob = (stringToDecode) => Buffer.from(stringToDecode, 'base64').toString('utf8');
1176
1177
 
1178
+ const DEFAULT_FLUSH_TIMEOUT_MS = 1000;
1179
+ const datadogAgent = new Agent({ keepAlive: true });
1180
+ const pendingByLogger = new WeakMap();
1181
+ const trackRequests = (transport) => {
1182
+ const pending = { count: 0, waiters: new Set() };
1183
+ const send = transport.log?.bind(transport);
1184
+ const settle = () => {
1185
+ pending.count = Math.max(0, pending.count - 1);
1186
+ if (pending.count === 0) {
1187
+ for (const wake of pending.waiters) {
1188
+ wake();
1189
+ }
1190
+ pending.waiters.clear();
1191
+ }
1192
+ };
1193
+ transport.log = (info, next) => {
1194
+ pending.count += 1;
1195
+ send?.(info, next);
1196
+ };
1197
+ transport.on('logged', settle);
1198
+ transport.on('warn', settle);
1199
+ return pending;
1200
+ };
1177
1201
  const logger = (config) => {
1178
1202
  const loggerConfig = {
1179
1203
  defaultMeta: {
@@ -1187,21 +1211,41 @@ const logger = (config) => {
1187
1211
  level: config.level || 'info',
1188
1212
  };
1189
1213
  if (config.datadogApiKey) {
1190
- const httpOptions = {
1214
+ const transport = new transports.Http({
1215
+ agent: datadogAgent,
1191
1216
  host: 'http-intake.logs.datadoghq.eu',
1192
1217
  path: `/api/v2/logs?dd-api-key=${config.datadogApiKey}&ddsource=nodejs&service=${config.service}`,
1193
1218
  port: 443,
1194
1219
  ssl: true,
1195
- };
1196
- return createLogger({
1220
+ });
1221
+ const datadogLogger = createLogger({
1197
1222
  ...loggerConfig,
1198
- transports: [new transports.Http(httpOptions)],
1223
+ transports: [transport],
1199
1224
  });
1225
+ pendingByLogger.set(datadogLogger, trackRequests(transport));
1226
+ return datadogLogger;
1200
1227
  }
1201
1228
  return createLogger({
1202
1229
  ...loggerConfig,
1203
1230
  transports: [new transports.Console({ forceConsole: true })],
1204
1231
  });
1205
1232
  };
1233
+ const flushLogger = (loggerToFlush, timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) => {
1234
+ const pending = loggerToFlush && pendingByLogger.get(loggerToFlush);
1235
+ if (!pending || pending.count === 0) {
1236
+ return Promise.resolve();
1237
+ }
1238
+ const drained = new Promise((resolve) => {
1239
+ pending.waiters.add(resolve);
1240
+ });
1241
+ return new Promise((resolve) => {
1242
+ const timer = setTimeout(resolve, timeoutMs);
1243
+ timer.unref();
1244
+ void drained.then(() => {
1245
+ clearTimeout(timer);
1246
+ resolve();
1247
+ });
1248
+ });
1249
+ };
1206
1250
 
1207
- export { AUTO_RESPONSE_SUPPRESSION_HEADERS, ClientError, DEFAULT_CLIENT_CONFIG$1 as DEFAULT_PARAMETER_STORE_CLIENT_CONFIG, DEFAULT_CLIENT_CONFIG as DEFAULT_SES_CLIENT_CONFIG, ServerError, alagarr, asyncReduce, atob, btoa, deepFlatten, dynamodb, emailHeaders, flatten, lambda, logger, parameterStore, partial, ses, splitEvery, sqs, times, until };
1251
+ export { AUTO_RESPONSE_SUPPRESSION_HEADERS, ClientError, DEFAULT_CLIENT_CONFIG$1 as DEFAULT_PARAMETER_STORE_CLIENT_CONFIG, DEFAULT_CLIENT_CONFIG as DEFAULT_SES_CLIENT_CONFIG, ServerError, alagarr, asyncReduce, atob, btoa, deepFlatten, dynamodb, emailHeaders, flatten, flushLogger, lambda, logger, parameterStore, partial, ses, splitEvery, sqs, times, until };
@@ -1,7 +1,31 @@
1
1
  'use strict';
2
2
 
3
+ var https = require('https');
3
4
  var winston = require('winston');
4
5
 
6
+ const DEFAULT_FLUSH_TIMEOUT_MS = 1000;
7
+ const datadogAgent = new https.Agent({ keepAlive: true });
8
+ const pendingByLogger = new WeakMap();
9
+ const trackRequests = (transport) => {
10
+ const pending = { count: 0, waiters: new Set() };
11
+ const send = transport.log?.bind(transport);
12
+ const settle = () => {
13
+ pending.count = Math.max(0, pending.count - 1);
14
+ if (pending.count === 0) {
15
+ for (const wake of pending.waiters) {
16
+ wake();
17
+ }
18
+ pending.waiters.clear();
19
+ }
20
+ };
21
+ transport.log = (info, next) => {
22
+ pending.count += 1;
23
+ send?.(info, next);
24
+ };
25
+ transport.on('logged', settle);
26
+ transport.on('warn', settle);
27
+ return pending;
28
+ };
5
29
  const logger = (config) => {
6
30
  const loggerConfig = {
7
31
  defaultMeta: {
@@ -15,21 +39,42 @@ const logger = (config) => {
15
39
  level: config.level || 'info',
16
40
  };
17
41
  if (config.datadogApiKey) {
18
- const httpOptions = {
42
+ const transport = new winston.transports.Http({
43
+ agent: datadogAgent,
19
44
  host: 'http-intake.logs.datadoghq.eu',
20
45
  path: `/api/v2/logs?dd-api-key=${config.datadogApiKey}&ddsource=nodejs&service=${config.service}`,
21
46
  port: 443,
22
47
  ssl: true,
23
- };
24
- return winston.createLogger({
48
+ });
49
+ const datadogLogger = winston.createLogger({
25
50
  ...loggerConfig,
26
- transports: [new winston.transports.Http(httpOptions)],
51
+ transports: [transport],
27
52
  });
53
+ pendingByLogger.set(datadogLogger, trackRequests(transport));
54
+ return datadogLogger;
28
55
  }
29
56
  return winston.createLogger({
30
57
  ...loggerConfig,
31
58
  transports: [new winston.transports.Console({ forceConsole: true })],
32
59
  });
33
60
  };
61
+ const flushLogger = (loggerToFlush, timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) => {
62
+ const pending = loggerToFlush && pendingByLogger.get(loggerToFlush);
63
+ if (!pending || pending.count === 0) {
64
+ return Promise.resolve();
65
+ }
66
+ const drained = new Promise((resolve) => {
67
+ pending.waiters.add(resolve);
68
+ });
69
+ return new Promise((resolve) => {
70
+ const timer = setTimeout(resolve, timeoutMs);
71
+ timer.unref();
72
+ void drained.then(() => {
73
+ clearTimeout(timer);
74
+ resolve();
75
+ });
76
+ });
77
+ };
34
78
 
79
+ exports.flushLogger = flushLogger;
35
80
  exports.logger = logger;
@@ -1,5 +1,29 @@
1
- import { format, createLogger, transports } from 'winston';
1
+ import { Agent } from 'https';
2
+ import { format, transports, createLogger } from 'winston';
2
3
 
4
+ const DEFAULT_FLUSH_TIMEOUT_MS = 1000;
5
+ const datadogAgent = new Agent({ keepAlive: true });
6
+ const pendingByLogger = new WeakMap();
7
+ const trackRequests = (transport) => {
8
+ const pending = { count: 0, waiters: new Set() };
9
+ const send = transport.log?.bind(transport);
10
+ const settle = () => {
11
+ pending.count = Math.max(0, pending.count - 1);
12
+ if (pending.count === 0) {
13
+ for (const wake of pending.waiters) {
14
+ wake();
15
+ }
16
+ pending.waiters.clear();
17
+ }
18
+ };
19
+ transport.log = (info, next) => {
20
+ pending.count += 1;
21
+ send?.(info, next);
22
+ };
23
+ transport.on('logged', settle);
24
+ transport.on('warn', settle);
25
+ return pending;
26
+ };
3
27
  const logger = (config) => {
4
28
  const loggerConfig = {
5
29
  defaultMeta: {
@@ -13,21 +37,41 @@ const logger = (config) => {
13
37
  level: config.level || 'info',
14
38
  };
15
39
  if (config.datadogApiKey) {
16
- const httpOptions = {
40
+ const transport = new transports.Http({
41
+ agent: datadogAgent,
17
42
  host: 'http-intake.logs.datadoghq.eu',
18
43
  path: `/api/v2/logs?dd-api-key=${config.datadogApiKey}&ddsource=nodejs&service=${config.service}`,
19
44
  port: 443,
20
45
  ssl: true,
21
- };
22
- return createLogger({
46
+ });
47
+ const datadogLogger = createLogger({
23
48
  ...loggerConfig,
24
- transports: [new transports.Http(httpOptions)],
49
+ transports: [transport],
25
50
  });
51
+ pendingByLogger.set(datadogLogger, trackRequests(transport));
52
+ return datadogLogger;
26
53
  }
27
54
  return createLogger({
28
55
  ...loggerConfig,
29
56
  transports: [new transports.Console({ forceConsole: true })],
30
57
  });
31
58
  };
59
+ const flushLogger = (loggerToFlush, timeoutMs = DEFAULT_FLUSH_TIMEOUT_MS) => {
60
+ const pending = loggerToFlush && pendingByLogger.get(loggerToFlush);
61
+ if (!pending || pending.count === 0) {
62
+ return Promise.resolve();
63
+ }
64
+ const drained = new Promise((resolve) => {
65
+ pending.waiters.add(resolve);
66
+ });
67
+ return new Promise((resolve) => {
68
+ const timer = setTimeout(resolve, timeoutMs);
69
+ timer.unref();
70
+ void drained.then(() => {
71
+ clearTimeout(timer);
72
+ resolve();
73
+ });
74
+ });
75
+ };
32
76
 
33
- export { logger };
77
+ export { flushLogger, logger };
@@ -1,7 +1,7 @@
1
1
  export { Alagarr, alagarr, ClientError, HandlerFunction, IInterfaceAlagarrOptions, IInterfaceHeaders, IInterfaceQueryParameters, IInterfaceRequest, IInterfaceResponse, ServerError, } from './alagarr';
2
2
  export { AUTO_RESPONSE_SUPPRESSION_HEADERS, DEFAULT_PARAMETER_STORE_CLIENT_CONFIG, DEFAULT_SES_CLIENT_CONFIG, dynamodb, emailHeaders, IDynamoDBConfig, IDynamoDBInstance, IEmailAttachment, IEmailHeaderOptions, IEmailOptions, ILambda, ILambdaInstance, IParameterStore, IParameterStoreInstance, ISES, ISESInstance, ISQS, ISQSInstance, IUnprocessedItems, lambda, parameterStore, ses, sqs, TBatchWriteItems, TDeleteItem, TDeleteQueueItem, TGetItem, TGetQueueItems, TGetQueueUrl, TGetSecret, TGetSecrets, TInvokeLambda, TPutItem, TQueryTable, TScanTable, TSendMail, TSendMessage, TUpdateItem, } from './aws';
3
3
  export { atob, btoa } from './base64';
4
- export { logger } from './logger/logger';
4
+ export { flushLogger, logger } from './logger/logger';
5
5
  export { asyncReduce, deepFlatten, flatten, List, partial, splitEvery, times, until, } from './utils';
6
6
  export type { APIGatewayEvent, APIGatewayProxyEventV2, Context, } from 'aws-lambda';
7
7
  export { Logger } from 'winston';
@@ -7,4 +7,5 @@ interface ILoggerConfig {
7
7
  service: string;
8
8
  }
9
9
  export declare const logger: (config: ILoggerConfig) => Logger;
10
+ export declare const flushLogger: (loggerToFlush: Logger | undefined, timeoutMs?: number) => Promise<void>;
10
11
  export type { Logger } from 'winston';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allthings/cloud-toolkit",
3
- "version": "5.4.0",
3
+ "version": "5.5.0-beta.1",
4
4
  "description": "",
5
5
  "author": "Allthings GmbH",
6
6
  "keywords": [
@@ -180,21 +180,21 @@
180
180
  "@commitlint/config-conventional": "21.2.3",
181
181
  "@opentelemetry/sdk-trace-base": "2.12.0",
182
182
  "@rollup/plugin-node-resolve": "16.0.3",
183
- "@types/node": "24.19.1",
183
+ "@types/node": "24.19.2",
184
184
  "@types/nodemailer": "8.0.2",
185
- "@vitest/coverage-v8": "5.0.3",
185
+ "@vitest/coverage-v8": "5.0.5",
186
186
  "aws-sdk-client-mock": "4.1.0",
187
187
  "conventional-changelog-conventionalcommits": "9.3.1",
188
- "eslint": "10.12.0",
188
+ "eslint": "10.13.0",
189
189
  "husky": "9.1.7",
190
190
  "lint-staged": "17.6.0",
191
- "prettier": "3.9.9",
191
+ "prettier": "3.9.10",
192
192
  "rimraf": "6.1.3",
193
- "rollup": "4.64.0",
193
+ "rollup": "4.64.8",
194
194
  "semantic-release": "25.0.9",
195
195
  "typescript": "6.0.3",
196
- "vite": "8.3.2",
197
- "vitest": "5.0.3"
196
+ "vite": "8.3.4",
197
+ "vitest": "5.0.5"
198
198
  },
199
199
  "prettier": {
200
200
  "printWidth": 80,