Skip to content

Commit cb289c8

Browse files
authored
Merge pull request #643 from appsignal/fix-global-flags
Fix global flags
2 parents 7f53c85 + ddc9f57 commit cb289c8

File tree

7 files changed

+87
-31
lines changed

7 files changed

+87
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
bump: patch
3+
type: fix
4+
---
5+
6+
Fix an issue when regexes with the `g` global flag are used on `ignoreErrors`. Before this change, after successfully matching on an error to ignore, if the following error would also match the same regular expression, the regular expression would then fail to match it.

Diff for: packages/javascript/jest.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ module.exports = {
44
roots: ["<rootDir>/src"],
55
transform: {
66
"^.+\\.tsx?$": "ts-jest"
7-
}
7+
},
8+
restoreMocks: true,
89
}

Diff for: packages/javascript/src/__mocks__/api.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
export const pushMock = jest.fn(span => Promise.resolve(span))
1+
let mock: jest.Mock
2+
3+
beforeEach(() => {
4+
mock = jest.fn(span => Promise.resolve(span))
5+
})
6+
7+
export const pushMockCall = (index: number = 0) => {
8+
expect(mock).toHaveBeenCalledTimes(index + 1)
9+
return mock.mock.calls[index][0].serialize()
10+
}
211

312
export const PushApi = jest.fn().mockImplementation(() => {
413
return {
5-
push: pushMock
14+
push: mock
615
}
716
})

Diff for: packages/javascript/src/__tests__/index.test.ts

+28-7
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { VERSION } from "../version"
33
import Appsignal from "../index"
44
import { Span } from "../span"
55

6+
import * as api from "../api"
7+
68
// @ts-ignore
7-
import { pushMock } from "../api"
9+
const pushMockCall: (index?: number) => any = api.pushMockCall
810

911
jest.mock("../api")
1012

@@ -51,7 +53,10 @@ describe("Appsignal", () => {
5153
ignoreErrors: ignored
5254
})
5355

54-
expect(appsignal.ignored).toEqual(ignored)
56+
// We clean the given regexes to remove the `g` flag, as it causes
57+
// matches to misbehave by remembering the last matched position:
58+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test#using_test_on_a_regex_with_the_global_flag
59+
expect(appsignal.ignored).toEqual([/Ignore me/m])
5560
})
5661

