Skip to content

Commit 0a438f2

Browse files
authored
feat(observability): allow defining custom uncaught exception handler (#10462)
The goal of this change is to provide a plug point to allow custom observability mechanisms to handle uncaught exceptions.
1 parent 01e380e commit 0a438f2

File tree

5 files changed

+34
-14
lines changed

5 files changed

+34
-14
lines changed

flavors/swagger-ui-react/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,15 @@ Passes initial values to the Swagger UI state.
200200

201201
⚠️ This prop is currently only applied once, on mount. Changes to this prop's value will not be propagated to the underlying Swagger UI instance. A future version of this module will remove this limitation, and the change will not be considered a breaking change.
202202

203+
204+
#### `uncaughtExceptionHandler`: PropTypes.func
205+
206+
Allows to define a custom uncaught exception handler. The default is `null`, which means that the default handler will be used.
207+
The default handler will log the error to the console.
208+
209+
⚠️ This prop is currently only applied once, on mount. Changes to this prop's value will not be propagated to the underlying Swagger UI instance. A future version of this module will remove this limitation, and the change will not be considered a breaking change.
210+
211+
203212
## Limitations
204213

205214
* Not all configuration bindings are available.

flavors/swagger-ui-react/index.jsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const SwaggerUI = ({
4646
oauth2RedirectUrl = config.defaults.oauth2RedirectUrl,
4747
onComplete = null,
4848
initialState = config.defaults.initialState,
49+
uncaughtExceptionHandler = config.defaults.uncaughtExceptionHandler,
4950
}) => {
5051
const [system, setSystem] = useState(null)
5152
const SwaggerUIComponent = system?.getComponent("App", "root")
@@ -85,6 +86,7 @@ const SwaggerUI = ({
8586
persistAuthorization,
8687
withCredentials,
8788
initialState,
89+
uncaughtExceptionHandler,
8890
...(typeof oauth2RedirectUrl === "string"
8991
? { oauth2RedirectUrl: oauth2RedirectUrl }
9092
: {}),
@@ -168,6 +170,7 @@ SwaggerUI.propTypes = {
168170
withCredentials: PropTypes.bool,
169171
oauth2RedirectUrl: PropTypes.string,
170172
initialState: PropTypes.object,
173+
uncaughtExceptionHandler: PropTypes.func,
171174
}
172175
SwaggerUI.System = SwaggerUIConstructor.System
173176
SwaggerUI.presets = SwaggerUIConstructor.presets

src/core/config/defaults.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ const defaultOptions = Object.freeze({
9494
"audio/",
9595
"video/",
9696
],
97+
98+
uncaughtExceptionHandler: null,
9799
})
98100

99101
export default defaultOptions

src/core/config/type-cast/mappings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ const mappings = {
133133
typeCaster: booleanTypeCaster,
134134
defaultValue: defaultOptions.withCredentials,
135135
},
136+
uncaughtExceptionHandler: { typeCaster: nullableFunctionTypeCaster },
136137
}
137138

138139
export default mappings

src/core/system.js

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ export default class Store {
124124
}
125125

126126
rebuildReducer() {
127-
this.store.replaceReducer(buildReducer(this.system.statePlugins))
127+
this.store.replaceReducer(buildReducer(this.system.statePlugins, this.getSystem))
128128
}
129129

130130
/**
@@ -176,7 +176,7 @@ export default class Store {
176176
if(!isFn(newAction)) {
177177
throw new TypeError("wrapActions needs to return a function that returns a new function (ie the wrapped action)")
178178
}
179-
return wrapWithTryCatch(newAction)
179+
return wrapWithTryCatch(newAction, this.getSystem)
180180
}, action || Function.prototype)
181181
})
182182
}
@@ -256,11 +256,11 @@ export default class Store {
256256

257257
return objMap(obj, (fn) => {
258258
return (...args) => {
259-
let res = wrapWithTryCatch(fn).apply(null, [getNestedState(), ...args])
259+
let res = wrapWithTryCatch(fn, this.getSystem).apply(null, [getNestedState(), ...args])
260260

261261
// If a selector returns a function, give it the system - for advanced usage
262262
if(typeof(res) === "function")
263-
res = wrapWithTryCatch(res)(getSystem())
263+
res = wrapWithTryCatch(res, this.getSystem)(getSystem())
264264

265265
return res
266266
}
@@ -333,7 +333,7 @@ function callAfterLoad(plugins, system, { hasLoaded } = {}) {
333333
if(isObject(plugins) && !isArray(plugins)) {
334334
if(typeof plugins.afterLoad === "function") {
335335
calledSomething = true
336-
wrapWithTryCatch(plugins.afterLoad).call(this, system)
336+
wrapWithTryCatch(plugins.afterLoad, system.getSystem).call(this, system)
337337
}
338338
}
339339

@@ -436,16 +436,16 @@ function systemExtend(dest={}, src={}) {
436436
return deepExtend(dest, src)
437437
}
438438

439-
function buildReducer(states) {
439+
function buildReducer(states, getSystem) {
440440
let reducerObj = objMap(states, (val) => {
441441
return val.reducers
442442
})
443-
return allReducers(reducerObj)
443+
return allReducers(reducerObj, getSystem)
444444
}
445445

446-
function allReducers(reducerSystem) {
446+
function allReducers(reducerSystem, getSystem) {
447447
let reducers = Object.keys(reducerSystem).reduce((obj, key) => {
448-
obj[key] = makeReducer(reducerSystem[key])
448+
obj[key] = makeReducer(reducerSystem[key], getSystem)
449449
return obj
450450
},{})
451451

@@ -456,14 +456,14 @@ function allReducers(reducerSystem) {
456456
return combineReducers(reducers)
457457
}
458458

459-
function makeReducer(reducerObj) {
459+
function makeReducer(reducerObj, getSystem) {
460460
return (state = new Map(), action) => {
461461
if(!reducerObj)
462462
return state
463463

464464
let redFn = (reducerObj[action.type])
465465
if(redFn) {
466-
const res = wrapWithTryCatch(redFn)(state, action)
466+
const res = wrapWithTryCatch(redFn, getSystem)(state, action)
467467
// If the try/catch wrapper kicks in, we'll get null back...
468468
// in that case, we want to avoid making any changes to state
469469
return res === null ? state : res
@@ -472,7 +472,7 @@ function makeReducer(reducerObj) {
472472
}
473473
}
474474

475-
function wrapWithTryCatch(fn, {
475+
function wrapWithTryCatch(fn, getSystem, {
476476
logErrors = true
477477
} = {}) {
478478
if(typeof fn !== "function") {
@@ -482,9 +482,14 @@ function wrapWithTryCatch(fn, {
482482
return function(...args) {
483483
try {
484484
return fn.call(this, ...args)
485-
} catch(e) {
485+
} catch(error) {
486486
if(logErrors) {
487-
console.error(e)
487+
const { uncaughtExceptionHandler} = getSystem().getConfigs()
488+
if (typeof uncaughtExceptionHandler === "function") {
489+
uncaughtExceptionHandler(error)
490+
} else {
491+
console.error(error)
492+
}
488493
}
489494
return null
490495
}

0 commit comments

Comments
 (0)