sentry-javascript icon indicating copy to clipboard operation
sentry-javascript copied to clipboard

TypeError: Converting circular structure to JSON

Open the21st opened this issue 3 years ago β€’ 77 comments

  • [x] Review the documentation: https://docs.sentry.io/
  • [x] Search for existing issues: https://github.com/getsentry/sentry-javascript/issues
  • [ ] Use the latest release: https://github.com/getsentry/sentry-javascript/releases
  • [x] Provide a link to the affected event from your Sentry account -> https://sentry.io/organizations/deepnote/issues/1781748535/events/7e842347bdf44a49a68f1648d581de36

Package + Version

  • [x] @sentry/browser 5.19.1

Version:

5.19.1

Description

Error:

TypeError: Converting circular structure to JSON
    --> starting at object with constructor 'b'
    |     property 'collections' -> object with constructor 'Object'
    |     property '149e8f85-8be3-4957-b38b-981c51e38da7' -> object with constructor 'Object'
 ...
  at JSON.stringify(<anonymous>)
  at eventToSentryRequest(../node_modules/@sentry/core/esm/request.js:6:20)
  at sendEvent(../node_modules/@sentry/browser/esm/transports/fetch.js:28:25)
  at sendEvent(../node_modules/@sentry/core/esm/basebackend.js:38:25)
  at call(../node_modules/@sentry/core/esm/baseclient.js:318:28)
  at _sendEvent(../node_modules/@sentry/browser/esm/client.js:45:37)
  at onfulfilled(../node_modules/@sentry/core/esm/baseclient.js:377:27)
  at onfulfilled(../node_modules/@sentry/utils/esm/syncpromise.js:136:33)
  at ? (../node_modules/@sentry/utils/esm/syncpromise.js:61:33)
  at Array.forEach(<anonymous>)
  at _executeHandlers(../node_modules/@sentry/utils/esm/syncpromise.js:55:28)
  at _attachHandler(../node_modules/@sentry/utils/esm/syncpromise.js:46:19)
  at executor(../node_modules/@sentry/utils/esm/syncpromise.js:126:19)
  at new e(../node_modules/@sentry/utils/esm/syncpromise.js:73:13)
  at then(../node_modules/@sentry/utils/esm/syncpromise.js:125:16)
  at executor(../node_modules/@sentry/core/esm/baseclient.js:348:18)
  at new e(../node_modules/@sentry/utils/esm/syncpromise.js:73:13)
  at _processEvent(../node_modules/@sentry/core/esm/baseclient.js:346:16)
  at apply(../node_modules/@sentry/core/esm/baseclient.js:91:14)
  at _invokeClient(../node_modules/@sentry/hub/esm/hub.js:58:39)
  at captureEvent(../node_modules/@sentry/hub/esm/hub.js:184:14)
  at handler(../node_modules/@sentry/browser/esm/integrations/globalhandlers.js:61:28)
  at triggerHandlers(../node_modules/@sentry/utils/esm/instrument.js:77:17)
  at Q.onerror(../node_modules/@sentry/utils/esm/instrument.js:434:9)

This is how we're reporting exceptions:

    // error: Error, severityLevel: Severity, extras: { [key: string]: any } 
    withScope(scope => {
      scope.setExtras(extras)
      scope.setLevel(severityLevel)
      captureException(error)
    })

the21st avatar Aug 12 '20 10:08 the21st

We have thousand of errors like this with sentry/5.26.0/bundle.min.js (and previous versions) :

Converting circular structure to JSON
    --> starting at object with constructor 'HTMLDivElement'
    |     property 'jQuery3410215558066319917922' -> object with constructor 'Object'
    |     property 'datetimepicker' -> object with constructor 'k...

and

JSON.stringify cannot serialize cyclic structures.

and

cyclic object value

Why not stringify in a try catch block?

nicolasbadia avatar Oct 21 '20 14:10 nicolasbadia

I'm also seeing this error multiple times a day and it's making it difficult to keep track of legitimate issues.

If anyone has advice on how to filter these out , that would be greatly appreciated!

Nevario avatar Jun 14 '21 21:06 Nevario

This is the only error getting reported for me whenever legitimate exceptions are triggered.

xmunoz avatar Jun 14 '21 22:06 xmunoz

Sorry you all are having trouble! I have a few questions:

  • Is this still happening in the latest version (which, as of now, is 6.7.1)?

  • Are any of you able to reproduce this behavior?

  • If so, what does the event look like right before this step? Do you know where in the event object this is occurring?