5762
describe("send", () => {
@@ -71,10 +76,18 @@ describe("Appsignal", () => {
7176

7277
appsignal.send(new Error(name))
7378

79+
expect(console.warn).toBeCalledTimes(1)
7480
expect(console.warn).toBeCalledWith(
7581
`[APPSIGNAL]: Ignored an error: ${name}`
7682
)
7783

84+
// As the regex used has the `g` flag, it would
85+
// remember the last matched position and fail to match again:
86+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test#using_test_on_a_regex_with_the_global_flag
87+
// We remove the `g` flag to make it match again, so it should match.
88+
appsignal.send(new Error(name))
89+
expect(console.warn).toBeCalledTimes(2)
90+
7891
// reset
7992
console.warn = original
8093
})
@@ -83,7 +96,7 @@ describe("Appsignal", () => {
8396
appsignal = new Appsignal({
8497
key: "TESTKEY",
8598
namespace: "test",
86-
matchBacktracePaths: [/here(.*)/]
99+
matchBacktracePaths: [/here(.*)/g]
87100
})
88101

89102
const error = new Error("test error")
@@ -94,15 +107,23 @@ describe("Appsignal", () => {
94107

95108
appsignal.send(error)
96109

97-
expect(pushMock).toHaveBeenCalled()
98-
const payload = pushMock.mock.calls[0][0].serialize()
110+
const firstPayload = pushMockCall(0)
99111

100-
expect(payload.error.backtrace).toEqual([
112+
expect(firstPayload.error.backtrace).toEqual([
101113
"Error: test error",
102114
" at Foo (/istheapp.js:13:10)"
103115
])
104116

105-
expect(payload.environment.backtrace_paths_matched).toEqual("1")
117+
expect(firstPayload.environment.backtrace_paths_matched).toEqual("1")
118+
119+
// As the regex used has the `g` flag, it would
120+
// remember the last matched position and fail to match again:
121+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test#using_test_on_a_regex_with_the_global_flag
122+
// We remove the `g` flag to make it match again, so it should match.
123+
appsignal.send(error)
124+
125+
const secondPayload = pushMockCall(1)
126+
expect(secondPayload.environment.backtrace_paths_matched).toEqual("1")
106127
})
107128
})
108129

Diff for: packages/javascript/src/__tests__/span.test.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ describe("Span", () => {
7171
].join("\n")
7272

7373
span.setError(error)
74-
span.cleanBacktracePath(new RegExp("/assets/(app/.*)$"))
74+
span.cleanBacktracePath([new RegExp("/assets/(app/.*)$")])
7575

7676
const backtrace = span.serialize().error.backtrace
7777
expect(backtrace).toEqual([
@@ -97,7 +97,7 @@ describe("Span", () => {
9797
].join("\n")
9898

9999
span.setError(error)
100-
span.cleanBacktracePath(new RegExp("/assets/(app/.*)$"))
100+
span.cleanBacktracePath([new RegExp("/assets/(app/.*)$")])
101101

102102
const backtrace = span.serialize().error.backtrace
103103
expect(backtrace).toEqual([
@@ -123,9 +123,9 @@ describe("Span", () => {
123123
].join("\n")
124124

125125
span.setError(error)
126-
span.cleanBacktracePath(
126+
span.cleanBacktracePath([
127127
new RegExp(".*/(assets/)(?:[0-9a-f]{16}/)?(app/.*)$")
128-
)
128+
])
129129

130130
const backtrace = span.serialize().error.backtrace
131131
expect(backtrace).toEqual([
@@ -181,7 +181,7 @@ describe("Span", () => {
181181
// empty string.
182182
//
183183
// This should result in the line not being modified.
184-
span.cleanBacktracePath(new RegExp(".*"))
184+
span.cleanBacktracePath([new RegExp(".*")])
185185

186186
const backtrace = span.serialize().error.backtrace
187187
expect(backtrace).toEqual([
@@ -203,7 +203,7 @@ describe("Span", () => {
203203
// empty string.
204204
//
205205
// This should result in the line not being modified.
206-
span.cleanBacktracePath(new RegExp(".*(z*)$"))
206+
span.cleanBacktracePath([new RegExp(".*(z*)$")])
207207

208208
const backtrace = span.serialize().error.backtrace
209209
expect(backtrace).toEqual([
@@ -220,7 +220,7 @@ describe("Span", () => {
220220
].join("\n")
221221

222222
span.setError(error)
223-
span.cleanBacktracePath(new RegExp("^pancakes/(.*)$"))
223+
span.cleanBacktracePath([new RegExp("^pancakes/(.*)$")])
224224

225225
const backtrace = span.serialize().error.backtrace
226226
expect(backtrace).toEqual([

Diff for: packages/javascript/src/index.ts

+31-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { AppsignalOptions } from "./interfaces/options"
1818
export default class Appsignal implements JSClient {
1919
public VERSION = VERSION
2020
public ignored: RegExp[] = []
21+
private matchBacktracePaths: RegExp[] = []
2122

2223
private _dispatcher: Dispatcher
2324
private _options: AppsignalOptions
@@ -40,7 +41,13 @@ export default class Appsignal implements JSClient {
4041
* @param {AppsignalOptions} options An object of options to configure the AppSignal client
4142
*/
4243
constructor(options: AppsignalOptions) {
43-
const { key = "", uri, revision, ignoreErrors } = options
44+
const {
45+
key = "",
46+
uri,
47+
revision,
48+
ignoreErrors,
49+
matchBacktracePaths
50+
} = options
4451

4552
// `revision` should be a `string`, but we attempt to
4653
// normalise to one anyway
@@ -65,6 +72,20 @@ export default class Appsignal implements JSClient {
6572
// property of a given `Error`
6673
if (ignoreErrors && Array.isArray(ignoreErrors)) {
6774
this.ignored = ignoreErrors
75+
.filter(value => value instanceof RegExp)
76+
.map(unglobalize)
77+
}
78+
79+
if (matchBacktracePaths) {
80+
if (Array.isArray(matchBacktracePaths)) {
81+
this.matchBacktracePaths = matchBacktracePaths
82+
} else {
83+
this.matchBacktracePaths = [matchBacktracePaths]
84+
}
85+
86+
this.matchBacktracePaths = this.matchBacktracePaths
87+
.filter(value => value instanceof RegExp)
88+
.map(unglobalize)
6889
}
6990

7091
this._dispatcher = new Dispatcher(this._queue, this._api)
@@ -207,9 +228,7 @@ export default class Appsignal implements JSClient {
207228
compose(...this._hooks.overrides)(span)
208229
}
209230

210-
if (this._options.matchBacktracePaths) {
211-
span.cleanBacktracePath(this._options.matchBacktracePaths)
212-
}
231+
span.cleanBacktracePath(this.matchBacktracePaths)
213232

214233
if (Environment.supportsPromises()) {
215234
// clear breadcrumbs as they are now loaded into the span,
@@ -447,3 +466,11 @@ export default class Appsignal implements JSClient {
447466
return event
448467
}
449468
}
469+
470+
// Returns a new `RegExp` object with the global flag removed.
471+
// This fixes issues where using global regexes repeatedly with `test` against
472+
// different strings will return unexpected results, due to the regex object
473+
// storing the location of its last match and resuming the search from there.
474+
function unglobalize(regexp: RegExp): RegExp {
475+
return new RegExp(regexp.source, regexp.flags.replace("g", ""))
476+
}

Diff for: packages/javascript/src/span.ts

+2-10
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,8 @@ export class Span extends Serializable<JSSpanData> {
7878
// @private
7979
// Do not use this function directly. Instead, set the `matchBacktracePaths`
8080
// configuration option when initializing AppSignal.
81-
public cleanBacktracePath(matchBacktracePaths: RegExp | RegExp[]): this {
82-
if (matchBacktracePaths instanceof RegExp) {
83-
matchBacktracePaths = [matchBacktracePaths]
84-
}
85-
86-
if (!Array.isArray(matchBacktracePaths)) {
81+
public cleanBacktracePath(matchBacktracePaths: RegExp[]): this {
82+
if (matchBacktracePaths.length === 0) {
8783
return this
8884
}
8985

@@ -100,10 +96,6 @@ export class Span extends Serializable<JSSpanData> {
10096
}
10197

10298
for (const matcher of matchBacktracePaths as RegExp[]) {
103-
if (!(matcher instanceof RegExp)) {
104-
continue
105-
}
106-
10799
const match = path.match(matcher)
108100
if (!match || match.length < 2) {
109101
continue

0 commit comments

Comments
 (0)