Skip to content
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

OTLPTraceExporter from exporter-trace-otlp-http exporting traces Base64 encoded in React Native #5178

Closed
Dsalvat596 opened this issue Nov 19, 2024 · 10 comments · Fixed by #5193
Assignees
Labels
bug Something isn't working needs:reproducer This bug/feature is in need of a minimal reproducer pkg:otlp-exporter-base triage

Comments

@Dsalvat596
Copy link

What happened?

Steps to Reproduce

Expected Result

Traces exported to collector endpoint in JSON format

Actual Result

Traces are being Base64 encoded via the OTLPTraceExporter, resulting in 400 response as endpoint expects JSON

Additional Details

When recreating this implementation of @optentelemetry in a React web app, traces are exported successfully in JSON format.

@opentelemetry DiagConsoleLogger indicates "stack":"ReferenceError: Property 'TextEncoder' doesn't exist\n at serializeRequest.

Importing the text-encoding polyfill clears this error and allows traces to be sent, but in the wrong format.

OpenTelemetry Setup Code

import 'text-encoding'; // Import the polyfill
import {
  CompositePropagator,
  W3CBaggagePropagator,
  W3CTraceContextPropagator,
} from '@opentelemetry/core';
import { WebTracerProvider } from '@opentelemetry/sdk-trace-web';
import {
  SimpleSpanProcessor,
  ConsoleSpanExporter,
} from '@opentelemetry/sdk-trace-base';
import { registerInstrumentations } from '@opentelemetry/instrumentation';
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { DiagConsoleLogger, DiagLogLevel, diag } from '@opentelemetry/api';
import { useEffect, useState } from 'react';
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG);

const GRAFANA_OTLP_ENDPOINT = '<my-trace-collector-url>`;
const SERVICE_NAME = 'my-react-native-app`;


const Tracer = async () => {
  const resource = new Resource({
    [ATTR_SERVICE_NAME]: SERVICE_NAME,
  });
  const provider = new WebTracerProvider({ resource });

  provider.addSpanProcessor(
    new SimpleSpanProcessor(
      new OTLPTraceExporter({
        url: GRAFANA_OTLP_ENDPOINT,
      })
    )
  );


  provider.addSpanProcessor(new SimpleSpanProcessor(new ConsoleSpanExporter()));

  provider.register({
    propagator: new CompositePropagator({
      propagators: [
        new W3CBaggagePropagator(),
        new W3CTraceContextPropagator(),
      ],
    }),
  });

  registerInstrumentations({
    tracerProvider: provider,
    instrumentations: [
      getWebAutoInstrumentations({
        '@opentelemetry/instrumentation-user-interaction': { enabled: false },
        '@opentelemetry/instrumentation-document-load': { enabled: false },
        '@opentelemetry/instrumentation-fetch': {
          propagateTraceHeaderCorsUrls: /.*/,
          clearTimingResources: false,
        },
      }),
    ],
  });
};

export interface TracerResult {
  loaded: boolean;
}

export const useTracer = (): TracerResult => {
  const [loaded, setLoaded] = useState<boolean>(false);

  useEffect(() => {
    if (!loaded) {
      Tracer()
        .catch(() => console.warn('failed to setup tracer'))
        .finally(() => setLoaded(true));
    }
  }, [loaded]);

  return {
    loaded,
  };
};

package.json