Also, it would be great if one of you could post a link to an example of such an error in Sentry.

We (theoretically) eliminate any cycles before we get to the jsonifying step.

lobsterkatie avatar Jun 16 '21 23:06 lobsterkatie

Hi @lobsterkatie,

Is this still happening in the latest version (which, as of now, is 6.7.1)?

Yes. I am using 6.7.1 and last received a new TypeError JSON.stringify() 2 hours ago.

Are any of you able to reproduce this behavior?

Yes, I can reproduce the error by submitting a particular form within my application... No errors are generated in the browser console.

If so, what does the event look like right before this step? Do you know where in the event object this is occurring?

Does this help?

    --> starting at object with constructor 'HTMLInputElement'
    |     property 'jQuery224046174201088114542' -> object with constructor 'Object'
    |     property 'bv.messages' -> object with constructor 'n.f...
  at JSON.stringify(<anonymous>)
  at eventToSentryRequest(../../core/src/request.ts:66:16)
  at sendEvent(../../browser/src/transports/fetch.ts:93:30)
  at sendEvent(../../core/src/basebackend.ts:94:26)
  at _sendEvent(../../core/src/baseclient.ts:449:24)
  at _sendEvent(../../browser/src/client.ts:76:11)
  at onfulfilled(../../core/src/baseclient.ts:537:14)
  at onfulfilled(../../utils/src/syncpromise.ts:102:21)
  at ? (../../utils/src/syncpromise.ts:228:19)
  at Array.forEach(<anonymous>)
  at _executeHandlers(../../utils/src/syncpromise.ts:220:20)
  at _attachHandler(../../utils/src/syncpromise.ts:208:10)
  at executor(../../utils/src/syncpromise.ts:92:12)
  at new t(../../utils/src/syncpromise.ts:34:7)
  at then(../../utils/src/syncpromise.ts:91:12)
  at _processEvent(../../core/src/baseclient.ts:527:8)
  at _captureEvent(../../core/src/baseclient.ts:459:17)
  at method(../../core/src/baseclient.ts:143:12)
  at _invokeClient(../../hub/src/hub.ts:475:23)
  at captureEvent(../../hub/src/hub.ts:250:10)
  at handler(../../browser/src/integrations/globalhandlers.ts:105:20)
  at triggerHandlers(../../utils/src/instrument.ts:101:7)
  at ft.onerror(../../utils/src/instrument.ts:608:5)
  at Object.trigger(/js/jquery-2.2.4.min.js:4:4869)
  at HTMLFormElement.<anonymous>(/js/jquery-2.2.4.min.js:4:5328)
  at Function.each(/js/jquery-2.2.4.min.js:2:2861)
  at n.fn.init.each(/js/jquery-2.2.4.min.js:2:845)
  at n.fn.init.trigger(/js/jquery-2.2.4.min.js:4:5304)
  at b._submit(/js/combined.min.js:1:305795)
  at b.validate(/js/combined.min.js:1:310007)
  at HTMLFormElement.<anonymous>(/js/combined.min.js:1:298341)
  at HTMLFormElement.dispatch(/js/jquery-2.2.4.min.js:3:7537)
  at HTMLFormElement.r.handle(/js/jquery-2.2.4.min.js:3:5620)
  at Object.trigger(/js/jquery-2.2.4.min.js:4:4818)
  at HTMLFormElement.<anonymous>(/js/jquery-2.2.4.min.js:4:5328)
  at Function.each(/js/jquery-2.2.4.min.js:2:2861)
  at n.fn.init.each(/js/jquery-2.2.4.min.js:2:845)
  at n.fn.init.trigger(/js/jquery-2.2.4.min.js:4:5304)
  at saveProject(/js/foxoms/appHelper.min.js:1:2750)
  at HTMLAnchorElement.onclick(/project/view/28858:1:1)

Also, it would be great if one of you could post a link to an example of such an error in Sentry.

Sentry Issue link

Thanks for looking into this!

Tim

Nevario avatar Jun 16 '21 23:06 Nevario

Thanks.

Yes, I saw that stacktrace in the original post, and it makes sense that that's where the problem is happening. And sorry for being unclear, but I was actually hoping that since you can reproduce it, you could stick a debugger in your code right before the crash and look at the event object and figure out where the circular reference is and post that part of the object.

lobsterkatie avatar Jun 17 '21 14:06 lobsterkatie

I don't really know anything about Sentry so I don't think I'm going to be much help with debugging this. However I can send you a screen recording and username / password to our SAAS where you can investigate further.

