-
Notifications
You must be signed in to change notification settings - Fork 62
/
aws_lambda.ts
123 lines (106 loc) · 3.71 KB
/
aws_lambda.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Types } from '@honeybadger-io/core'
import Honeybadger from '../server'
import type { Handler, Callback, Context } from 'aws-lambda'
export type SyncHandler<TEvent = any, TResult = any> = (
event: TEvent,
context: Context,
callback: Callback<TResult>,
) => void;
export type AsyncHandler<TEvent = any, TResult = any> = (
event: TEvent,
context: Context,
) => Promise<TResult>;
function isHandlerSync(handler: Handler): handler is SyncHandler {
return handler.length > 2
}
function reportToHoneybadger(hb: typeof Honeybadger, err: Error | string | null, callback: (err: Error | string | null) => void) {
hb.notify(err, {
afterNotify: function () {
hb.clear()
callback(err)
}
})
}
function asyncHandler<TEvent = any, TResult = any>(handler: AsyncHandler<TEvent, TResult>, hb: typeof Honeybadger): AsyncHandler<TEvent, TResult> {
return function wrappedLambdaHandler(event, context) {
return new Promise<TResult>((resolve, reject) => {
hb.run(() => {
const timeoutHandler = setupTimeoutWarning(hb, context)
try {
const result = handler(event, context);
// check if handler returns a promise
if (result && result.then) {
result
.then(resolve)
.catch(err => reportToHoneybadger(hb, err, reject))
.finally(() => clearTimeout(timeoutHandler))
}
else {
clearTimeout(timeoutHandler)
resolve(result)
}
} catch (err) {
clearTimeout(timeoutHandler)
reportToHoneybadger(hb, err, reject)
}
})
})
}
}
function syncHandler<TEvent = any, TResult = any>(handler: SyncHandler<TEvent, TResult>, hb: typeof Honeybadger): SyncHandler<TEvent, TResult> {
return function wrappedLambdaHandler(event, context, cb) {
hb.run(() => {
const timeoutHandler = setupTimeoutWarning(hb, context)
try {
handler(event, context, (error, result) => {
clearTimeout(timeoutHandler)
if (error) {
return reportToHoneybadger(hb, error, cb)
}
cb(null, result)
});
} catch (err) {
clearTimeout(timeoutHandler)
reportToHoneybadger(hb, err, cb)
}
})
}
}
function shouldReportTimeoutWarning(hb: typeof Honeybadger, context: Context): boolean {
return typeof context.getRemainingTimeInMillis === 'function' && !!((hb.config as Types.ServerlessConfig).reportTimeoutWarning)
}
function setupTimeoutWarning(hb: typeof Honeybadger, context: Context) {
if (!shouldReportTimeoutWarning(hb, context)) {
return
}
const delay = context.getRemainingTimeInMillis() - ((hb.config as Types.ServerlessConfig).timeoutWarningThresholdMs)
return setTimeout(() => {
hb.notify(`${context.functionName}[${context.functionVersion}] may have timed out`)
}, delay > 0 ? delay : 0)
}
export function lambdaHandler<TEvent = any, TResult = any>(handler: Handler<TEvent, TResult>): Handler<TEvent, TResult> {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const hb: typeof Honeybadger = this
if (isHandlerSync(handler)) {
return syncHandler(handler, hb)
}
return asyncHandler(handler, hb)
}
let listenerRemoved = false
/**
* Removes AWS Lambda default listener that
* exits the process before letting us report to honeybadger.
*/
export function removeAwsDefaultUncaughtExceptionListener() {
if (listenerRemoved) {
return
}
listenerRemoved = true
const listeners = process.listeners('uncaughtException')
if (listeners.length === 0) {
return
}
// We assume it's the first listener
process.removeListener('uncaughtException', listeners[0])
}