I recently discovered that Sentry's source code is open source and thought it would be fun to look into it and see if I can learn anything from it. In this post, I'll outline some of the noteworthy code-related things I've noticed in the Sentry repository.
Just as a bit of context on the Sentry repository: it is a monorepo that holds their Django (python) API and their React web app.
Sentry has detailed reasoning for why they've opted to open source their code. They seem to really believe in it and going as far as to say "there is no other choice but open source for building a viable software company serving modern development cycles".
Sentry has licensing in place to legally prevent other companies from reselling Sentry. Apart from that, you can pretty much freely use the code they've written, which is awesome.
Mark migrations as "dangerous"
In the database migration files, there is a boolean property called is_dangerous
that you can mark as True
to indicate that a migration should be run manually by a team member. They recommend setting this to True
for large data migrations, adding columns to highly active tables, or whenever you're in doubt.
1class Migration(migrations.Migration):2 is_dangerous = True
Docker image size reduction trick
Sentry uses massively long RUN
commands that are essentially a bunch of commands all bundled into a single command using the &&
operator. This is a trick that people do to keep their Docker image sizes down since every RUN
command in a Dockerfile adds an extra layer to the Docker image, thus increasing the size of the image.
Sentry could leverage multi-stage builds to help further reduce their Docker image size.
Bootstrap scripts running before React app renders
Sentry has a bootstrapping script that runs to load some configuration data along with locale/translation-related data BEFORE even rendering the React application. This must be some important data that the web app could need to know what it should show the user on the initial page load.
Code comments explaining how the app loads
Here's a look at the config object that Sentry fetches during the bootstrapping process:
1export interface Config {2 theme: 'light' | 'dark';3 languageCode: string;4 csrfCookieName: string;5 features: Set<string>;6 singleOrganization: boolean;7 urlPrefix: string;8 needsUpgrade: boolean;9 supportEmail: string;10 user: User;1112 invitesEnabled: boolean;13 privacyUrl: string | null;14 isOnPremise: boolean;15 lastOrganization: string | null;16 gravatarBaseUrl: string;1718 /**19 * This comes from django (django.contrib.messages)20 */21 messages: {message: string; level: string}[];22 dsn: string;23 userIdentity: {ip_address: string; email: string; id: string; isStaff: boolean};24 termsUrl: string | null;25 isAuthenticated: boolean;26 version: {27 current: string;28 build: string;29 upgradeAvailable: boolean;30 latest: string;31 };32 sentryConfig: {33 dsn: string;34 release: string;35 whitelistUrls: string[];36 };37 distPrefix: string;38 apmSampling: number;39 dsn_requests: string;40 demoMode: boolean;41 statuspage?: {42 id: string;43 api_host: string;44 };45}
The config is fetched using an API call to their server.
1const BOOTSTRAP_URL = '/api/client-config/';23/* Loads config into global window object */4async function bootWithHydration() {5 const response = await fetch(BOOTSTRAP_URL);6 const data: Config = await response.json();78 window.__initialData = data;910 return bootApplication(data);11}
An example of why the config data is being fetched before the React app starts rendering is the theme
value that holds the user's preference for light
or dark
theme. If the theme
is fetched before starting to render the UI, then the UI can render the correct theme on the first try.
It's not uncommon for apps not to incorrectly handle this situation, which causes the app to initially render in the "default" theme and then quickly switch to the correct user-chosen theme. This "theme flashing" phenomena is well explained by Josh Comeau in this blog post.
CSS written with LESS
LESS is a CSS preprocessor that lets you write out your styles in a less verbose syntax than pure CSS. I'm surprised to see Sentry using LESS since it seems to be much less (pun intended) popular than SASS.
If you're interested in which preprocessor is better (LESS or SASS), then you can read this article from CSS tricks which compares the feature sets of both preprocessors.
SPA and server-rendered mode
Sentry can either load the web application using server-side rendering via Django templates (see base react component in the server templates) or it can load the web app in SPA mode, which is purely client-side (see entrypoint file).
Based on this code comment, it seems as though Sentry would run in SPA mode when running the app locally in development or when loading the app for deploy previews (and it looks like they use Vercel for their deploy previews).
EJS for web app entrypoint
Instead of the typical index.html
file you see as the entrypoint file for the web app, Sentry uses an EJS file named index.ejs
which allows for some cool things like conditional logic and loops to be used to generate HTML.
As an example, here is a loop used to set some values on the global window
object:
1<% if (htmlWebpackPlugin.options.window) { %>2<script>3 <% for (var varName in htmlWebpackPlugin.options.window) { %>4 window['<%=varName%>'] = <%= JSON.stringify(htmlWebpackPlugin.options.window[varName]) %>;5 <% } %>6 </script>7<% } %>
Scary long webpack configuration file
Webpack config file is at 579 lines of code. Would take a long time to try to figure out what each step of the config does.
Custom Sentry font
All the cool kids have their own font these days. Not sure if it's a font their created or just one they've purchased and renamed.
Loading animation/message while bootstrapping app
While the web app is being bootstrapped, there is a fallback UI that displays a loading animation to the user. This is a nice user experience and the loading message is likely to make a few developers smile:
Please wait while we load an obnoxious amount of JavaScript.
Whitelist files in .dockerignore
The .dockerignore
file (used to tell Docker which files to ignore when creating an image) ignores everything and whitelists what shouldn't be ignored. This seems like a good strategy to take since you don't have to remember to keep the .dockerignore
file up-to-date with files you want to be ignored.
1# Ignore everything2*34# List of things that shouldn't be ignored5!/docker6!/package.json7!/yarn.lock8!/dist/requirements.txt9!/dist/*.whl
Non-normalized global store
First, of all, Sentry uses Reflux for their global state management in their React web application. This is the first I've heard of this library, but it seems similar to Redux and follows the Flux pattern (i.e. actions get dispatched that result in the store being updated).
I find it interesting that they've opted not to normalize their data in their global stores. I've found data hard to manage when there is not a normalized store, but it seems to serve Sentry's purposes well.
If they had normalized their store, they could more efficiently retrieve/update entities in their store by IDs, but instead, they need to loop through every item in an array to retrieve/update the right one.
For example, here is how they get an "event" by id
from their global store:
1get(id) {2 for (let i = 0; i < this.items.length; i++) {3 if (this.items[i].id === id) {4 return this.items[i];5 }6 }7 return undefined;8},
The same pattern is used when trying to find a "member" by id.
AsyncComponent
Sentry's higher-level components are called "views" and many of them are built using a custom build AsyncComponent
class component which has logic to display a loading indicator while data is being fetched as well properly handle errors whenever they are encountered.
Animations with framer-motion
framer-motion is used to handle some animations/transitions in the UI. I've heard of the library before, but I've never been aware of an app that was using it.
Here is their Toast
component that uses framer-motion:
1const Toast = styled(motion.div)`2 display: flex;3 align-items: center;4 height: 40px;5 padding: 0 15px 0 10px;6 margin-top: 15px;7 background: ${p => p.theme.gray500};8 color: #fff;9 border-radius: 44px 7px 7px 44px;10 box-shadow: 0 4px 12px 0 rgba(47, 40, 55, 0.16);11 position: relative;12`;1314Toast.defaultProps = {15 initial: {16 opacity: 0,17 y: 70,18 },19 animate: {20 opacity: 1,21 y: 0,22 },23 exit: {24 opacity: 0,25 y: 70,26 },27 transition: testableTransition({28 type: 'spring',29 stiffness: 450,30 damping: 25,31 }),32};
Those are some of the noteworthy things that popped out to me while analyzing the Sentry codebase. If there are any other open-source web apps that you find interesting let me know on Twitter since I quite enjoy learning from other projects. I found this awesome-selfhosted repo that lists out a lot of potentially good open source projects that could be interesting to look into.