I'll post those details in the support ticket I opened with Sentry.

Nevario avatar Jun 17 '21 23:06 Nevario

We've been running into this error lately, and the bad part is, it supercedes the actual error we're trying to capture. So users are running into bugs in our app, and we can't get the full context.

Here's a link to such an issue (only Sentry support will be able to access it): https://sentry.io/organizations/reelcrafter/issues/2465876224/?project=1384460&query=is%3Aunresolved

Public link for everyone else: https://sentry.io/share/issue/4693c970ebf84ffca15a3403e18e53eb/

In our case, we're passing a Vue component's entire $data variable, which is basically all the reactive variables that belong to a component. Taking some responsibility here, I should probably use something like safe-json-stringify to stringify that myself, then JSON.parse() it before passing it to Sentry, so Sentry can format it nicely.

    Vue.prototype.$reportError = function (
      exception: any,
      options: ReportErrorOptions,
      metadata?: Record<string, any>
    ) {
      reportError(exception, options, {
        ...metadata,
        component: this.$options.name,
        componentProps: JSON.parse(safeJsonStringify(this.$props)),
        componentData: JSON.parse(safeJsonStringify(this.$data)),
      });
    }

I'll try this going forward, though ideally, Sentry should probably perform a safe stringify internally to save us the step. πŸ™‚

ffxsam avatar Jun 20 '21 22:06 ffxsam

I did more debugging on why we're seeing this issue and it's related to usage of the cookies package. I believe the Cookies object in that package has some circular references.

xmunoz avatar Jun 21 '21 01:06 xmunoz

I'll try this going forward, though ideally, Sentry should probably perform a safe stringify internally to save us the step. πŸ™‚

We do that for breadcrumbs, user, extra, and contexts. Would be great to catch which field is not normalized. @ffxsam would you be able to use beforeSend or any other mechanism to pinpoint which event field is causing a trouble here?

kamilogorek avatar Jun 21 '21 10:06 kamilogorek

@kamilogorek So, to continue showing that path from the code above:

The object in the 3rd argument above gets passed into reportError() which starts like this:

export default function reportError(
  exception: any,
  options: ReportErrorOptions,
  metadata?: Record<string, any>
) {
  let extra = metadata || {};

and then:

  Sentry.setContext('clientData', {
    ...extra,
    rawException: exception,
    while: options.while,
  });

So, indeed, it's the context that's not being safe-stringified, it looks like.

ffxsam avatar Jun 21 '21 22:06 ffxsam

That is really odd, as we definitely normalize those, see: https://github.com/getsentry/sentry-javascript/blob/master/packages/core/src/baseclient.ts#L331-L393 πŸ€”

kamilogorek avatar Jun 22 '21 09:06 kamilogorek

Though, the error is happening in a JSON.stringify call within https://github.com/getsentry/sentry-javascript/blob/master/packages/core/src/request.ts

Hmm, so maybe it's not the context.

ffxsam avatar Jun 22 '21 15:06 ffxsam

We're using the node SDK, here is where the issue is being triggered: Captura de pantalla de 2021-06-22 11-04-45

xmunoz avatar Jun 22 '21 16:06 xmunoz

That's an accurate assessment, however, eventToSentryRequest is never called by anything other than sendEvent, which in turn is only called by captureEvent. So there's no flow available in the SDK that'd omit this serialization, and that's why I'm kinda confused and not sure where the issue originates from.

kamilogorek avatar Jun 23 '21 09:06 kamilogorek

Let us know if there's some way we can help pinpoint the issue!

ffxsam avatar Jun 23 '21 17:06 ffxsam

What I did to debug https://github.com/getsentry/sentry-javascript/pull/3727 is:

  1. Get reproducible error scenario
  2. Add Sentry.addGlobalEventProcessor(event => { debugger; })
  3. Trigger error
  4. Call JSON.stringify(error) and trace the source of circular reference

Once we have the source, we'll be able to reproduce and patch it easily. The biggest problem now is actually knowing the source of that reference.

kamilogorek avatar Jun 25 '21 13:06 kamilogorek

I'll try that out next time I get some free time, unless someone beats me to it!

ffxsam avatar Jun 25 '21 15:06 ffxsam

I can't seem to replicate it now, but our users in production sure can. πŸ˜•

The last error was interesting, actually. The error was properly caught (it was an S3 timeout error): https://sentry.io/share/issue/ae3a5367cac143418d35c642017dc48a/

But it also generated a separate "circular structure" issue, too. There must've been something in the S3 error that it didn't like, just not sure what. I'm fairly certain it wasn't anything I was setting in the context or extra.

ffxsam avatar Jun 30 '21 06:06 ffxsam

@ffxsam there's one more thing you can try (which is basically what https://github.com/getsentry/sentry-javascript/pull/3776 will provide under the hood)

import { normalize } from '@sentry/utils';

Sentry.init({
  beforeSend(event) {
    return normalize(event);
  }
});

kamilogorek avatar Jul 02 '21 07:07 kamilogorek

The above fix worked for me.

That said, I've attached the json stacktrace from Sentry. Does this help you track down the root cause? sentry.json.log

xmunoz avatar Jul 08 '21 21:07 xmunoz

Unfortunately, there's nothing that'd point to the malformed data in this JSON log.

kamilogorek avatar Jul 12 '21 09:07 kamilogorek

If someone stumbles upon this issue, starting v6.9.0 you can use an internal option ensureNoCircularStructures that will do exactly what my snippet above does. As mentioned, it's an experiment, and internal, so whenever you update your SDK past v6.9.0, make sure that it's still available.

Sentry.init({
  _experiments: {
    ensureNoCircularStructures: true
  }
})

This should allow the malformed events to be delivered to Sentry and should allow you to see some [Circular ~] references in the data UI.

kamilogorek avatar Jul 12 '21 09:07 kamilogorek

I've updated to 6.9.0 and set ensureNoCircularStructures: true like above guidance, but still having these errors:

Mostly: Screenshot 2021-07-24 153048

Sometimes:

Screenshot 2021-07-24 153052

Ben-Mack avatar Jul 24 '21 08:07 Ben-Mack

@Ben-Mack - Can you please link to the above issues in Sentry?

lobsterkatie avatar Jul 26 '21 18:07 lobsterkatie

Still seeing this in production:

https://sentry.io/share/issue/608a9cbdf67f4631b3ea5fdd8e93cfbd/

Sentry is definitely pointing to Sentry as the culprit. πŸ˜„ I'll try the ensureNoCircularStructures: true trick above when I get a few minutes, and see if we stop seeing the issue.

ffxsam avatar Jul 28 '21 15:07 ffxsam

@ffxsam - thanks for the link. I took a look and the

Converting circular structure to JSON
    --> starting at object with constructor 'constructor'
    |     property 'response' -> object with constructor 'constructor'
    --- property 'request' closes the circle

there definitely seems to indicate that you have this structure:

request: {
  response: {
    request: <pointer to grandparent object>
  }
}

somewhere in your event object. As Kamil has said, if that’s coming from the fact that you add $data and $props to your error context, it should get put through the serializer and not be a problem. Indeed, in my testing, that's exactly what happens:

image

If, however, I add the same structure to my event after the processors have run (in my case, in beforeSend), I (predictably) get this error:

image

Assuming you're not doing that, nothing in our code should allow this to happen. I have a (possible?) theory, though.

I found and looked at the other issue you linked, earlier in the thread (the AWS error), and happened to spot this:

image

and it reminded me that LogRocket used our SDK as the basis for theirs. If you're using both, I wonder if some weird conflict is happening between the two as they both try to monkeypatch the same globals, etc, which is mixing things up somehow. I don't totally get how their code would affect an object created by us - this is definitely grasping-at-straws territory - but something fishy is certainly happening...

Let us know if you pinpoint the location of the bad data and we can go from there.

lobsterkatie avatar Jul 28 '21 18:07 lobsterkatie

This issue has gone three weeks without activity. In another week, I will close it.

But! If you comment or otherwise update it, I will reset the clock, and if you label it Status: Backlog or Status: In Progress, I will leave it alone ... forever!


"A weed is but an unloved flower." ― Ella Wheeler Wilcox πŸ₯€

github-actions[bot] avatar Oct 14 '21 12:10 github-actions[bot]

I'm still using the @sentry/utils work-around mentioned above. I would love to see a real resolution for this.

xmunoz avatar Oct 14 '21 13:10 xmunoz

@xmunoz you can remove @sentry/utils and use this config instead:

Sentry.init({
  _experiments: {
    ensureNoCircularStructures: true
  }
})

I'll close the issue, as it seems like there is a working solution. I'd prefer someone to create a new issue with a fresh description if it's still an issue. Please do not hesitate to ping me if it is still relevant, and I will happily reopen it. Cheers!

kamilogorek avatar Oct 14 '21 13:10 kamilogorek