{
  "name": "my-app",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    ....
  },
  "dependencies": {
    "@apollo/client": "3.5.10",
    "@eva-design/eva": "^2.2.0",
    "@expo/react-native-action-sheet": "^4.0.1",
    "@gorhom/bottom-sheet": "^4.6.0",
    "@grafana/faro-react": "^1.11.0",
    "@grafana/faro-web-tracing": "^1.11.0",
    "@opentelemetry/api": "^1.9.0",
    "@opentelemetry/auto-instrumentations-web": "^0.42.0",
    "@opentelemetry/exporter-collector": "^0.25.0",
    "@opentelemetry/exporter-trace-otlp-http": "^0.54.0",
    "@opentelemetry/instrumentation": "^0.54.0",
    "@opentelemetry/sdk-trace-base": "^1.27.0",
    "@opentelemetry/sdk-trace-web": "^1.27.0",
    "@react-native-async-storage/async-storage": "1.21.0",
    "@react-native-community/checkbox": "^0.5.17",
    "@react-native-community/netinfo": "11.1.0",
    "@react-navigation/bottom-tabs": "^6.2.0",
    "@react-navigation/native": "^6.0.2",
    "@react-navigation/native-stack": "^6.1.0",
    "@sentry/react-native": "~5.33.1",
    "@ui-kitten/components": "^5.1.1",
    "@ui-kitten/eva-icons": "^5.1.1",
    "@ui-kitten/template-ts": "^5.1.2",
    "date-fns": "^2.30.0",
    "expo": "~50.0.19",
    "expo-application": "~5.8.4",
    "expo-auth-session": "~5.4.0",
    "expo-checkbox": "^3.0.0",
    "expo-clipboard": "~5.0.1",
    "expo-constants": "~15.4.6",
    "expo-crypto": "~12.8.1",
    "expo-dev-client": "~3.3.12",
    "expo-device": "~5.9.4",
    "expo-local-authentication": "~13.8.0",
    "expo-location": "~16.5.5",
    "expo-notifications": "~0.27.8",
    "expo-splash-screen": "~0.26.5",
    "expo-status-bar": "~1.11.1",
    "expo-updates": "~0.24.13",
    "formik": "^2.4.5",
    "geolib": "^3.3.4",
    "graphql": "^16.3.0",
    "graphql-ws": "^5.11.2",
    "jwt-decode": "^3.1.2",
    "lodash": "^4.17.21",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-native": "0.73.6",
    "react-native-animatable": "^1.4.0",
    "react-native-circular-progress-indicator": "^4.4.2",
    "react-native-collapsible": "^1.6.1",
    "react-native-error-boundary": "^1.1.13",
    "react-native-gesture-handler": "~2.14.0",
    "react-native-google-places-autocomplete": "^2.5.6",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-loading-spinner-overlay": "^2.0.0",
    "react-native-map-link": "^2.11.4",
    "react-native-maps": "1.10.0",
    "react-native-maps-directions": "^1.9.0",
    "react-native-modal": "^13.0.1",
    "react-native-paper": "^4.11.2",
    "react-native-qrcode-svg": "^6.3.0",
    "react-native-reanimated": "~3.6.2",
    "react-native-safe-area-context": "4.8.2",
    "react-native-safearea-height": "^1.0.6",
    "react-native-screens": "~3.29.0",
    "react-native-svg": "14.1.0",
    "react-native-svg-animated-linear-gradient": "^0.4.0",
    "react-native-toast-message": "^2.1.5",
    "react-native-turbo-mock-location-detector": "^2.3.1",
    "react-native-vector-icons": "^9.0.0",
    "sentry-expo": "~7.2.0",
    "styled-components": "^5.3.1",
    "text-encoding": "^0.7.0",
    "yup": "^0.32.11"
  },
  "devDependencies": {
    "@babel/core": "^7.12.9",
    "@babel/runtime": "^7.15.3",
    "@react-native-community/eslint-config": "^3.2.0",
    "@testing-library/jest-native": "^5.4.2",
    "@testing-library/react-native": "^12.2.2",
    "@types/jest": "^29.5.3",
    "@types/react-test-renderer": "^18.0.0",
    "@types/styled-components": "^5.1.14",
    "@types/styled-components-react-native": "^5.1.1",
    "@typescript-eslint/eslint-plugin": "^6.7.5",
    "@typescript-eslint/parser": "^6.7.5",
    "babel-jest": "^26.6.3",
    "babel-plugin-module-resolver": "^4.1.0",
    "eslint": "^8.51.0",
    "eslint-plugin-react": "^7.32.2",
    "husky": "^8.0.3",
    "jest": "^29.6.2",
    "lint-staged": "^13.1.2",
    "metro-react-native-babel-preset": "^0.66.2",
    "patch-package": "^8.0.0",
    "prettier": "^2.8.7",
    "react-devtools": "^5.0.0",
    "react-test-renderer": "18.2.0",
    "reactotron-react-native": "^5.0.3",
    "typescript": "^5.3.0"
  },
  "resolutions": {
    "@types/react": "^18"
  },
  "jest": {
    "preset": "react-native",
    "setupFilesAfterEnv": [
      "@testing-library/jest-native/extend-expect"
    ],
    "moduleFileExtensions": [
      "ts",
      "tsx",
      "js",
      "jsx",
      "json",
      "node"
    ]
  },
  "lint-staged": {
    "src/**/*.{ts,tsx}": [
      "prettier --write",
      "eslint --fix"
    ]
  }
}

