Avatar

Hej, I'm Julia.

Creating a Sentry Logging Module to Set Context in a React Native App

#react-native #sentry #typescript

3 min read

We experienced a real puzzler of a bug last week. We had just released a massive new feature, and over the next two days, had gotten a number of users emailing in saying they couldn’t proceed at a certain point of the flow (a button that should have been pressable remained disabled). Looking at the analytics, the data agreed with what the emailers were saying, and the numbers experiencing the issue were not insignificant.

Unfortunately, when we set about to solve the issue, no one in our engineering team, or the wider team could replicate the bug. Reading through the code didn’t surface any obvious errors either, and we were stumped. So what’s one to do next? 🤔

Get more information.

Our app’s logging capabilities up until this point had been woeful. We were tracking app errors with Sentry but nothing much else besides that. This meant that we were not able to surface any information to do with our bug above, as the app was not crashing per se. I therefore created a custom logging module to allow us to raise custom errors in Sentry, wherever we needed to understand the context around why users might be experiencing certain issues (rather than just relying on Sentry’s automatic error tracking when the app crashes).

Here’s what my logging module looks like. Note that it uses the concept of Sentry’s custom scope and context which you can read more about here. I found the official documents a little confusing, and it took a couple of tries before I started to see my custom logs show up, so I thought I’d write this up for others who might also be struggling.

Logging module

import * as Sentry from '@sentry/browser'

enum EFeature1Context {
  EXAMPLE_CONTEXT_1 = 'EXAMPLE_CONTEXT_1',
  EXAMPLE_CONTEXT_2 = 'EXAMPLE_CONTEXT_2',
}

enum EFeature2Context {
  EXAMPLE_CONTEXT_3 = 'EXAMPLE_CONTEXT_3',
}

export const contextNames = {
  ...EFeature1Context,
  ...EFeature2Context,
}

export type TContextName = keyof typeof contextNames
export type TFeatureName = 'feature_1' | 'feature_2'

export const logLevel = Sentry.Severity

export function log(
  featureName: TFeatureName,
  errorMessage: string,
  contextName: TContextName,
  properties: object,
  level: Sentry.Severity = Sentry.Severity.Error,
) {
  Sentry.setContext(contextName, properties)

  Sentry.withScope((scope) => {
    scope.setTag('feature', featureName)
    scope.setLevel(level)
    Sentry.captureException(new Error(errorMessage))
  })
}

export default {
  log,
}

The route I went down was to create a log method, that takes a number of arguments, which are passed into Sentry’s setContext and withScope methods. The idea is to first set up the Sentry context which includes an object which you can stuff with whatever information you need for debugging. I then created a custom scope to set a tag (making it easily searchable within Sentry), the error level (e.g. info, warning, error, critical etc. which you can get from the Sentry.Severity list that comes inbuilt with the Sentry package) and the error message.

Note that the separation of context names into different enums is for convenience and helping to keep track of the various contexts you might be setting if your app is a large one, and you’re using Typescript.

To use the Logging module in your app, you’d just need to add this line of code wherever you want to log additional context.

Logging.log(
  'feature_1',
  'example_error_message',
  contextNames.EXAMPLE_CONTEXT_1,
  {
    user_type: 'abc',
    country: 'uk',
    screen: 'screen_name_example',
  },
  logLevel.Error,
)

© 2016-2024 Julia Tan · Powered by Next JS.