-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(webapp): user-based Sentry attribution with tenant tags #3678
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
Open
d-cs
wants to merge
13
commits into
main
Choose a base branch
from
sentry-user-attribution
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
0bcef22
feat(webapp): attach tenant context to Sentry events
d-cs 84c381b
chore(webapp): trim sentry tenant attribution server-change to one-liner
d-cs 978b4e5
fix(webapp): address sentry tenant attribution review
d-cs c78e88f
refactor(webapp): user-based sentry attribution, no middleware DB lookup
d-cs b16902a
Merge branch 'main' into sentry-user-attribution
d-cs 6673298
fix(webapp): wrap sentry tenant processor registration in singleton()
d-cs 90d64a9
fix(webapp): give PAT-authenticated requests user.id in sentry events
d-cs 7c49164
test(webapp): cover createLoaderPATApiRoute tenant context enrichment
d-cs 253c5e0
test(webapp): replace stubs with real DB seed in PAT tenant-context test
d-cs 487f6e3
test(webapp): remove apiBuilderPAT.test.ts — CI lacks local postgres
d-cs c18cbc6
Merge branch 'main' into sentry-user-attribution
d-cs 9b72f93
chore(webapp): clarify sentry tenant-attribution changelog
d-cs 307d39d
fix(webapp): make sentry processor singleton actually deduplicate
d-cs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| --- | ||
| area: webapp | ||
| type: feature | ||
| --- | ||
|
|
||
| Stamp Sentry events with the signed-in user so "Users Impacted" counts individual humans, and enrich events with org / project / environment tags when that context is available (dashboard URLs, authenticated API requests). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
d-cs marked this conversation as resolved.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import { AsyncLocalStorage } from "node:async_hooks"; | ||
| import type { AuthenticatedEnvironment } from "./apiAuth.server"; | ||
|
|
||
| // All fields are optional. The middleware establishes an empty scope per | ||
| // request; entry points fill what they know: | ||
| // - URL-matching paths get the slug trio from the Express middleware (zero IO). | ||
| // - The `_app` layout adds `userId` for any authenticated request. | ||
| // - The env layout adds tenant IDs / env type after its own existing DB query. | ||
| // - API routes get the full set up-front from `authenticationResult.environment`. | ||
| export type TenantContext = { | ||
| userId?: string; | ||
| orgSlug?: string; | ||
| projectSlug?: string; | ||
| envSlug?: string; | ||
| orgId?: string; | ||
| projectId?: string; | ||
| projectRef?: string; | ||
| envId?: string; | ||
| envType?: "DEVELOPMENT" | "PREVIEW" | "STAGING" | "PRODUCTION"; | ||
| impersonating?: boolean; | ||
| }; | ||
|
|
||
| const storage = new AsyncLocalStorage<TenantContext>(); | ||
|
|
||
| export const tenantContext = { | ||
| run<T>(ctx: TenantContext, fn: () => T): T { | ||
| return storage.run(ctx, fn); | ||
| }, | ||
| get(): TenantContext | undefined { | ||
| return storage.getStore(); | ||
| }, | ||
| enrich(patch: Partial<TenantContext>): void { | ||
| const current = storage.getStore(); | ||
| if (current) Object.assign(current, patch); | ||
| }, | ||
| }; | ||
|
|
||
| export function tenantContextFromAuthEnvironment(env: AuthenticatedEnvironment): TenantContext { | ||
| return { | ||
| userId: env.orgMember?.userId, | ||
| orgSlug: env.organization.slug, | ||
| projectSlug: env.project.slug, | ||
| envSlug: env.slug, | ||
| orgId: env.organization.id, | ||
| projectId: env.project.id, | ||
| projectRef: env.project.externalRef, | ||
| envId: env.id, | ||
| envType: env.type, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import type { NextFunction, Request, Response } from "express"; | ||
| import { tenantContext, type TenantContext } from "./tenantContext.server"; | ||
|
|
||
| const URL_PATTERN = /^\/orgs\/([^/]+)(?:\/projects\/([^/]+)(?:\/env\/([^/]+))?)?/; | ||
|
|
||
| export type ParsedTenantPath = { | ||
| orgSlug: string; | ||
| projectSlug?: string; | ||
| envSlug?: string; | ||
| }; | ||
|
|
||
| // Pulls whatever tenant slugs are present in the URL. `/orgs/:o` returns the | ||
| // org alone; `/orgs/:o/projects/:p` adds the project; `/orgs/:o/projects/:p/env/:e` | ||
| // returns all three. Non-tenant paths (`/`, `/login`, `/admin/*`) return undefined. | ||
| export function parseTenantPath(pathname: string): ParsedTenantPath | undefined { | ||
| const match = pathname.match(URL_PATTERN); | ||
| if (!match) return undefined; | ||
| const [, orgSlug, projectSlug, envSlug] = match; | ||
| if (!orgSlug) return undefined; | ||
| return { | ||
| orgSlug, | ||
| ...(projectSlug ? { projectSlug } : {}), | ||
| ...(envSlug ? { envSlug } : {}), | ||
| }; | ||
| } | ||
|
|
||
| export function resolveTenantContextFromPath(pathname: string): TenantContext { | ||
| return parseTenantPath(pathname) ?? {}; | ||
| } | ||
|
|
||
| export type PathResolver = (pathname: string) => TenantContext; | ||
|
|
||
| export function createTenantContextMiddleware(resolver: PathResolver) { | ||
| // Always establish an ALS scope, even when the path carries no tenant | ||
| // slugs. Authenticated loaders (e.g. the `_app` layout) then enrich the | ||
| // same scope with `userId`, so non-tenant pages still get user attribution. | ||
| return function tenantContextMiddleware(req: Request, res: Response, next: NextFunction) { | ||
| tenantContext.run(resolver(req.path), () => next()); | ||
| }; | ||
| } | ||
|
|
||
| export const tenantContextMiddleware = createTenantContextMiddleware(resolveTenantContextFromPath); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| import type { Event, EventHint } from "@sentry/remix"; | ||
| import { tenantContext } from "../services/tenantContext.server"; | ||
|
|
||
| export function addTenantContextToEvent(event: Event, _hint: EventHint): Event { | ||
| const ctx = tenantContext.get(); | ||
| if (!ctx) return event; | ||
| return { | ||
| ...event, | ||
| // Only stamp user.id when we have a real user — keeps "Users Impacted" | ||
| // counting distinct humans rather than mixing in tenants. Events without | ||
| // a known user (e.g. unauthenticated paths) skip user attribution. | ||
| ...(ctx.userId ? { user: { ...event.user, id: ctx.userId } } : {}), | ||
| tags: { | ||
| ...event.tags, | ||
| ...(ctx.orgSlug ? { org_slug: ctx.orgSlug } : {}), | ||
| ...(ctx.projectSlug ? { project_slug: ctx.projectSlug } : {}), | ||
| ...(ctx.envSlug ? { env_slug: ctx.envSlug } : {}), | ||
| ...(ctx.orgId ? { org_id: ctx.orgId } : {}), | ||
| ...(ctx.projectId ? { project_id: ctx.projectId } : {}), | ||
| ...(ctx.projectRef ? { project_ref: ctx.projectRef } : {}), | ||
| ...(ctx.envId ? { environment_id: ctx.envId } : {}), | ||
| ...(ctx.envType ? { env_type: ctx.envType } : {}), | ||
| ...(ctx.impersonating ? { impersonating: "true" } : {}), | ||
| }, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.