@palmetto/pubsub 2.0.0 → 2.1.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.
package/README.md CHANGED
@@ -49,6 +49,7 @@ yarn add @palmetto/pubsub zod
49
49
  schema: TestModelSchema,
50
50
  transport: RABBITMQ_TRANSPORT,
51
51
  exchangeType: "topic",
52
+ messageLogLevel: "info",
52
53
  };
53
54
 
54
55
  const publisher = new Publisher(console, [rabbitMqPublisher]);
@@ -92,6 +93,7 @@ yarn add @palmetto/pubsub zod
92
93
  retries: 20,
93
94
  transport: RABBITMQ_TRANSPORT,
94
95
  exchangeType: "fanout",
96
+ messageLogLevel: "debug",
95
97
  };
96
98
 
97
99
  function onMessage(message: Model) {
@@ -106,6 +108,18 @@ yarn add @palmetto/pubsub zod
106
108
  await subscriber.startSubscribe(schemaConfig, onMessage);
107
109
  ```
108
110
 
111
+ ### Message logging
112
+
113
+ For each message you can indicate how to log messages when they are published or handled by a subscriber. Use the `messageLogLevel` property of the queue configuration object.
114
+
115
+ Valid values are:
116
+
117
+ - off
118
+ - debug (default)
119
+ - info
120
+ - warn
121
+ - error
122
+
109
123
  ### RabbitMQ Usage
110
124
 
111
125
  For specific details about using RabbitMQ see [RabbitMQ README.md](src/rabbitmq/README.md)
@@ -11,4 +11,5 @@ export declare class BullMqPublisher implements PublisherProvider {
11
11
  publish(config: BullMqQueueConfiguration, message: string): Promise<void>;
12
12
  init(config: BullMqQueueConfiguration): Promise<void>;
13
13
  close(): Promise<void>;
14
+ enrichPublishedMesssageLog(config: BullMqQueueConfiguration): Record<string, unknown>;
14
15
  }
@@ -63,5 +63,10 @@ class BullMqPublisher {
63
63
  this.queues.clear();
64
64
  });
65
65
  }
66
+ enrichPublishedMesssageLog(config) {
67
+ return {
68
+ job: config.job,
69
+ };
70
+ }
66
71
  }
67
72
  exports.BullMqPublisher = BullMqPublisher;
@@ -10,4 +10,6 @@ export declare class BullMqPubSubProvider implements PubSubProvider {
10
10
  startSubscribe(config: BullMqQueueConfiguration, onMessage: (s: string, context: MessageContext) => Promise<MessageResult> | MessageResult): Promise<StopSubscribe>;
11
11
  close(): Promise<void>;
12
12
  init(config: BullMqQueueConfiguration): Promise<void>;
13
+ enrichPublishedMesssageLog(config: BullMqQueueConfiguration): Record<string, unknown>;
14
+ enrichHandledMesssageLog(config: BullMqQueueConfiguration): Record<string, unknown>;
13
15
  }
@@ -36,5 +36,11 @@ class BullMqPubSubProvider {
36
36
  yield this.publisher.init(config);
37
37
  });
38
38
  }
39
+ enrichPublishedMesssageLog(config) {
40
+ return this.publisher.enrichPublishedMesssageLog(config);
41
+ }
42
+ enrichHandledMesssageLog(config) {
43
+ return this.subscriber.enrichHandledMesssageLog(config);
44
+ }
39
45
  }
40
46
  exports.BullMqPubSubProvider = BullMqPubSubProvider;
@@ -23,5 +23,6 @@ export declare class BullMqSubscriber implements SubscriberProvider {
23
23
  removeSubscriber(subscribedMessage: SubscribedMessage): void;
24
24
  startSubscribe(config: BullMqQueueConfiguration, onMessage: (s: string, context: BullMqMessageContext) => Promise<MessageResult> | MessageResult): Promise<StopSubscribe>;
25
25
  close(): Promise<void>;
26
+ enrichHandledMesssageLog(config: BullMqQueueConfiguration): Record<string, unknown>;
26
27
  }
27
28
  export {};
@@ -118,5 +118,10 @@ class BullMqSubscriber {
118
118
  this.stops.clear();
119
119
  });
120
120
  }
121
+ enrichHandledMesssageLog(config) {
122
+ return {
123
+ job: config.job,
124
+ };
125
+ }
121
126
  }
122
127
  exports.BullMqSubscriber = BullMqSubscriber;
@@ -1,8 +1,2 @@
1
- export interface LogErrorPayload {
2
- message: string;
3
- kind: string;
4
- stack?: string;
5
- cause?: unknown;
6
- statusCode?: number;
7
- }
1
+ import { LogErrorPayload } from "./log";
8
2
  export declare function createLogErrorPayload(error: unknown): LogErrorPayload;
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- /* compare to code in @palmetto/nestjs-logger */
2
+ /* copied from @palmetto/nestjs-logger */
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.createLogErrorPayload = createLogErrorPayload;
5
5
  function createLogErrorPayload(error) {
@@ -25,7 +25,9 @@ export interface PublisherProvider {
25
25
  init(config: PubSubConfiguration): Promise<void> | void;
26
26
  publish(config: PubSubConfiguration, message: string): Promise<void> | void;
27
27
  close(): Promise<void> | void;
28
+ enrichPublishedMesssageLog(config: PubSubConfiguration): Record<string, unknown>;
28
29
  }
30
+ export type MessageLogLevel = "error" | "warn" | "info" | "debug" | "off";
29
31
  /**
30
32
  * Minimal configuration needed to publish or subscribe to a message
31
33
  */
@@ -50,6 +52,10 @@ export interface PubSubConfiguration {
50
52
  * Minimum amount of time to wait, in milliseconds, before retrying a message
51
53
  */
52
54
  retryDelay?: number;
55
+ /**
56
+ * log level to use when logging message payloads (default: debug)
57
+ */
58
+ messageLogLevel?: MessageLogLevel;
53
59
  }
54
60
  export declare enum MessageResult {
55
61
  /**
@@ -105,6 +111,7 @@ export interface SubscriberProvider {
105
111
  transport: string;
106
112
  startSubscribe(config: PubSubConfiguration, onMessage: (s: string, context: MessageContext) => Promise<MessageResult> | MessageResult): Promise<StopSubscribe> | StopSubscribe;
107
113
  close(): Promise<void> | void;
114
+ enrichHandledMesssageLog(config: PubSubConfiguration): Record<string, unknown>;
108
115
  }
109
116
  /**
110
117
  * A class that implements both the publisher and subscriber
package/dist/log.d.ts ADDED
@@ -0,0 +1,37 @@
1
+ /** copied from @nestjs/logger */
2
+ export type Log<TRequiredKeys extends "http" | "error" | "message" | "extra" = "message"> = Omit<LogPayload, TRequiredKeys> & Required<Pick<LogPayload, TRequiredKeys>>;
3
+ export interface LogPayload {
4
+ message: string;
5
+ error?: LogErrorPayload;
6
+ http?: LogHttpPayload;
7
+ identity?: LogIdentityPayload;
8
+ extra?: Record<string, unknown>;
9
+ }
10
+ export interface LogErrorPayload {
11
+ message: string;
12
+ kind: string;
13
+ stack?: string;
14
+ cause?: unknown;
15
+ statusCode?: number;
16
+ }
17
+ export interface LogHttpPayload {
18
+ method: string;
19
+ status_code: number;
20
+ url?: string;
21
+ route?: string;
22
+ client_ip?: string;
23
+ useragent?: string;
24
+ referer?: string;
25
+ request?: {
26
+ headers?: Record<string, string>;
27
+ content_length?: number;
28
+ };
29
+ response?: {
30
+ headers?: Record<string, string>;
31
+ content_length?: number;
32
+ };
33
+ }
34
+ export interface LogIdentityPayload {
35
+ userId: string;
36
+ organizationId: string;
37
+ }
package/dist/log.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,10 @@
1
+ import { Logger, MessageLogLevel } from "./interfaces";
2
+ export declare function startTiming(): [number, number];
3
+ export declare function getDuration(start: [number, number]): number;
4
+ export declare function logMessage({ note, message, logger, level, extra, }: {
5
+ note: string;
6
+ message: unknown;
7
+ logger: Logger;
8
+ level?: MessageLogLevel;
9
+ extra?: Record<string, unknown>;
10
+ }): void;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startTiming = startTiming;
4
+ exports.getDuration = getDuration;
5
+ exports.logMessage = logMessage;
6
+ function startTiming() {
7
+ return process.hrtime();
8
+ }
9
+ function getDuration(start) {
10
+ return process.hrtime(start)[1] / 1000000; // convert to milliseconds
11
+ }
12
+ function logMessage({ note, message, logger, level = "debug", extra, }) {
13
+ var _a;
14
+ if (level === "off") {
15
+ return;
16
+ }
17
+ const logFn = level === "info" ? logger.log.bind(logger) : (_a = logger.debug) === null || _a === void 0 ? void 0 : _a.bind(logger);
18
+ if (!logFn) {
19
+ return;
20
+ }
21
+ const msg = {
22
+ message: note,
23
+ extra: Object.assign(Object.assign({}, extra), { message }),
24
+ };
25
+ logFn(msg);
26
+ }
package/dist/publisher.js CHANGED
@@ -14,6 +14,7 @@ const zod_1 = require("zod");
14
14
  const uuid_1 = require("uuid");
15
15
  const crypto_1 = require("crypto");
16
16
  const errors_js_1 = require("./errors.js");
17
+ const message_logger_js_1 = require("./message-logger.js");
17
18
  class Publisher {
18
19
  constructor(logger, providers) {
19
20
  var _a, _b;
@@ -52,7 +53,6 @@ class Publisher {
52
53
  }
53
54
  publish(config, message) {
54
55
  return __awaiter(this, void 0, void 0, function* () {
55
- var _a, _b;
56
56
  const provider = this.getProvider(config);
57
57
  if (!message.id) {
58
58
  message.id = (0, uuid_1.v4)();
@@ -70,11 +70,26 @@ class Publisher {
70
70
  }
71
71
  const check = schema.safeEncode(message);
72
72
  if (!check.success) {
73
+ (0, message_logger_js_1.logMessage)({
74
+ note: "Publish message failed schema validation",
75
+ message: message,
76
+ level: "error",
77
+ logger: this.logger,
78
+ extra: Object.assign({ transport: provider.transport, name: config.name }, provider.enrichPublishedMesssageLog(config)),
79
+ });
73
80
  throw new errors_js_1.SchemaValidationError(`Schema did not accept the published message: ${check.error.message}`);
74
81
  }
75
- (_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `Publisher publishing message for ${provider.transport}`);
76
82
  const json = JSON.stringify(check.data);
83
+ const start = (0, message_logger_js_1.startTiming)();
77
84
  yield provider.publish(config, json);
85
+ const duration = (0, message_logger_js_1.getDuration)(start);
86
+ (0, message_logger_js_1.logMessage)({
87
+ note: "Published message",
88
+ message: check.data,
89
+ level: config.messageLogLevel,
90
+ logger: this.logger,
91
+ extra: Object.assign({ transport: provider.transport, name: config.name, durationMs: duration }, provider.enrichPublishedMesssageLog(config)),
92
+ });
78
93
  });
79
94
  }
80
95
  close() {
@@ -25,6 +25,7 @@ export declare class RabbitMqPublisher implements PublisherProvider {
25
25
  * @returns A promise that is completed when the message is published
26
26
  */
27
27
  publish(config: RabbitQueueExchangeConfiguration, message: string): Promise<void>;
28
+ enrichPublishedMesssageLog(config: RabbitQueueExchangeConfiguration): Record<string, unknown>;
28
29
  private publishToExchange;
29
30
  private publishToQueue;
30
31
  close(): Promise<void> | undefined;
@@ -89,17 +89,25 @@ class RabbitMqPublisher {
89
89
  }
90
90
  });
91
91
  }
92
+ enrichPublishedMesssageLog(config) {
93
+ if (config.publishToSpecificQueue) {
94
+ return {
95
+ publishToSpecificQueue: config.publishToSpecificQueue,
96
+ };
97
+ }
98
+ return {
99
+ exchangeName: (0, config_js_1.getExchangeName)(config),
100
+ routingKey: (0, config_js_1.getRoutingKey)(config),
101
+ };
102
+ }
92
103
  publishToExchange(channel, config, message) {
93
104
  return __awaiter(this, void 0, void 0, function* () {
94
- var _a, _b, _c, _d;
95
105
  const exchangeName = (0, config_js_1.getExchangeName)(config);
96
- (_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `Publishing message to ${exchangeName} - ${message} [starting]`);
97
106
  const ok = yield channel.publish(exchangeName, (0, config_js_1.getRoutingKey)(config), Buffer.from(message, "utf8"), {
98
107
  contentType: "application/json",
99
108
  timestamp: new Date().valueOf(),
100
109
  persistent: true,
101
110
  });
102
- (_d = (_c = this.logger).debug) === null || _d === void 0 ? void 0 : _d.call(_c, `Published message to ${exchangeName} - ${message} [${ok}]`);
103
111
  if (!ok) {
104
112
  throw new errors_js_1.PublishError(`RabbitMq publish to ${exchangeName} failed`);
105
113
  }
@@ -107,14 +115,11 @@ class RabbitMqPublisher {
107
115
  }
108
116
  publishToQueue(channel, queue, message) {
109
117
  return __awaiter(this, void 0, void 0, function* () {
110
- var _a, _b, _c, _d;
111
- (_b = (_a = this.logger).debug) === null || _b === void 0 ? void 0 : _b.call(_a, `Publishing message to queue:${queue} - ${message} [starting]`);
112
118
  const ok = yield channel.sendToQueue(queue, Buffer.from(message, "utf8"), {
113
119
  contentType: "application/json",
114
120
  timestamp: new Date().valueOf(),
115
121
  persistent: true,
116
122
  });
117
- (_d = (_c = this.logger).debug) === null || _d === void 0 ? void 0 : _d.call(_c, `Published message to queue:${queue} - ${message} [${ok}]`);
118
123
  if (!ok) {
119
124
  throw new errors_js_1.PublishError(`RabbitMq publish to queue:${queue} failed`);
120
125
  }
@@ -13,4 +13,6 @@ export declare class RabbitMqPubSubProvider implements PubSubProvider {
13
13
  startSubscribe(config: RabbitQueueExchangeConfiguration, onMessage: (s: string, context: MessageContext) => Promise<MessageResult> | MessageResult): StopSubscribe;
14
14
  close(): Promise<void>;
15
15
  init(config: RabbitQueueExchangeConfiguration): Promise<void>;
16
+ enrichPublishedMesssageLog(config: RabbitQueueExchangeConfiguration): Record<string, unknown>;
17
+ enrichHandledMesssageLog(config: RabbitQueueExchangeConfiguration): Record<string, unknown>;
16
18
  }
@@ -38,5 +38,11 @@ class RabbitMqPubSubProvider {
38
38
  yield this.publisher.init(config);
39
39
  });
40
40
  }
41
+ enrichPublishedMesssageLog(config) {
42
+ return this.publisher.enrichPublishedMesssageLog(config);
43
+ }
44
+ enrichHandledMesssageLog(config) {
45
+ return this.subscriber.enrichHandledMesssageLog(config);
46
+ }
41
47
  }
42
48
  exports.RabbitMqPubSubProvider = RabbitMqPubSubProvider;
@@ -23,6 +23,7 @@ export declare class RabbitMqSubscriber implements SubscriberProvider {
23
23
  removeSubscriber(subscribedMessage: SubscribedMessage): void;
24
24
  startSubscribe(config: RabbitQueueExchangeConfiguration, onMessage: (s: string, context: RabbitMqMessageContext) => Promise<MessageResult> | MessageResult): StopSubscribe;
25
25
  close(): Promise<void>;
26
+ enrichHandledMesssageLog(config: RabbitQueueExchangeConfiguration): Record<string, unknown>;
26
27
  static getRetries(msg: ConsumeMessage): number;
27
28
  static getSentDates(msg: ConsumeMessage): {
28
29
  firstSent: Date | undefined;
@@ -89,6 +89,11 @@ class RabbitMqSubscriber {
89
89
  this.subscribers.clear();
90
90
  });
91
91
  }
92
+ enrichHandledMesssageLog(config) {
93
+ return {
94
+ queueName: (0, config_js_1.getQueueName)(config),
95
+ };
96
+ }
92
97
  static getRetries(msg) {
93
98
  var _a;
94
99
  return typeof ((_a = msg.properties.headers) === null || _a === void 0 ? void 0 : _a[RETRIES_HEADER]) === "number"
@@ -13,6 +13,8 @@ exports.Subscriber = void 0;
13
13
  const interfaces_js_1 = require("./interfaces.js");
14
14
  const errors_js_1 = require("./errors.js");
15
15
  const events_1 = require("events");
16
+ const message_logger_js_1 = require("./message-logger.js");
17
+ const create_log_error_payload_js_1 = require("./create-log-error-payload.js");
16
18
  class SubscribedMessage {
17
19
  constructor(stop) {
18
20
  this.stop = stop;
@@ -54,23 +56,23 @@ class Subscriber {
54
56
  throw new errors_js_1.MissingPubSubProviderError(`No provider configured for ${config.transport}`);
55
57
  }
56
58
  const transform = (jsonStr, context) => __awaiter(this, void 0, void 0, function* () {
57
- var _a;
58
- const start = new Date().valueOf();
59
+ const start = (0, message_logger_js_1.startTiming)();
59
60
  const jsonObject = JSON.parse(jsonStr);
60
61
  const r = schema.safeDecode(jsonObject);
61
62
  if (!r.success) {
62
- const id = interfaces_js_1.IdMetaSchema.safeParse(jsonObject);
63
- if (id.success) {
64
- this.logger.warn(`Unable to parse message ${id.data.id} created at ${(_a = id.data.meta) === null || _a === void 0 ? void 0 : _a.createdAt} for ${config.transport}:${config.name} - schemaError`);
65
- }
66
- else {
67
- this.logger.warn(`Unable to parse message for ${config.transport}:${config.name} - schemaError`);
68
- }
63
+ const durationMs = (0, message_logger_js_1.getDuration)(start);
64
+ (0, message_logger_js_1.logMessage)({
65
+ note: "Subscribed message failed schema validation",
66
+ message: jsonObject,
67
+ level: "error",
68
+ logger: this.logger,
69
+ extra: Object.assign({ transport: provider.transport, name: config.name, durationMs }, provider.enrichHandledMesssageLog(config)),
70
+ });
69
71
  const handledEventContext = {
70
72
  message: jsonStr,
71
73
  context,
72
74
  config,
73
- durationMs: new Date().valueOf() - start,
75
+ durationMs,
74
76
  result: interfaces_js_1.MessageResult.Fail,
75
77
  };
76
78
  this.events.emit("schemaError", handledEventContext);
@@ -79,22 +81,38 @@ class Subscriber {
79
81
  }
80
82
  try {
81
83
  const result = yield onMessage(r.data, context);
84
+ const durationMs = (0, message_logger_js_1.getDuration)(start);
82
85
  const eventContext = {
83
86
  message: jsonStr,
84
87
  context,
85
88
  config,
86
- durationMs: new Date().valueOf() - start,
89
+ durationMs,
87
90
  result,
88
91
  };
92
+ (0, message_logger_js_1.logMessage)({
93
+ note: "Subscriber processed message",
94
+ message: r.data,
95
+ level: config.messageLogLevel,
96
+ logger: this.logger,
97
+ extra: Object.assign({ transport: provider.transport, name: config.name, durationMs, result: result.toString() }, provider.enrichHandledMesssageLog(config)),
98
+ });
89
99
  this.events.emit("messageHandled", eventContext);
90
100
  return result;
91
101
  }
92
102
  catch (err) {
103
+ const durationMs = (0, message_logger_js_1.getDuration)(start);
104
+ (0, message_logger_js_1.logMessage)({
105
+ note: "Subscriber error when processing message",
106
+ message: r.data,
107
+ level: "error",
108
+ logger: this.logger,
109
+ extra: Object.assign(Object.assign({ transport: provider.transport, name: config.name, durationMs }, provider.enrichHandledMesssageLog(config)), { error: (0, create_log_error_payload_js_1.createLogErrorPayload)(err) }),
110
+ });
93
111
  const eventContext = {
94
112
  message: jsonStr,
95
113
  context,
96
114
  config,
97
- durationMs: new Date().valueOf() - start,
115
+ durationMs,
98
116
  err,
99
117
  };
100
118
  this.events.emit("messageHandled", eventContext);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palmetto/pubsub",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "main": "./dist/main.js",
5
5
  "scripts": {
6
6
  "lint": "yarn run -T eslint --fix ./src",