Relevant log output

## Traces being created on the client, 
DEBUG  items to be sent [{"_attributeValueLengthLimit": Infinity, "_droppedAttributesCount": 0, "_droppedEventsCount": 0, "_droppedLinksCount": 0, "_duration": [1, 172728833], "_ended": true, "_performanceOffset": NaN, "_performanceStartTime": 345362964.276208, "_spanContext": {"spanId": "9bddc617a3821ea9", "traceFlags": 1, "traceId": "a31d5b512aa2d966ecfd093da35d11f8", "traceState": undefined}, "_spanLimits": {"attributeCountLimit": 128, "attributePerEventCountLimit": 128, "attributePerLinkCountLimit": 128, "attributeValueLengthLimit": Infinity, "eventCountLimit": 128, "linkCountLimit": 128}, "_spanProcessor": {"_spanProcessors": [Array]}, "_startTimeProvided": false, "attributes": {"example-attribute": "example-value"}, "endTime": [1732027524, 681728833], "events": [], "instrumentationLibrary": {"name": "default", "schemaUrl": undefined, "version": undefined}, "kind": 0, "links": [], "name": "example-span", "parentSpanId": undefined, "resource": {"_asyncAttributesPromise": undefined, "_attributes": [Object], "_syncAttributes": [Object], "asyncAttributesPending": false}, "startTime": [1732027523, 509000000], "status": {"code": 0}}]



## Request payload in XHR  (base64 encoded)
eyJyZXNvdXJjZVNwYW5zIjpbeyJyZXNvdXJjZSI6eyJhdHRyaWJ1dGVzIjpbeyJrZXkiOiJzZXJ2aWNlLm5hbWUiLCJ2YWx1ZSI6eyJzdHJpbmdWYWx1ZSI6InJlYWN0LW5hdGl2ZS1mdWVscGF5In19LHsia2V5IjoidGVsZW1ldHJ5LnNkay5sYW5ndWFnZSIsInZhbHVlIjp7InN0cmluZ1ZhbHVlIjoid2VianMifX0seyJrZXkiOiJ0ZWxlbWV0cnkuc2RrLm5hbWUiLCJ2YWx1ZSI6eyJzdHJpbmdWYWx1ZSI6Im9wZW50ZWxlbWV0cnkifX0seyJrZXkiOiJ0ZWxlbWV0cnkuc2RrLnZlcnNpb24iLCJ2YWx1ZSI6eyJzdHJpbmdWYWx1ZSI6IjEuMjcuMCJ9fV0sImRyb3BwZWRBdHRyaWJ1dGVzQ291bnQiOjB9LCJzY29wZVNwYW5zIjpbeyJzY29wZSI6eyJuYW1lIjoiZGVmYXVsdCJ9LCJzcGFucyI6W3sidHJhY2VJZCI6IjdmNWZlMzc5YzBkNzYzOTE1MzY4MWU4NzQ2MmJhMGFmIiwic3BhbklkIjoiN2U0MzI1NTZlZDM2N2M4NiIsIm5hbWUiOiJleGFtcGxlLXNwYW4iLCJraW5kIjoxLCJzdGFydFRpbWVVbml4TmFubyI6IjE3MzIwMjc1OTM0MjUwMDAwMDAiLCJlbmRUaW1lVW5peE5hbm8iOiIxNzMyMDI3NTk0NjExNzc3NzA5IiwiYXR0cmlidXRlcyI6W3sia2V5IjoiZXhhbXBsZS1hdHRyaWJ1dGUiLCJ2YWx1ZSI6eyJzdHJpbmdWYWx1ZSI6ImV4YW1wbGUtdmFsdWUifX1dLCJkcm9wcGVkQXR0cmlidXRlc0NvdW50IjowLCJldmVudHMiOltdLCJkcm9wcGVkRXZlbnRzQ291bnQiOjAsInN0YXR1cyI6eyJjb2RlIjowfSwibGlua3MiOltdLCJkcm9wcGVkTGlua3NDb3VudCI6MH1dfV19XX0=

