Skip to content

feat(observability): allow defining custom uncaught exception handler #10462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions flavors/swagger-ui-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,15 @@ Passes initial values to the Swagger UI state.

⚠️ 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.


#### `uncaughtExceptionHandler`: PropTypes.func

Allows to define a custom uncaught exception handler. The default is `null`, which means that the default handler will be used.
The default handler will log the error to the console.

⚠️ 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.


## Limitations

* Not all configuration bindings are available.
Expand Down
3 changes: 3 additions & 0 deletions flavors/swagger-ui-react/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const SwaggerUI = ({
oauth2RedirectUrl = config.defaults.oauth2RedirectUrl,
onComplete = null,
initialState = config.defaults.initialState,
uncaughtExceptionHandler = config.defaults.uncaughtExceptionHandler,
}) => {
const [system, setSystem] = useState(null)
const SwaggerUIComponent = system?.getComponent("App", "root")
Expand Down Expand Up @@ -85,6 +86,7 @@ const SwaggerUI = ({
persistAuthorization,
withCredentials,
initialState,
uncaughtExceptionHandler,
...(typeof oauth2RedirectUrl === "string"
? { oauth2RedirectUrl: oauth2RedirectUrl }
: {}),
Expand Down Expand Up @@ -168,6 +170,7 @@ SwaggerUI.propTypes = {
withCredentials: PropTypes.bool,
oauth2RedirectUrl: PropTypes.string,
initialState: PropTypes.object,
uncaughtExceptionHandler: PropTypes.func,
}
SwaggerUI.System = SwaggerUIConstructor.System
SwaggerUI.presets = SwaggerUIConstructor.presets
Expand Down
2 changes: 2 additions & 0 deletions src/core/config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ const defaultOptions = Object.freeze({
"audio/",
"video/",
],

uncaughtExceptionHandler: null,
})

export default defaultOptions
1 change: 1 addition & 0 deletions src/core/config/type-cast/mappings.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ const mappings = {
typeCaster: booleanTypeCaster,
defaultValue: defaultOptions.withCredentials,
},
uncaughtExceptionHandler: { typeCaster: nullableFunctionTypeCaster },
}

export default mappings
33 changes: 19 additions & 14 deletions src/core/system.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default class Store {
}

rebuildReducer() {
this.store.replaceReducer(buildReducer(this.system.statePlugins))
this.store.replaceReducer(buildReducer(this.system.statePlugins, this.getSystem))
}

/**
Expand Down Expand Up @@ -176,7 +176,7 @@ export default class Store {
if(!isFn(newAction)) {
throw new TypeError("wrapActions needs to return a function that returns a new function (ie the wrapped action)")
}
return wrapWithTryCatch(newAction)
return wrapWithTryCatch(newAction, this.getSystem)
}, action || Function.prototype)
})
}
Expand Down Expand Up @@ -256,11 +256,11 @@ export default class Store {

return objMap(obj, (fn) => {
return (...args) => {
let res = wrapWithTryCatch(fn).apply(null, [getNestedState(), ...args])
let res = wrapWithTryCatch(fn, this.getSystem).apply(null, [getNestedState(), ...args])

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

return res
}
Expand Down Expand Up @@ -333,7 +333,7 @@ function callAfterLoad(plugins, system, { hasLoaded } = {}) {
if(isObject(plugins) && !isArray(plugins)) {
if(typeof plugins.afterLoad === "function") {
calledSomething = true
wrapWithTryCatch(plugins.afterLoad).call(this, system)
wrapWithTryCatch(plugins.afterLoad, system.getSystem).call(this, system)
}
}

Expand Down Expand Up @@ -436,16 +436,16 @@ function systemExtend(dest={}, src={}) {
return deepExtend(dest, src)
}

function buildReducer(states) {
function buildReducer(states, getSystem) {
let reducerObj = objMap(states, (val) => {
return val.reducers
})
return allReducers(reducerObj)
return allReducers(reducerObj, getSystem)
}

function allReducers(reducerSystem) {
function allReducers(reducerSystem, getSystem) {
let reducers = Object.keys(reducerSystem).reduce((obj, key) => {
obj[key] = makeReducer(reducerSystem[key])
obj[key] = makeReducer(reducerSystem[key], getSystem)
return obj
},{})

Expand All @@ -456,14 +456,14 @@ function allReducers(reducerSystem) {
return combineReducers(reducers)
}

function makeReducer(reducerObj) {
function makeReducer(reducerObj, getSystem) {
return (state = new Map(), action) => {
if(!reducerObj)
return state

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

function wrapWithTryCatch(fn, {
function wrapWithTryCatch(fn, getSystem, {
logErrors = true
} = {}) {
if(typeof fn !== "function") {
Expand All @@ -482,9 +482,14 @@ function wrapWithTryCatch(fn, {
return function(...args) {
try {
return fn.call(this, ...args)
} catch(e) {
} catch(error) {
if(logErrors) {
console.error(e)
const { uncaughtExceptionHandler} = getSystem().getConfigs()
if (typeof uncaughtExceptionHandler === "function") {
uncaughtExceptionHandler(error)
} else {
console.error(error)
}
}
return null
}
Expand Down