Skip to content

Commit c62fbc8

Browse files
committed
feat: capture ignores
1 parent e104edb commit c62fbc8

File tree

4 files changed

+132
-4
lines changed

4 files changed

+132
-4
lines changed

src/core/captureIgnores.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { log } from './logging'
2+
import { CaptureIgnore, CapturePayload } from './types'
3+
4+
export function shouldIgnoreCapture(
5+
capturePayload: CapturePayload,
6+
captureIgnores: CaptureIgnore[]
7+
) {
8+
const stringCaptureIgnore = (capturePayload: CapturePayload, ignoreEntry: string) => {
9+
if (ignoreEntry === '') return false
10+
return capturePayload.functionName.includes(ignoreEntry)
11+
}
12+
const regexpCaptureIgnore = (capturePayload: CapturePayload, ignoreEntry: RegExp) => {
13+
const matches = capturePayload.functionName.match(ignoreEntry)
14+
return (matches && matches.length > 0) ?? false
15+
}
16+
17+
for (let i = 0; i < captureIgnores.length; i++) {
18+
const ignoreEntry = captureIgnores[i]
19+
if (typeof ignoreEntry === 'string') {
20+
return stringCaptureIgnore(capturePayload, ignoreEntry)
21+
} else if (ignoreEntry instanceof RegExp) {
22+
return regexpCaptureIgnore(capturePayload, ignoreEntry)
23+
} else if (typeof ignoreEntry === 'function') {
24+
return ignoreEntry(capturePayload)
25+
} else {
26+
log.warn(
27+
'capture',
28+
`Invalid "captureIgnores" entry value of type "${typeof ignoreEntry}". Only strings, regular expressions and functions are supported. Learn more: https://docs.useflytrap.com/config/introduction`
29+
)
30+
return false
31+
}
32+
}
33+
return false
34+
}

src/core/storage.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
stringify,
2727
superJsonRegisterCustom
2828
} from './stringify'
29+
import { shouldIgnoreCapture } from './captureIgnores'
2930

3031
const loadedCaptures = new Map<string, CaptureDecryptedAndRevived | undefined>()
3132