Operating System and Version

macOS 15.1

Runtime and Version

react-native 0.73.6
expo ~50.0.19
iPhone 16 Pro Simulator iOS 18.0

@Dsalvat596 Dsalvat596 added bug Something isn't working triage labels Nov 19, 2024
@pichlermarc
Copy link
Member

Hi @Dsalvat596, thanks for reporting this.

Just to clarify: are you saying that this does work in a react web app, but does not work in react native? 🤔
Were you including that same polyfill when you tried it in the react web app?

@pichlermarc pichlermarc added needs:reproducer This bug/feature is in need of a minimal reproducer pkg:otlp-exporter-base labels Nov 19, 2024
@Dsalvat596
Copy link
Author

Hi @pichlermarc, thanks for responding.

Yes, it does work as expected in the react web app, no polyfill needed.

I should mention this is an Expo app, and the documentation here indicates The TextDecoder API is not [spec-compliant](https://encoding.spec.whatwg.org/#textdecoder) on native platforms. Only the UTF-8 encoding is supported. If you need support for more encodings, use a polyfill like [text-encoding](https://www.npmjs.com/package/text-encoding).

Im not sure if this issue is related or it is something specific to the exporter itself.

@pichlermarc
Copy link
Member

Hmm, that's weird.

Unfortunately I'm not really familiar with any of these tools and react-native itself - so it's difficult for me to figure out how to get started.. 😞 Would you be able to provide a simple repository that I can use to investigate this? 🤔

@jpmunz
Copy link

jpmunz commented Nov 20, 2024

hey @Dsalvat596, the react native app on the demo repo isn't merged yet but can be helpful to try and reproduce, I reduced its tracer setup a bit to match your code snippet and wasn't able to see the problem here: open-telemetry/opentelemetry-demo@fe81089

but then downgrading to RN v0.73.6 I do see that exception about TextEncoder raised: open-telemetry/opentelemetry-demo@a6ca5da

I'm not sure as to the root cause but this does appear to be due to an issue on that version of React Native (also was seeing it on 0.73.8). Is upgrading to a version >= 0.74 a possibility for you?

(I did this testing on Android btw)

@Dsalvat596
Copy link
Author

@pichlermarc @jpmunz

I didn't upgrade my RN version, but downgrading these packages:

    "@opentelemetry/auto-instrumentations-web": "^0.42.0",
    "@opentelemetry/exporter-collector": "^0.25.0",
    "@opentelemetry/exporter-trace-otlp-http": "^0.54.0",
    "@opentelemetry/instrumentation": "^0.54.0",
    "@opentelemetry/sdk-trace-base": "^1.27.0",
    "@opentelemetry/sdk-trace-web": "^1.27.0",

to

    "@opentelemetry/auto-instrumentations-web": "^0.40.0",
    "@opentelemetry/core": "^1.25.0",
    "@opentelemetry/exporter-trace-otlp-http": "^0.52.0",
    "@opentelemetry/resources": "^1.25.0",
    "@opentelemetry/sdk-trace-base": "^1.25.0",
    "@opentelemetry/sdk-trace-web": "^1.25.0",
    "@opentelemetry/semantic-conventions": "^1.27.0",

as per the demo,

Seemed to resolve the issue.

Thank you!

@jpmunz
Copy link

jpmunz commented Nov 20, 2024

Glad it worked!

Still unsure as to the underlying cause though, on this commit here I'm using those same downgraded versions but am able to reproduce the error: https://github.com/open-telemetry/opentelemetry-demo/blob/a6ca5da1392ca78acdb55dfd2d4dc65aec1b4c77/src/reactnativeapp/package.json

@jpmunz
Copy link

jpmunz commented Nov 21, 2024

Sorry forgot about the poly fill for text-encoding when I did my last test, yes I was seeing the same as you

To summarize:

  • React Native 0.73 needs the 'text-encoding' polyfill regardless of otel package versions used (0.74+ doesn't need it)
  • Even with the polyfill RN 0.73 using @opentelemetry/exporter-trace-otlp-http on >= 0.53.0 fails to send with that base64 text (fails to send on RN 0.74 too though with a different error)
  • @opentelemetry/exporter-trace-otlp-http 0.52.0 seems to work well on both RN versions

There was quite a few changes between these versions (experimental/v0.52.0...experimental/v0.53.0) but I would guess the relevant changes happened in #4743

I wasn't able to narrow down the issue to a specific line but my guess is there isn't anything wrong with the implementation itself but rather comes down to APIs being used that weren't before that end up having some differing or flawed implementation in React Native. E.g. it looks like UInt8Array started being used and could run up against things like facebook/react-native#41079 where more poly fills may be needed

I'll add a note to the demo app (open-telemetry/opentelemetry-demo#1781) and link to this issue, I think this falls in the bucket of the existing otel packages being possible to use in React Native but having to be careful of choosing the right versions / configurations and ultimately probably needing more explicit support for react native as a platform along with node and browser

@pichlermarc
Copy link
Member

@jpmunz hmm, I think it might actually be the changes from #4895 that are causing this. We're serializing to Unit8Array in 0.52.0 already (introduced in #4581) and if I read the above correctly, 0.52.0 works fine everywhere.

In #4895 I added code to make a Blob from that Uint8Array and that was first released in 0.53.0.
I don't recall the exact reason why I added it - I think I maybe made a mistake and it's actually not needed at all. Looking at the MDN docs it's fine to just pass a TypedArray 🤔

The error I get with 0.55.0 using the demo from open-telemetry/opentelemetry-demo#1781 is Creating blobs from 'ArrayBuffer' and 'ArrayBufferView' are not supported. When I modify the code in the node_modules to directly pass the Uint8Array instead of constructing a Blob the request succeeds.

I'll open a draft PR and will link it here.

@pichlermarc
Copy link
Member

pichlermarc commented Nov 22, 2024

#5193 is my attempt at tackling this - when I turn on debug logging the logs say that the xhr request succeeds.

Unfortunately I was not able to get the traces to show up in the OTel-demo's Jaeger instance (it also did not work with the original set of dependencies though, so maybe something's wrong with my setup?). 😞
Edit: It does show up in Jaeger. 🎉 I likely had something not configured properly before.
Edit 2: @jpmunz I would appreciate your review over at #5193 if you have time 🙂

@jpmunz
Copy link

jpmunz commented Nov 22, 2024

@pichlermarc awesome! Will take a look

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs:reproducer This bug/feature is in need of a minimal reproducer pkg:otlp-exporter-base triage
Projects
None yet
3 participants