@openfn/ws-worker 1.12.0 → 1.13.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/CHANGELOG.md +23 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +214 -97
- package/dist/start.js +225 -97
- package/package.json +7 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# ws-worker
|
|
2
2
|
|
|
3
|
+
## 1.13.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ce5022a: Added sentry notifications for server and websocket errors
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 0a176aa: Ignore empty log lines (don't send them to lightning)
|
|
12
|
+
- Updated dependencies [0a176aa]
|
|
13
|
+
- @openfn/logger@1.0.5
|
|
14
|
+
- @openfn/engine-multi@1.6.2
|
|
15
|
+
- @openfn/lexicon@1.2.0
|
|
16
|
+
- @openfn/runtime@1.6.4
|
|
17
|
+
|
|
18
|
+
## 1.12.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- e2f1197: Better logging on credential errors
|
|
23
|
+
- Updated dependencies [e2f1197]
|
|
24
|
+
- @openfn/engine-multi@1.6.1
|
|
25
|
+
|
|
3
26
|
## 1.12.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
package/dist/index.d.ts
CHANGED
|
@@ -53,6 +53,7 @@ type WorkerRunOptions = ExecuteOptions & {
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
type Context = {
|
|
56
|
+
id: string;
|
|
56
57
|
channel: Channel;
|
|
57
58
|
state: RunState;
|
|
58
59
|
logger: Logger;
|
|
@@ -73,6 +74,8 @@ type ServerOptions = {
|
|
|
73
74
|
min?: number;
|
|
74
75
|
max?: number;
|
|
75
76
|
};
|
|
77
|
+
sentryDsn?: string;
|
|
78
|
+
sentryEnv?: string;
|
|
76
79
|
socketTimeoutSeconds?: number;
|
|
77
80
|
payloadLimitMb?: number;
|
|
78
81
|
collectionsVersion?: string;
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { exec as _exec } from "node:child_process";
|
|
5
|
+
import * as Sentry5 from "@sentry/node";
|
|
5
6
|
import Koa from "koa";
|
|
6
7
|
import bodyParser from "koa-bodyparser";
|
|
7
8
|
import koaLogger from "koa-logger";
|
|
@@ -210,6 +211,9 @@ var startWorkloop = (app, logger, minBackoff, maxBackoff, maxWorkers) => {
|
|
|
210
211
|
};
|
|
211
212
|
var workloop_default = startWorkloop;
|
|
212
213
|
|
|
214
|
+
// src/api/execute.ts
|
|
215
|
+
import * as Sentry2 from "@sentry/node";
|
|
216
|
+
|
|
213
217
|
// src/util/convert-lightning-plan.ts
|
|
214
218
|
import crypto2 from "node:crypto";
|
|
215
219
|
import path from "node:path";
|
|
@@ -387,26 +391,6 @@ var convert_lightning_plan_default = (run, options = {}) => {
|
|
|
387
391
|
};
|
|
388
392
|
};
|
|
389
393
|
|
|
390
|
-
// src/util/get-with-reply.ts
|
|
391
|
-
var get_with_reply_default = (channel, event, payload) => new Promise((resolve, reject) => {
|
|
392
|
-
channel.push(event, payload).receive("ok", (evt) => {
|
|
393
|
-
resolve(evt);
|
|
394
|
-
}).receive("error", (e) => {
|
|
395
|
-
reject(e);
|
|
396
|
-
}).receive("timeout", (e) => {
|
|
397
|
-
reject(e);
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
// src/util/stringify.ts
|
|
402
|
-
import stringify from "fast-safe-stringify";
|
|
403
|
-
var stringify_default = (obj) => stringify(obj, (_key, value) => {
|
|
404
|
-
if (value instanceof Uint8Array) {
|
|
405
|
-
return Array.from(value);
|
|
406
|
-
}
|
|
407
|
-
return value;
|
|
408
|
-
});
|
|
409
|
-
|
|
410
394
|
// src/util/create-run-state.ts
|
|
411
395
|
var create_run_state_default = (plan, input) => {
|
|
412
396
|
const state = {
|
|
@@ -440,6 +424,67 @@ var create_run_state_default = (plan, input) => {
|
|
|
440
424
|
return state;
|
|
441
425
|
};
|
|
442
426
|
|
|
427
|
+
// src/util/send-event.ts
|
|
428
|
+
import * as Sentry from "@sentry/node";
|
|
429
|
+
|
|
430
|
+
// src/errors.ts
|
|
431
|
+
var LightningSocketError = class extends Error {
|
|
432
|
+
constructor(event, message) {
|
|
433
|
+
super(`[${event}] ${message}`);
|
|
434
|
+
this.name = "LightningSocketError";
|
|
435
|
+
this.event = "";
|
|
436
|
+
this.rejectMessage = "";
|
|
437
|
+
this.event = event;
|
|
438
|
+
this.rejectMessage = message;
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
var LightningTimeoutError = class extends Error {
|
|
442
|
+
constructor(event) {
|
|
443
|
+
super(`[${event}] timeout`);
|
|
444
|
+
this.name = "LightningTimeoutError";
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// src/util/send-event.ts
|
|
449
|
+
var sendEvent = (context, event, payload) => {
|
|
450
|
+
const { channel, logger, id: runId = "<unknown run>" } = context;
|
|
451
|
+
return new Promise((resolve, reject) => {
|
|
452
|
+
const report = (error) => {
|
|
453
|
+
logger.error(`${runId} :: ${event} :: ERR: ${error.message || error}`);
|
|
454
|
+
const context2 = {
|
|
455
|
+
run_id: runId,
|
|
456
|
+
event
|
|
457
|
+
};
|
|
458
|
+
const extras = {};
|
|
459
|
+
if (error.rejectMessage) {
|
|
460
|
+
extras.rejection_reason = error.rejectMessage;
|
|
461
|
+
}
|
|
462
|
+
Sentry.captureException(error, (scope) => {
|
|
463
|
+
scope.setContext("run", context2);
|
|
464
|
+
scope.setExtras(extras);
|
|
465
|
+
return scope;
|
|
466
|
+
});
|
|
467
|
+
error.reportedToSentry = true;
|
|
468
|
+
reject(error);
|
|
469
|
+
};
|
|
470
|
+
channel.push(event, payload).receive("error", (message) => {
|
|
471
|
+
report(new LightningSocketError(event, message));
|
|
472
|
+
}).receive("timeout", () => {
|
|
473
|
+
report(new LightningTimeoutError(event));
|
|
474
|
+
}).receive("ok", resolve);
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
var send_event_default = sendEvent;
|
|
478
|
+
|
|
479
|
+
// src/util/stringify.ts
|
|
480
|
+
import stringify from "fast-safe-stringify";
|
|
481
|
+
var stringify_default = (obj) => stringify(obj, (_key, value) => {
|
|
482
|
+
if (value instanceof Uint8Array) {
|
|
483
|
+
return Array.from(value);
|
|
484
|
+
}
|
|
485
|
+
return value;
|
|
486
|
+
});
|
|
487
|
+
|
|
443
488
|
// src/util/throttle.ts
|
|
444
489
|
var createThrottler = () => {
|
|
445
490
|
const q = [];
|
|
@@ -525,7 +570,7 @@ function getVersion() {
|
|
|
525
570
|
|
|
526
571
|
// src/events/run-start.ts
|
|
527
572
|
async function onRunStart(context, event) {
|
|
528
|
-
const {
|
|
573
|
+
const { state, options = {} } = context;
|
|
529
574
|
const time = (timestamp() - BigInt(1e7)).toString();
|
|
530
575
|
const versionLogContext = {
|
|
531
576
|
...context,
|
|
@@ -538,7 +583,7 @@ async function onRunStart(context, event) {
|
|
|
538
583
|
worker: await getVersion(),
|
|
539
584
|
...event.versions
|
|
540
585
|
};
|
|
541
|
-
await sendEvent(
|
|
586
|
+
await sendEvent(context, RUN_START, {
|
|
542
587
|
versions,
|
|
543
588
|
/// use the engine time in run start
|
|
544
589
|
timestamp: timeInMicroseconds(event.time)
|
|
@@ -600,7 +645,7 @@ var calculateRunExitReason = (state) => {
|
|
|
600
645
|
|
|
601
646
|
// src/events/step-complete.ts
|
|
602
647
|
async function onStepComplete(context, event, error) {
|
|
603
|
-
const {
|
|
648
|
+
const { state, options } = context;
|
|
604
649
|
const dataclipId = crypto3.randomUUID();
|
|
605
650
|
const step_id = state.activeStep;
|
|
606
651
|
const job_id = state.activeJob;
|
|
@@ -645,13 +690,13 @@ async function onStepComplete(context, event, error) {
|
|
|
645
690
|
const reason = calculateJobExitReason(job_id, event.state, error);
|
|
646
691
|
state.reasons[job_id] = reason;
|
|
647
692
|
Object.assign(evt, reason);
|
|
648
|
-
return sendEvent(
|
|
693
|
+
return sendEvent(context, STEP_COMPLETE, evt);
|
|
649
694
|
}
|
|
650
695
|
|
|
651
696
|
// src/events/step-start.ts
|
|
652
697
|
import crypto4 from "node:crypto";
|
|
653
698
|
async function onStepStart(context, event) {
|
|
654
|
-
const {
|
|
699
|
+
const { state } = context;
|
|
655
700
|
state.activeStep = crypto4.randomUUID();
|
|
656
701
|
state.activeJob = event.jobId;
|
|
657
702
|
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
@@ -663,7 +708,7 @@ async function onStepStart(context, event) {
|
|
|
663
708
|
if (!state.withheldDataclips[input_dataclip_id]) {
|
|
664
709
|
evt.input_dataclip_id = input_dataclip_id;
|
|
665
710
|
}
|
|
666
|
-
await sendEvent(
|
|
711
|
+
await sendEvent(context, STEP_START, evt);
|
|
667
712
|
}
|
|
668
713
|
|
|
669
714
|
// src/util/log-final-reason.ts
|
|
@@ -685,12 +730,12 @@ ${reason.error_type}: ${reason.error_message || "unknown"}`;
|
|
|
685
730
|
|
|
686
731
|
// src/events/run-complete.ts
|
|
687
732
|
async function onWorkflowComplete(context, event) {
|
|
688
|
-
const { state,
|
|
733
|
+
const { state, onFinish, logger } = context;
|
|
689
734
|
const result = state.dataclips[state.lastDataclipId];
|
|
690
735
|
const reason = calculateRunExitReason(state);
|
|
691
736
|
await log_final_reason_default(context, reason);
|
|
692
737
|
try {
|
|
693
|
-
await sendEvent(
|
|
738
|
+
await sendEvent(context, RUN_COMPLETE, {
|
|
694
739
|
final_dataclip_id: state.lastDataclipId,
|
|
695
740
|
timestamp: timeInMicroseconds(event.time),
|
|
696
741
|
...reason
|
|
@@ -706,14 +751,14 @@ async function onWorkflowComplete(context, event) {
|
|
|
706
751
|
|
|
707
752
|
// src/events/run-error.ts
|
|
708
753
|
async function onRunError(context, event) {
|
|
709
|
-
const { state,
|
|
754
|
+
const { state, logger, onFinish } = context;
|
|
710
755
|
try {
|
|
711
756
|
const reason = calculateJobExitReason("", { data: {} }, event);
|
|
712
757
|
if (state.activeJob) {
|
|
713
758
|
await onJobError(context, { error: event });
|
|
714
759
|
}
|
|
715
760
|
await log_final_reason_default(context, reason);
|
|
716
|
-
await sendEvent(
|
|
761
|
+
await sendEvent(context, RUN_COMPLETE, {
|
|
717
762
|
final_dataclip_id: state.lastDataclipId,
|
|
718
763
|
...reason
|
|
719
764
|
});
|
|
@@ -739,6 +784,7 @@ function execute(channel, engine, logger, plan, input, options = {}, onFinish =
|
|
|
739
784
|
logger.info("executing ", plan.id);
|
|
740
785
|
const state = create_run_state_default(plan, input);
|
|
741
786
|
const context = {
|
|
787
|
+
id: plan.id,
|
|
742
788
|
channel,
|
|
743
789
|
state,
|
|
744
790
|
logger,
|
|
@@ -746,76 +792,104 @@ function execute(channel, engine, logger, plan, input, options = {}, onFinish =
|
|
|
746
792
|
options,
|
|
747
793
|
onFinish
|
|
748
794
|
};
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
} catch (e) {
|
|
757
|
-
logger.error(
|
|
758
|
-
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
759
|
-
);
|
|
760
|
-
logger.error(e);
|
|
795
|
+
Sentry2.withIsolationScope(async () => {
|
|
796
|
+
Sentry2.addBreadcrumb({
|
|
797
|
+
category: "run",
|
|
798
|
+
message: "Executing run: loading metadata",
|
|
799
|
+
level: "info",
|
|
800
|
+
data: {
|
|
801
|
+
runId: plan.id
|
|
761
802
|
}
|
|
803
|
+
});
|
|
804
|
+
const throttle = throttle_default();
|
|
805
|
+
const addEvent = (eventName, handler) => {
|
|
806
|
+
const wrappedFn = async (event) => {
|
|
807
|
+
if (eventName !== "workflow-log") {
|
|
808
|
+
Sentry2.addBreadcrumb({
|
|
809
|
+
category: "event",
|
|
810
|
+
message: eventName,
|
|
811
|
+
level: "info"
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
const lightningEvent = eventMap[eventName] ?? eventName;
|
|
815
|
+
try {
|
|
816
|
+
await handler(context, event);
|
|
817
|
+
logger.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
818
|
+
} catch (e) {
|
|
819
|
+
if (!e.reportedToSentry) {
|
|
820
|
+
Sentry2.captureException(e);
|
|
821
|
+
logger.error(e);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
return {
|
|
826
|
+
[eventName]: wrappedFn
|
|
827
|
+
};
|
|
762
828
|
};
|
|
763
|
-
|
|
764
|
-
|
|
829
|
+
const listeners = Object.assign(
|
|
830
|
+
{},
|
|
831
|
+
addEvent("workflow-start", throttle(onRunStart)),
|
|
832
|
+
addEvent("job-start", throttle(onStepStart)),
|
|
833
|
+
addEvent("job-complete", throttle(onStepComplete)),
|
|
834
|
+
addEvent("job-error", throttle(onJobError)),
|
|
835
|
+
addEvent("workflow-log", throttle(onJobLog)),
|
|
836
|
+
// This will also resolve the promise
|
|
837
|
+
addEvent("workflow-complete", throttle(onWorkflowComplete)),
|
|
838
|
+
addEvent("workflow-error", throttle(onRunError))
|
|
839
|
+
// TODO send autoinstall logs
|
|
840
|
+
);
|
|
841
|
+
engine.listen(plan.id, listeners);
|
|
842
|
+
const resolvers = {
|
|
843
|
+
credential: (id) => loadCredential(context, id)
|
|
844
|
+
// TODO not supported right now
|
|
845
|
+
// dataclip: (id: string) => loadDataclip(channel, id),
|
|
765
846
|
};
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
// TODO not supported right now
|
|
783
|
-
// dataclip: (id: string) => loadDataclip(channel, id),
|
|
784
|
-
};
|
|
785
|
-
setTimeout(async () => {
|
|
786
|
-
let loadedInput = input;
|
|
787
|
-
if (typeof input === "string") {
|
|
788
|
-
logger.debug("loading dataclip", input);
|
|
847
|
+
setTimeout(async () => {
|
|
848
|
+
let loadedInput = input;
|
|
849
|
+
if (typeof input === "string") {
|
|
850
|
+
logger.debug("loading dataclip", input);
|
|
851
|
+
try {
|
|
852
|
+
loadedInput = await loadDataclip(context, input);
|
|
853
|
+
logger.success("dataclip loaded");
|
|
854
|
+
} catch (e) {
|
|
855
|
+
return onRunError(context, {
|
|
856
|
+
workflowId: plan.id,
|
|
857
|
+
message: `Failed to load dataclip ${input}${e.message ? `: ${e.message}` : ""}`,
|
|
858
|
+
type: "DataClipError",
|
|
859
|
+
severity: "exception"
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
}
|
|
789
863
|
try {
|
|
790
|
-
|
|
791
|
-
|
|
864
|
+
Sentry2.addBreadcrumb({
|
|
865
|
+
category: "run",
|
|
866
|
+
message: "run metadata loaded: starting run",
|
|
867
|
+
level: "info",
|
|
868
|
+
data: {
|
|
869
|
+
runId: plan.id
|
|
870
|
+
}
|
|
871
|
+
});
|
|
872
|
+
engine.execute(plan, loadedInput, { resolvers, ...options });
|
|
792
873
|
} catch (e) {
|
|
793
|
-
|
|
874
|
+
Sentry2.addBreadcrumb({
|
|
875
|
+
category: "run",
|
|
876
|
+
message: "exception in run",
|
|
877
|
+
level: "info",
|
|
878
|
+
data: {
|
|
879
|
+
runId: plan.id
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
onRunError(context, {
|
|
794
883
|
workflowId: plan.id,
|
|
795
|
-
message:
|
|
796
|
-
type:
|
|
797
|
-
severity:
|
|
884
|
+
message: e.message,
|
|
885
|
+
type: e.type,
|
|
886
|
+
severity: e.severity
|
|
798
887
|
});
|
|
799
888
|
}
|
|
800
|
-
}
|
|
801
|
-
try {
|
|
802
|
-
engine.execute(plan, loadedInput, { resolvers, ...options });
|
|
803
|
-
} catch (e) {
|
|
804
|
-
onRunError(context, {
|
|
805
|
-
workflowId: plan.id,
|
|
806
|
-
message: e.message,
|
|
807
|
-
type: e.type,
|
|
808
|
-
severity: e.severity
|
|
809
|
-
});
|
|
810
|
-
}
|
|
889
|
+
});
|
|
811
890
|
});
|
|
812
891
|
return context;
|
|
813
892
|
}
|
|
814
|
-
var sendEvent = (channel, event, payload) => new Promise((resolve, reject) => {
|
|
815
|
-
channel.push(event, payload).receive("error", reject).receive("timeout", () => {
|
|
816
|
-
reject(new Error("timeout"));
|
|
817
|
-
}).receive("ok", resolve);
|
|
818
|
-
});
|
|
819
893
|
function onJobError(context, event) {
|
|
820
894
|
const { state, error, jobId } = event;
|
|
821
895
|
if (state?.errors?.[jobId]?.message === error.message) {
|
|
@@ -824,7 +898,8 @@ function onJobError(context, event) {
|
|
|
824
898
|
return onStepComplete(context, event, event.error);
|
|
825
899
|
}
|
|
826
900
|
}
|
|
827
|
-
function onJobLog(
|
|
901
|
+
function onJobLog(context, event) {
|
|
902
|
+
const { state, options } = context;
|
|
828
903
|
let message = event.message;
|
|
829
904
|
if (event.redacted) {
|
|
830
905
|
message = [
|
|
@@ -844,17 +919,17 @@ function onJobLog({ channel, state, options }, event) {
|
|
|
844
919
|
if (state.activeStep) {
|
|
845
920
|
log.step_id = state.activeStep;
|
|
846
921
|
}
|
|
847
|
-
return sendEvent(
|
|
922
|
+
return sendEvent(context, RUN_LOG, log);
|
|
848
923
|
}
|
|
849
|
-
async function loadDataclip(
|
|
850
|
-
const result = await
|
|
924
|
+
async function loadDataclip(context, stateId) {
|
|
925
|
+
const result = await sendEvent(context, GET_DATACLIP, {
|
|
851
926
|
id: stateId
|
|
852
927
|
});
|
|
853
928
|
const str = enc.decode(new Uint8Array(result));
|
|
854
929
|
return JSON.parse(str);
|
|
855
930
|
}
|
|
856
|
-
async function loadCredential(
|
|
857
|
-
return
|
|
931
|
+
async function loadCredential(context, credentialId) {
|
|
932
|
+
return sendEvent(context, GET_CREDENTIAL, { id: credentialId });
|
|
858
933
|
}
|
|
859
934
|
|
|
860
935
|
// src/middleware/healthcheck.ts
|
|
@@ -863,6 +938,7 @@ var healthcheck_default = (ctx) => {
|
|
|
863
938
|
};
|
|
864
939
|
|
|
865
940
|
// src/channels/run.ts
|
|
941
|
+
import * as Sentry3 from "@sentry/node";
|
|
866
942
|
var joinRunChannel = (socket, token, runId, logger) => {
|
|
867
943
|
return new Promise((resolve, reject) => {
|
|
868
944
|
let didReceiveOk = false;
|
|
@@ -873,13 +949,18 @@ var joinRunChannel = (socket, token, runId, logger) => {
|
|
|
873
949
|
if (!didReceiveOk) {
|
|
874
950
|
didReceiveOk = true;
|
|
875
951
|
logger.success(`connected to ${channelName}`, e);
|
|
876
|
-
const run = await
|
|
952
|
+
const run = await send_event_default(
|
|
953
|
+
{ channel, logger, id: runId },
|
|
954
|
+
GET_PLAN
|
|
955
|
+
);
|
|
877
956
|
resolve({ channel, run });
|
|
878
957
|
}
|
|
879
958
|
}).receive("error", (err) => {
|
|
959
|
+
Sentry3.captureException(err);
|
|
880
960
|
logger.error(`error connecting to ${channelName}`, err);
|
|
881
961
|
reject(err);
|
|
882
962
|
}).receive("timeout", (err) => {
|
|
963
|
+
Sentry3.captureException(err);
|
|
883
964
|
logger.error(`Timeout for ${channelName}`, err);
|
|
884
965
|
reject(err);
|
|
885
966
|
});
|
|
@@ -896,6 +977,7 @@ var run_default = joinRunChannel;
|
|
|
896
977
|
|
|
897
978
|
// src/channels/worker-queue.ts
|
|
898
979
|
import EventEmitter from "node:events";
|
|
980
|
+
import * as Sentry4 from "@sentry/node";
|
|
899
981
|
import { Socket as PhxSocket } from "phoenix";
|
|
900
982
|
import { WebSocket } from "ws";
|
|
901
983
|
import { API_VERSION } from "@openfn/lexicon/lightning";
|
|
@@ -924,7 +1006,17 @@ var worker_token_default = generateWorkerToken;
|
|
|
924
1006
|
// src/channels/worker-queue.ts
|
|
925
1007
|
var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger, SocketConstructor = PhxSocket) => {
|
|
926
1008
|
const events = new EventEmitter();
|
|
1009
|
+
Sentry4.addBreadcrumb({
|
|
1010
|
+
category: "lifecycle",
|
|
1011
|
+
message: "Connecting to worker queue",
|
|
1012
|
+
level: "info"
|
|
1013
|
+
});
|
|
927
1014
|
worker_token_default(secret, serverId, logger).then(async (token) => {
|
|
1015
|
+
Sentry4.addBreadcrumb({
|
|
1016
|
+
category: "lifecycle",
|
|
1017
|
+
message: "Worker token generated",
|
|
1018
|
+
level: "info"
|
|
1019
|
+
});
|
|
928
1020
|
const params = {
|
|
929
1021
|
token,
|
|
930
1022
|
api_version: API_VERSION,
|
|
@@ -933,11 +1025,19 @@ var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger, So
|
|
|
933
1025
|
const socket = new SocketConstructor(endpoint, {
|
|
934
1026
|
params,
|
|
935
1027
|
transport: WebSocket,
|
|
936
|
-
timeout: timeout * 1e3
|
|
1028
|
+
timeout: timeout * 1e3,
|
|
1029
|
+
reconnectAfterMs: (tries) => Math.max(tries * 1e3)
|
|
937
1030
|
});
|
|
938
1031
|
let didOpen = false;
|
|
1032
|
+
let shouldReportConnectionError = true;
|
|
939
1033
|
socket.onOpen(() => {
|
|
1034
|
+
Sentry4.addBreadcrumb({
|
|
1035
|
+
category: "lifecycle",
|
|
1036
|
+
message: "Web socket connected",
|
|
1037
|
+
level: "info"
|
|
1038
|
+
});
|
|
940
1039
|
didOpen = true;
|
|
1040
|
+
shouldReportConnectionError = true;
|
|
941
1041
|
const channel = socket.channel("worker:queue");
|
|
942
1042
|
channel.onMessage = (ev, load) => {
|
|
943
1043
|
events.emit("message", ev, load);
|
|
@@ -957,6 +1057,16 @@ var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger, So
|
|
|
957
1057
|
events.emit("disconnect");
|
|
958
1058
|
});
|
|
959
1059
|
socket.onError((e) => {
|
|
1060
|
+
Sentry4.addBreadcrumb({
|
|
1061
|
+
category: "lifecycle",
|
|
1062
|
+
message: "Error in web socket connection",
|
|
1063
|
+
level: "info"
|
|
1064
|
+
});
|
|
1065
|
+
if (shouldReportConnectionError) {
|
|
1066
|
+
logger.debug("Reporting connection error to sentry");
|
|
1067
|
+
shouldReportConnectionError = false;
|
|
1068
|
+
Sentry4.captureException(e);
|
|
1069
|
+
}
|
|
960
1070
|
if (!didOpen) {
|
|
961
1071
|
events.emit("error", e.message);
|
|
962
1072
|
didOpen = false;
|
|
@@ -1061,6 +1171,13 @@ function createServer(engine, options = {}) {
|
|
|
1061
1171
|
const router = new Router();
|
|
1062
1172
|
app.events = new EventEmitter2();
|
|
1063
1173
|
app.engine = engine;
|
|
1174
|
+
if (options.sentryDsn) {
|
|
1175
|
+
Sentry5.init({
|
|
1176
|
+
environment: options.sentryEnv,
|
|
1177
|
+
dsn: options.sentryDsn
|
|
1178
|
+
});
|
|
1179
|
+
Sentry5.setupKoaErrorHandler(app);
|
|
1180
|
+
}
|
|
1064
1181
|
app.use(bodyParser());
|
|
1065
1182
|
app.use(
|
|
1066
1183
|
koaLogger((str, _args) => {
|
package/dist/start.js
CHANGED
|
@@ -142,6 +142,7 @@ var runtime_engine_default = createMock;
|
|
|
142
142
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
143
143
|
import { promisify } from "node:util";
|
|
144
144
|
import { exec as _exec } from "node:child_process";
|
|
145
|
+
import * as Sentry5 from "@sentry/node";
|
|
145
146
|
import Koa from "koa";
|
|
146
147
|
import bodyParser from "koa-bodyparser";
|
|
147
148
|
import koaLogger from "koa-logger";
|
|
@@ -350,6 +351,9 @@ var startWorkloop = (app, logger2, minBackoff2, maxBackoff2, maxWorkers) => {
|
|
|
350
351
|
};
|
|
351
352
|
var workloop_default = startWorkloop;
|
|
352
353
|
|
|
354
|
+
// src/api/execute.ts
|
|
355
|
+
import * as Sentry2 from "@sentry/node";
|
|
356
|
+
|
|
353
357
|
// src/util/convert-lightning-plan.ts
|
|
354
358
|
import crypto3 from "node:crypto";
|
|
355
359
|
import path from "node:path";
|
|
@@ -527,26 +531,6 @@ var convert_lightning_plan_default = (run2, options = {}) => {
|
|
|
527
531
|
};
|
|
528
532
|
};
|
|
529
533
|
|
|
530
|
-
// src/util/get-with-reply.ts
|
|
531
|
-
var get_with_reply_default = (channel, event, payload) => new Promise((resolve5, reject) => {
|
|
532
|
-
channel.push(event, payload).receive("ok", (evt) => {
|
|
533
|
-
resolve5(evt);
|
|
534
|
-
}).receive("error", (e) => {
|
|
535
|
-
reject(e);
|
|
536
|
-
}).receive("timeout", (e) => {
|
|
537
|
-
reject(e);
|
|
538
|
-
});
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// src/util/stringify.ts
|
|
542
|
-
import stringify from "fast-safe-stringify";
|
|
543
|
-
var stringify_default = (obj) => stringify(obj, (_key, value) => {
|
|
544
|
-
if (value instanceof Uint8Array) {
|
|
545
|
-
return Array.from(value);
|
|
546
|
-
}
|
|
547
|
-
return value;
|
|
548
|
-
});
|
|
549
|
-
|
|
550
534
|
// src/util/create-run-state.ts
|
|
551
535
|
var create_run_state_default = (plan, input) => {
|
|
552
536
|
const state = {
|
|
@@ -580,6 +564,67 @@ var create_run_state_default = (plan, input) => {
|
|
|
580
564
|
return state;
|
|
581
565
|
};
|
|
582
566
|
|
|
567
|
+
// src/util/send-event.ts
|
|
568
|
+
import * as Sentry from "@sentry/node";
|
|
569
|
+
|
|
570
|
+
// src/errors.ts
|
|
571
|
+
var LightningSocketError = class extends Error {
|
|
572
|
+
constructor(event, message) {
|
|
573
|
+
super(`[${event}] ${message}`);
|
|
574
|
+
this.name = "LightningSocketError";
|
|
575
|
+
this.event = "";
|
|
576
|
+
this.rejectMessage = "";
|
|
577
|
+
this.event = event;
|
|
578
|
+
this.rejectMessage = message;
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
var LightningTimeoutError = class extends Error {
|
|
582
|
+
constructor(event) {
|
|
583
|
+
super(`[${event}] timeout`);
|
|
584
|
+
this.name = "LightningTimeoutError";
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
// src/util/send-event.ts
|
|
589
|
+
var sendEvent = (context, event, payload) => {
|
|
590
|
+
const { channel, logger: logger2, id: runId = "<unknown run>" } = context;
|
|
591
|
+
return new Promise((resolve5, reject) => {
|
|
592
|
+
const report = (error) => {
|
|
593
|
+
logger2.error(`${runId} :: ${event} :: ERR: ${error.message || error}`);
|
|
594
|
+
const context2 = {
|
|
595
|
+
run_id: runId,
|
|
596
|
+
event
|
|
597
|
+
};
|
|
598
|
+
const extras = {};
|
|
599
|
+
if (error.rejectMessage) {
|
|
600
|
+
extras.rejection_reason = error.rejectMessage;
|
|
601
|
+
}
|
|
602
|
+
Sentry.captureException(error, (scope) => {
|
|
603
|
+
scope.setContext("run", context2);
|
|
604
|
+
scope.setExtras(extras);
|
|
605
|
+
return scope;
|
|
606
|
+
});
|
|
607
|
+
error.reportedToSentry = true;
|
|
608
|
+
reject(error);
|
|
609
|
+
};
|
|
610
|
+
channel.push(event, payload).receive("error", (message) => {
|
|
611
|
+
report(new LightningSocketError(event, message));
|
|
612
|
+
}).receive("timeout", () => {
|
|
613
|
+
report(new LightningTimeoutError(event));
|
|
614
|
+
}).receive("ok", resolve5);
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
var send_event_default = sendEvent;
|
|
618
|
+
|
|
619
|
+
// src/util/stringify.ts
|
|
620
|
+
import stringify from "fast-safe-stringify";
|
|
621
|
+
var stringify_default = (obj) => stringify(obj, (_key, value) => {
|
|
622
|
+
if (value instanceof Uint8Array) {
|
|
623
|
+
return Array.from(value);
|
|
624
|
+
}
|
|
625
|
+
return value;
|
|
626
|
+
});
|
|
627
|
+
|
|
583
628
|
// src/util/throttle.ts
|
|
584
629
|
var createThrottler = () => {
|
|
585
630
|
const q = [];
|
|
@@ -665,7 +710,7 @@ function getVersion() {
|
|
|
665
710
|
|
|
666
711
|
// src/events/run-start.ts
|
|
667
712
|
async function onRunStart(context, event) {
|
|
668
|
-
const {
|
|
713
|
+
const { state, options = {} } = context;
|
|
669
714
|
const time = (timestamp() - BigInt(1e7)).toString();
|
|
670
715
|
const versionLogContext = {
|
|
671
716
|
...context,
|
|
@@ -678,7 +723,7 @@ async function onRunStart(context, event) {
|
|
|
678
723
|
worker: await getVersion(),
|
|
679
724
|
...event.versions
|
|
680
725
|
};
|
|
681
|
-
await sendEvent(
|
|
726
|
+
await sendEvent(context, RUN_START, {
|
|
682
727
|
versions,
|
|
683
728
|
/// use the engine time in run start
|
|
684
729
|
timestamp: timeInMicroseconds(event.time)
|
|
@@ -740,7 +785,7 @@ var calculateRunExitReason = (state) => {
|
|
|
740
785
|
|
|
741
786
|
// src/events/step-complete.ts
|
|
742
787
|
async function onStepComplete(context, event, error) {
|
|
743
|
-
const {
|
|
788
|
+
const { state, options } = context;
|
|
744
789
|
const dataclipId = crypto4.randomUUID();
|
|
745
790
|
const step_id = state.activeStep;
|
|
746
791
|
const job_id = state.activeJob;
|
|
@@ -785,13 +830,13 @@ async function onStepComplete(context, event, error) {
|
|
|
785
830
|
const reason = calculateJobExitReason(job_id, event.state, error);
|
|
786
831
|
state.reasons[job_id] = reason;
|
|
787
832
|
Object.assign(evt, reason);
|
|
788
|
-
return sendEvent(
|
|
833
|
+
return sendEvent(context, STEP_COMPLETE, evt);
|
|
789
834
|
}
|
|
790
835
|
|
|
791
836
|
// src/events/step-start.ts
|
|
792
837
|
import crypto5 from "node:crypto";
|
|
793
838
|
async function onStepStart(context, event) {
|
|
794
|
-
const {
|
|
839
|
+
const { state } = context;
|
|
795
840
|
state.activeStep = crypto5.randomUUID();
|
|
796
841
|
state.activeJob = event.jobId;
|
|
797
842
|
const input_dataclip_id = state.inputDataclips[event.jobId];
|
|
@@ -803,7 +848,7 @@ async function onStepStart(context, event) {
|
|
|
803
848
|
if (!state.withheldDataclips[input_dataclip_id]) {
|
|
804
849
|
evt.input_dataclip_id = input_dataclip_id;
|
|
805
850
|
}
|
|
806
|
-
await sendEvent(
|
|
851
|
+
await sendEvent(context, STEP_START, evt);
|
|
807
852
|
}
|
|
808
853
|
|
|
809
854
|
// src/util/log-final-reason.ts
|
|
@@ -825,12 +870,12 @@ ${reason.error_type}: ${reason.error_message || "unknown"}`;
|
|
|
825
870
|
|
|
826
871
|
// src/events/run-complete.ts
|
|
827
872
|
async function onWorkflowComplete(context, event) {
|
|
828
|
-
const { state,
|
|
873
|
+
const { state, onFinish, logger: logger2 } = context;
|
|
829
874
|
const result = state.dataclips[state.lastDataclipId];
|
|
830
875
|
const reason = calculateRunExitReason(state);
|
|
831
876
|
await log_final_reason_default(context, reason);
|
|
832
877
|
try {
|
|
833
|
-
await sendEvent(
|
|
878
|
+
await sendEvent(context, RUN_COMPLETE, {
|
|
834
879
|
final_dataclip_id: state.lastDataclipId,
|
|
835
880
|
timestamp: timeInMicroseconds(event.time),
|
|
836
881
|
...reason
|
|
@@ -846,14 +891,14 @@ async function onWorkflowComplete(context, event) {
|
|
|
846
891
|
|
|
847
892
|
// src/events/run-error.ts
|
|
848
893
|
async function onRunError(context, event) {
|
|
849
|
-
const { state,
|
|
894
|
+
const { state, logger: logger2, onFinish } = context;
|
|
850
895
|
try {
|
|
851
896
|
const reason = calculateJobExitReason("", { data: {} }, event);
|
|
852
897
|
if (state.activeJob) {
|
|
853
898
|
await onJobError(context, { error: event });
|
|
854
899
|
}
|
|
855
900
|
await log_final_reason_default(context, reason);
|
|
856
|
-
await sendEvent(
|
|
901
|
+
await sendEvent(context, RUN_COMPLETE, {
|
|
857
902
|
final_dataclip_id: state.lastDataclipId,
|
|
858
903
|
...reason
|
|
859
904
|
});
|
|
@@ -879,6 +924,7 @@ function execute(channel, engine, logger2, plan, input, options = {}, onFinish =
|
|
|
879
924
|
logger2.info("executing ", plan.id);
|
|
880
925
|
const state = create_run_state_default(plan, input);
|
|
881
926
|
const context = {
|
|
927
|
+
id: plan.id,
|
|
882
928
|
channel,
|
|
883
929
|
state,
|
|
884
930
|
logger: logger2,
|
|
@@ -886,76 +932,104 @@ function execute(channel, engine, logger2, plan, input, options = {}, onFinish =
|
|
|
886
932
|
options,
|
|
887
933
|
onFinish
|
|
888
934
|
};
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
} catch (e) {
|
|
897
|
-
logger2.error(
|
|
898
|
-
`${plan.id} :: ${lightningEvent} :: ERR: ${e.message || e.toString()}`
|
|
899
|
-
);
|
|
900
|
-
logger2.error(e);
|
|
935
|
+
Sentry2.withIsolationScope(async () => {
|
|
936
|
+
Sentry2.addBreadcrumb({
|
|
937
|
+
category: "run",
|
|
938
|
+
message: "Executing run: loading metadata",
|
|
939
|
+
level: "info",
|
|
940
|
+
data: {
|
|
941
|
+
runId: plan.id
|
|
901
942
|
}
|
|
943
|
+
});
|
|
944
|
+
const throttle = throttle_default();
|
|
945
|
+
const addEvent = (eventName, handler) => {
|
|
946
|
+
const wrappedFn = async (event) => {
|
|
947
|
+
if (eventName !== "workflow-log") {
|
|
948
|
+
Sentry2.addBreadcrumb({
|
|
949
|
+
category: "event",
|
|
950
|
+
message: eventName,
|
|
951
|
+
level: "info"
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
const lightningEvent = eventMap[eventName] ?? eventName;
|
|
955
|
+
try {
|
|
956
|
+
await handler(context, event);
|
|
957
|
+
logger2.info(`${plan.id} :: ${lightningEvent} :: OK`);
|
|
958
|
+
} catch (e) {
|
|
959
|
+
if (!e.reportedToSentry) {
|
|
960
|
+
Sentry2.captureException(e);
|
|
961
|
+
logger2.error(e);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
};
|
|
965
|
+
return {
|
|
966
|
+
[eventName]: wrappedFn
|
|
967
|
+
};
|
|
902
968
|
};
|
|
903
|
-
|
|
904
|
-
|
|
969
|
+
const listeners = Object.assign(
|
|
970
|
+
{},
|
|
971
|
+
addEvent("workflow-start", throttle(onRunStart)),
|
|
972
|
+
addEvent("job-start", throttle(onStepStart)),
|
|
973
|
+
addEvent("job-complete", throttle(onStepComplete)),
|
|
974
|
+
addEvent("job-error", throttle(onJobError)),
|
|
975
|
+
addEvent("workflow-log", throttle(onJobLog)),
|
|
976
|
+
// This will also resolve the promise
|
|
977
|
+
addEvent("workflow-complete", throttle(onWorkflowComplete)),
|
|
978
|
+
addEvent("workflow-error", throttle(onRunError))
|
|
979
|
+
// TODO send autoinstall logs
|
|
980
|
+
);
|
|
981
|
+
engine.listen(plan.id, listeners);
|
|
982
|
+
const resolvers = {
|
|
983
|
+
credential: (id) => loadCredential(context, id)
|
|
984
|
+
// TODO not supported right now
|
|
985
|
+
// dataclip: (id: string) => loadDataclip(channel, id),
|
|
905
986
|
};
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
// TODO not supported right now
|
|
923
|
-
// dataclip: (id: string) => loadDataclip(channel, id),
|
|
924
|
-
};
|
|
925
|
-
setTimeout(async () => {
|
|
926
|
-
let loadedInput = input;
|
|
927
|
-
if (typeof input === "string") {
|
|
928
|
-
logger2.debug("loading dataclip", input);
|
|
987
|
+
setTimeout(async () => {
|
|
988
|
+
let loadedInput = input;
|
|
989
|
+
if (typeof input === "string") {
|
|
990
|
+
logger2.debug("loading dataclip", input);
|
|
991
|
+
try {
|
|
992
|
+
loadedInput = await loadDataclip(context, input);
|
|
993
|
+
logger2.success("dataclip loaded");
|
|
994
|
+
} catch (e) {
|
|
995
|
+
return onRunError(context, {
|
|
996
|
+
workflowId: plan.id,
|
|
997
|
+
message: `Failed to load dataclip ${input}${e.message ? `: ${e.message}` : ""}`,
|
|
998
|
+
type: "DataClipError",
|
|
999
|
+
severity: "exception"
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
929
1003
|
try {
|
|
930
|
-
|
|
931
|
-
|
|
1004
|
+
Sentry2.addBreadcrumb({
|
|
1005
|
+
category: "run",
|
|
1006
|
+
message: "run metadata loaded: starting run",
|
|
1007
|
+
level: "info",
|
|
1008
|
+
data: {
|
|
1009
|
+
runId: plan.id
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
engine.execute(plan, loadedInput, { resolvers, ...options });
|
|
932
1013
|
} catch (e) {
|
|
933
|
-
|
|
1014
|
+
Sentry2.addBreadcrumb({
|
|
1015
|
+
category: "run",
|
|
1016
|
+
message: "exception in run",
|
|
1017
|
+
level: "info",
|
|
1018
|
+
data: {
|
|
1019
|
+
runId: plan.id
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
onRunError(context, {
|
|
934
1023
|
workflowId: plan.id,
|
|
935
|
-
message:
|
|
936
|
-
type:
|
|
937
|
-
severity:
|
|
1024
|
+
message: e.message,
|
|
1025
|
+
type: e.type,
|
|
1026
|
+
severity: e.severity
|
|
938
1027
|
});
|
|
939
1028
|
}
|
|
940
|
-
}
|
|
941
|
-
try {
|
|
942
|
-
engine.execute(plan, loadedInput, { resolvers, ...options });
|
|
943
|
-
} catch (e) {
|
|
944
|
-
onRunError(context, {
|
|
945
|
-
workflowId: plan.id,
|
|
946
|
-
message: e.message,
|
|
947
|
-
type: e.type,
|
|
948
|
-
severity: e.severity
|
|
949
|
-
});
|
|
950
|
-
}
|
|
1029
|
+
});
|
|
951
1030
|
});
|
|
952
1031
|
return context;
|
|
953
1032
|
}
|
|
954
|
-
var sendEvent = (channel, event, payload) => new Promise((resolve5, reject) => {
|
|
955
|
-
channel.push(event, payload).receive("error", reject).receive("timeout", () => {
|
|
956
|
-
reject(new Error("timeout"));
|
|
957
|
-
}).receive("ok", resolve5);
|
|
958
|
-
});
|
|
959
1033
|
function onJobError(context, event) {
|
|
960
1034
|
const { state, error, jobId } = event;
|
|
961
1035
|
if (state?.errors?.[jobId]?.message === error.message) {
|
|
@@ -964,7 +1038,8 @@ function onJobError(context, event) {
|
|
|
964
1038
|
return onStepComplete(context, event, event.error);
|
|
965
1039
|
}
|
|
966
1040
|
}
|
|
967
|
-
function onJobLog(
|
|
1041
|
+
function onJobLog(context, event) {
|
|
1042
|
+
const { state, options } = context;
|
|
968
1043
|
let message = event.message;
|
|
969
1044
|
if (event.redacted) {
|
|
970
1045
|
message = [
|
|
@@ -984,17 +1059,17 @@ function onJobLog({ channel, state, options }, event) {
|
|
|
984
1059
|
if (state.activeStep) {
|
|
985
1060
|
log.step_id = state.activeStep;
|
|
986
1061
|
}
|
|
987
|
-
return sendEvent(
|
|
1062
|
+
return sendEvent(context, RUN_LOG, log);
|
|
988
1063
|
}
|
|
989
|
-
async function loadDataclip(
|
|
990
|
-
const result = await
|
|
1064
|
+
async function loadDataclip(context, stateId) {
|
|
1065
|
+
const result = await sendEvent(context, GET_DATACLIP, {
|
|
991
1066
|
id: stateId
|
|
992
1067
|
});
|
|
993
1068
|
const str = enc.decode(new Uint8Array(result));
|
|
994
1069
|
return JSON.parse(str);
|
|
995
1070
|
}
|
|
996
|
-
async function loadCredential(
|
|
997
|
-
return
|
|
1071
|
+
async function loadCredential(context, credentialId) {
|
|
1072
|
+
return sendEvent(context, GET_CREDENTIAL, { id: credentialId });
|
|
998
1073
|
}
|
|
999
1074
|
|
|
1000
1075
|
// src/middleware/healthcheck.ts
|
|
@@ -1003,6 +1078,7 @@ var healthcheck_default = (ctx) => {
|
|
|
1003
1078
|
};
|
|
1004
1079
|
|
|
1005
1080
|
// src/channels/run.ts
|
|
1081
|
+
import * as Sentry3 from "@sentry/node";
|
|
1006
1082
|
var joinRunChannel = (socket, token, runId, logger2) => {
|
|
1007
1083
|
return new Promise((resolve5, reject) => {
|
|
1008
1084
|
let didReceiveOk = false;
|
|
@@ -1013,13 +1089,18 @@ var joinRunChannel = (socket, token, runId, logger2) => {
|
|
|
1013
1089
|
if (!didReceiveOk) {
|
|
1014
1090
|
didReceiveOk = true;
|
|
1015
1091
|
logger2.success(`connected to ${channelName}`, e);
|
|
1016
|
-
const run2 = await
|
|
1092
|
+
const run2 = await send_event_default(
|
|
1093
|
+
{ channel, logger: logger2, id: runId },
|
|
1094
|
+
GET_PLAN
|
|
1095
|
+
);
|
|
1017
1096
|
resolve5({ channel, run: run2 });
|
|
1018
1097
|
}
|
|
1019
1098
|
}).receive("error", (err) => {
|
|
1099
|
+
Sentry3.captureException(err);
|
|
1020
1100
|
logger2.error(`error connecting to ${channelName}`, err);
|
|
1021
1101
|
reject(err);
|
|
1022
1102
|
}).receive("timeout", (err) => {
|
|
1103
|
+
Sentry3.captureException(err);
|
|
1023
1104
|
logger2.error(`Timeout for ${channelName}`, err);
|
|
1024
1105
|
reject(err);
|
|
1025
1106
|
});
|
|
@@ -1036,6 +1117,7 @@ var run_default = joinRunChannel;
|
|
|
1036
1117
|
|
|
1037
1118
|
// src/channels/worker-queue.ts
|
|
1038
1119
|
import EventEmitter2 from "node:events";
|
|
1120
|
+
import * as Sentry4 from "@sentry/node";
|
|
1039
1121
|
import { Socket as PhxSocket } from "phoenix";
|
|
1040
1122
|
import { WebSocket } from "ws";
|
|
1041
1123
|
import { API_VERSION } from "@openfn/lexicon/lightning";
|
|
@@ -1064,7 +1146,17 @@ var worker_token_default = generateWorkerToken;
|
|
|
1064
1146
|
// src/channels/worker-queue.ts
|
|
1065
1147
|
var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger2, SocketConstructor = PhxSocket) => {
|
|
1066
1148
|
const events = new EventEmitter2();
|
|
1149
|
+
Sentry4.addBreadcrumb({
|
|
1150
|
+
category: "lifecycle",
|
|
1151
|
+
message: "Connecting to worker queue",
|
|
1152
|
+
level: "info"
|
|
1153
|
+
});
|
|
1067
1154
|
worker_token_default(secret, serverId, logger2).then(async (token) => {
|
|
1155
|
+
Sentry4.addBreadcrumb({
|
|
1156
|
+
category: "lifecycle",
|
|
1157
|
+
message: "Worker token generated",
|
|
1158
|
+
level: "info"
|
|
1159
|
+
});
|
|
1068
1160
|
const params = {
|
|
1069
1161
|
token,
|
|
1070
1162
|
api_version: API_VERSION,
|
|
@@ -1073,11 +1165,19 @@ var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger2, S
|
|
|
1073
1165
|
const socket = new SocketConstructor(endpoint, {
|
|
1074
1166
|
params,
|
|
1075
1167
|
transport: WebSocket,
|
|
1076
|
-
timeout: timeout * 1e3
|
|
1168
|
+
timeout: timeout * 1e3,
|
|
1169
|
+
reconnectAfterMs: (tries) => Math.max(tries * 1e3)
|
|
1077
1170
|
});
|
|
1078
1171
|
let didOpen = false;
|
|
1172
|
+
let shouldReportConnectionError = true;
|
|
1079
1173
|
socket.onOpen(() => {
|
|
1174
|
+
Sentry4.addBreadcrumb({
|
|
1175
|
+
category: "lifecycle",
|
|
1176
|
+
message: "Web socket connected",
|
|
1177
|
+
level: "info"
|
|
1178
|
+
});
|
|
1080
1179
|
didOpen = true;
|
|
1180
|
+
shouldReportConnectionError = true;
|
|
1081
1181
|
const channel = socket.channel("worker:queue");
|
|
1082
1182
|
channel.onMessage = (ev, load) => {
|
|
1083
1183
|
events.emit("message", ev, load);
|
|
@@ -1097,6 +1197,16 @@ var connectToWorkerQueue = (endpoint, serverId, secret, timeout = 10, logger2, S
|
|
|
1097
1197
|
events.emit("disconnect");
|
|
1098
1198
|
});
|
|
1099
1199
|
socket.onError((e) => {
|
|
1200
|
+
Sentry4.addBreadcrumb({
|
|
1201
|
+
category: "lifecycle",
|
|
1202
|
+
message: "Error in web socket connection",
|
|
1203
|
+
level: "info"
|
|
1204
|
+
});
|
|
1205
|
+
if (shouldReportConnectionError) {
|
|
1206
|
+
logger2.debug("Reporting connection error to sentry");
|
|
1207
|
+
shouldReportConnectionError = false;
|
|
1208
|
+
Sentry4.captureException(e);
|
|
1209
|
+
}
|
|
1100
1210
|
if (!didOpen) {
|
|
1101
1211
|
events.emit("error", e.message);
|
|
1102
1212
|
didOpen = false;
|
|
@@ -1201,6 +1311,13 @@ function createServer(engine, options = {}) {
|
|
|
1201
1311
|
const router = new Router();
|
|
1202
1312
|
app.events = new EventEmitter3();
|
|
1203
1313
|
app.engine = engine;
|
|
1314
|
+
if (options.sentryDsn) {
|
|
1315
|
+
Sentry5.init({
|
|
1316
|
+
environment: options.sentryEnv,
|
|
1317
|
+
dsn: options.sentryDsn
|
|
1318
|
+
});
|
|
1319
|
+
Sentry5.setupKoaErrorHandler(app);
|
|
1320
|
+
}
|
|
1204
1321
|
app.use(bodyParser());
|
|
1205
1322
|
app.use(
|
|
1206
1323
|
koaLogger((str, _args) => {
|
|
@@ -6209,6 +6326,8 @@ function parseArgs(argv) {
|
|
|
6209
6326
|
WORKER_PORT,
|
|
6210
6327
|
WORKER_REPO_DIR,
|
|
6211
6328
|
WORKER_SECRET,
|
|
6329
|
+
WORKER_SENTRY_DSN,
|
|
6330
|
+
WORKER_SENTRY_ENV,
|
|
6212
6331
|
WORKER_STATE_PROPS_TO_REMOVE,
|
|
6213
6332
|
WORKER_SOCKET_TIMEOUT_SECONDS,
|
|
6214
6333
|
OPENFN_ADAPTORS_REPO
|
|
@@ -6229,6 +6348,11 @@ function parseArgs(argv) {
|
|
|
6229
6348
|
}).option("secret", {
|
|
6230
6349
|
alias: "s",
|
|
6231
6350
|
description: "Worker secret. (comes from WORKER_SECRET by default). Env: WORKER_SECRET"
|
|
6351
|
+
}).option("sentry-dsn", {
|
|
6352
|
+
alias: ["dsn"],
|
|
6353
|
+
description: "Sentry DSN. Env: WORKER_SENTRY_DSN"
|
|
6354
|
+
}).option("sentry-env", {
|
|
6355
|
+
description: "Sentry environment. Defaults to 'dev'. Env: WORKER_SENTRY_ENV"
|
|
6232
6356
|
}).option("socket-timeout", {
|
|
6233
6357
|
description: `Timeout for websockets to Lighting, in seconds. Defaults to 10.`
|
|
6234
6358
|
}).option("lightning-public-key", {
|
|
@@ -6280,6 +6404,8 @@ function parseArgs(argv) {
|
|
|
6280
6404
|
repoDir: setArg(args2.repoDir, WORKER_REPO_DIR),
|
|
6281
6405
|
monorepoDir: setArg(args2.monorepoDir, OPENFN_ADAPTORS_REPO),
|
|
6282
6406
|
secret: setArg(args2.secret, WORKER_SECRET),
|
|
6407
|
+
sentryDsn: setArg(args2.sentryDsn, WORKER_SENTRY_DSN),
|
|
6408
|
+
sentryEnv: setArg(args2.sentryEnv, WORKER_SENTRY_ENV, "dev"),
|
|
6283
6409
|
lightningPublicKey: setArg(
|
|
6284
6410
|
args2.lightningPublicKey,
|
|
6285
6411
|
WORKER_LIGHTNING_PUBLIC_KEY
|
|
@@ -6333,6 +6459,8 @@ function engineReady(engine) {
|
|
|
6333
6459
|
lightning: args.lightning,
|
|
6334
6460
|
logger,
|
|
6335
6461
|
secret: args.secret,
|
|
6462
|
+
sentryDsn: args.sentryDsn,
|
|
6463
|
+
sentryEnv: args.sentryEnv,
|
|
6336
6464
|
noLoop: !args.loop,
|
|
6337
6465
|
// TODO need to feed this through properly
|
|
6338
6466
|
backoff: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfn/ws-worker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "A Websocket Worker to connect Lightning to a Runtime Engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@koa/router": "^12.0.0",
|
|
14
|
+
"@sentry/node": "^9.5.0",
|
|
14
15
|
"@types/koa-logger": "^3.1.2",
|
|
15
16
|
"@types/ws": "^8.5.6",
|
|
16
17
|
"fast-safe-stringify": "^2.1.1",
|
|
@@ -22,9 +23,9 @@
|
|
|
22
23
|
"koa-logger": "^3.2.1",
|
|
23
24
|
"phoenix": "1.7.10",
|
|
24
25
|
"ws": "^8.18.0",
|
|
25
|
-
"@openfn/engine-multi": "1.6.
|
|
26
|
-
"@openfn/logger": "1.0.
|
|
27
|
-
"@openfn/runtime": "1.6.
|
|
26
|
+
"@openfn/engine-multi": "1.6.2",
|
|
27
|
+
"@openfn/logger": "1.0.5",
|
|
28
|
+
"@openfn/runtime": "1.6.4",
|
|
28
29
|
"@openfn/lexicon": "^1.2.0"
|
|
29
30
|
},
|
|
30
31
|
"devDependencies": {
|
|
@@ -37,11 +38,12 @@
|
|
|
37
38
|
"@types/yargs": "^17.0.12",
|
|
38
39
|
"ava": "5.1.0",
|
|
39
40
|
"nodemon": "3.0.1",
|
|
41
|
+
"sentry-testkit": "^6.1.0",
|
|
40
42
|
"tslib": "^2.4.0",
|
|
41
43
|
"tsup": "^6.2.3",
|
|
42
44
|
"typescript": "^4.6.4",
|
|
43
45
|
"yargs": "^17.6.2",
|
|
44
|
-
"@openfn/lightning-mock": "2.1.
|
|
46
|
+
"@openfn/lightning-mock": "2.1.4"
|
|
45
47
|
},
|
|
46
48
|
"files": [
|
|
47
49
|
"dist",
|