@@ -178,7 +179,7 @@ export const liveFlytrapStorage: FlytrapStorage = {
178179
},
179180
async saveCapture(functions, calls, error?) {
180181
// Here error if
181-
const { publicApiKey, projectId } = (await getLoadedConfig()) ?? {}
182+
const { publicApiKey, projectId, captureIgnores } = (await getLoadedConfig()) ?? {}
182183

183184
if (!publicApiKey || !projectId || empty(publicApiKey, projectId)) {
184185
console.error(
@@ -206,7 +207,7 @@ export const liveFlytrapStorage: FlytrapStorage = {
206207
const linkedCalls = addLinksToCaptures(calls, { args, outputs })
207208
const linkedFunctions = addLinksToCaptures(functions, { args, outputs })
208209

209-
const newPayload: CapturePayload = {
210+
const payload: CapturePayload = {
210211
capturedUserId: getUserId(),
211212
projectId,
212213
functionName:
@@ -224,8 +225,16 @@ export const liveFlytrapStorage: FlytrapStorage = {
224225
})
225226
}
226227

228+
// Capture ignores
229+
if (captureIgnores) {
230+
if (shouldIgnoreCapture(payload, captureIgnores)) {
231+
log.info('storage', `Ignored capture with name "${payload.functionName}"`)
232+
return
233+
}
234+
}
235+
227236
const { data: stringifiedPayload, error: stringifyError } = tryCatchSync(() =>
228-
stringify(newPayload)
237+
stringify(payload)
229238
)
230239

231240
if (stringifyError || !stringifiedPayload) {
@@ -261,7 +270,7 @@ export const liveFlytrapStorage: FlytrapStorage = {
261270
} else {
262271
log.info(
263272
'storage',
264-
`Saved capture with name "${newPayload.functionName}". Payload Size: ${formatBytes(
273+
`Saved capture with name "${payload.functionName}". Payload Size: ${formatBytes(
265274
stringifiedPayload.length
266275
)}`
267276
)

src/core/types.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export type LogGroup =
1313
| 'transform'
1414
| 'cache'
1515

16+
export type CaptureIgnore = string | RegExp | ((payload: CapturePayload) => boolean)
17+
1618
export type FlytrapConfig = {
1719
projectId: string
1820
publicApiKey: string
@@ -47,6 +49,31 @@ export type FlytrapConfig = {
4749
* [Learn more](https://docs.useflytrap.com/config/introduction)
4850
*/
4951
excludeDirectories?: string[]
52+
/**
53+
* Prevent certain captures from being sent to the Flytrap API. Ignoring
54+
* captures is useful if there are certain errors that are expected to be
55+
* thrown, for example "UNAUTHORIZED" tRPC responses.
56+
*
57+
* Capture ignores can be defined using partial matching of the capture
58+
* name, regular expressions or a function that returns a boolean. If any
59+
* of the ignore criteria returns true, the capture is ignored.
60+
*
61+
* @example
62+
* ```typescript
63+
* defineFlytrapConfig({
64+
* captureIgnores: [
65+
* // Ignore captures whose name matches below regex
66+
* /Hello World/g,
67+
* // Ignore captures whose name contains "UNAUTHORIZED"
68+
* "UNAUTHORIZED",
69+
* // Ignore all captures
70+
* (capture: CapturePayload) => true
71+
* ]
72+
* })
73+
* ```
74+
* [Learn more](https://docs.useflytrap.com/config/introduction)
75+
*/
76+
captureIgnores?: CaptureIgnore[]
5077
}
5178

5279
export type ErrorType = {

test/excludes.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { _babelInterop } from '../src/transform/artifacts/artifacts'
55
import babelTraverse from '@babel/traverse'
66
import { findIgnoredImports, shouldIgnoreCall } from '../src/transform/packageIgnores'
77
import { excludeDirectoriesIncludeFilePath } from '../src/transform/excludes'
8+
import { CapturePayload } from '../src/exports'
9+
import { shouldIgnoreCapture } from '../src/core/captureIgnores'
810

911
describe('excludeDirectories', () => {
1012
it('excludeDirectoriesIncludeFilePath', () => {
@@ -142,3 +144,59 @@ describe('packageIgnores', () => {
142144
})
143145
}
144146
})
147+
148+
describe('captureIgnores', () => {
149+
const createMockCapturePayload = (partialPayload: Partial<CapturePayload>): CapturePayload => ({
150+
functionName: 'Lorem ipsum',
151+
capturedUserId: 'rasmus@useflytrap.com',
152+
args: '',
153+
outputs: '',
154+
error: 'encrypted',
155+
functions: [],
156+
calls: [],
157+
projectId: 'mock-project-id'
158+
})
159+
160+
const mockCapturePayload = createMockCapturePayload({})
161+
162+
const fixtures = {
163+
strings: [
164+
['Hello World', false],
165+
['Lorem ipsuM', false],
166+
['', false],
167+
['Lorem', true],
168+
['Lorem ipsum', true]
169+
],
170+
funcs: [
171+
[() => true, true],
172+
[(c: CapturePayload) => c.functionName.includes('Lorem'), true],
173+
[(c: CapturePayload) => c.capturedUserId === 'rasmus@useflytrap.com', true],
174+
[() => false, false],
175+
[(c: CapturePayload) => c.functionName.includes('LoRem'), false],
176+
[(c: CapturePayload) => c.capturedUserId === 'max@useflytrap.com', false]
177+
],
178+
regexps: [
179+
[/Hello World/g, false],
180+
[/Lorem ipsuM/g, false],
181+
[/\\/g, false],
182+
[/Lorem/g, true],
183+
[/Lorem ipsum/g, true],
184+
[(c: CapturePayload) => c.functionName.includes('Lorem'), true],
185+
[(c: CapturePayload) => c.capturedUserId === 'rasmus@useflytrap.com', true],
186+
[() => false, false],
187+
[(c: CapturePayload) => c.functionName.includes('LoRem'), false],
188+
[(c: CapturePayload) => c.capturedUserId === 'max@useflytrap.com', false]
189+
]
190+
} as const
191+
192+
for (const [key, keyFixtures] of Object.entries(fixtures)) {
193+
it(`capture ignores > ${key}`, () => {
194+
for (let i = 0; i < keyFixtures.length; i++) {
195+
expect(
196+
shouldIgnoreCapture(mockCapturePayload, [keyFixtures[i][0]]),
197+
`fixture "${keyFixtures[i][0]}"`
198+
).toEqual(keyFixtures[i][1])
199+
}
200+
})
201+
}
202+
})

0 commit comments

Comments
 (0)