Monitoring & analytics
The mental model before you execute
Once your app is live, two questions stop being optional: is it broken? and is anyone using it? A vendor app ships “working”, but it does not tell you when a checkout silently 500s for one customer, or which of your funnel pages quietly loses half its visitors. Two different tools answer those two questions — error monitoring (Sentry) and web analytics — and confusing them is the most common beginner mistake. This guide builds the mental model so the setup phases make sense when you run them; it does not run commands for you, the playbooks do that.
Why it matters
Section titled “Why it matters”Without either tool you are flying blind in two distinct ways. Error monitoring is your smoke alarm: when an exception fires in production, you want to know immediately, with the stack trace and the environment, before the customer emails you (or worse, leaves silently). Web analytics is your traffic report: it tells you where visitors come from, what path they take, and where they give up — the difference between guessing why signups are flat and seeing that 60% drop off on the pricing page. One watches your code; the other watches your users. You need both, and you should never make one do the other’s job.
The two tools at a glance — different question, different data
Section titled “The two tools at a glance — different question, different data”The clearest way to keep these straight is by the question each one answers and the kind of event it records.
| Error monitoring (Sentry) | Web analytics | |
|---|---|---|
| Answers | ”Is my app broken — where, and how often?" | "Who visits, and where do they drop off?” |
| Watches | Your code (exceptions, slow transactions) | Your users (pageviews, funnels, conversions) |
| Event | A thrown exception, a failed job, a slow request | A pageview, a click, a goal completion |
| You want | Zero of these (every one is a bug) | More of these (every one is a visitor) |
| Lives | Server-side SDK in Laravel + a dashboard | A JS snippet in your page <head> + a dashboard |
| Alerts on | A new error, an error spike, a slow endpoint | Usually reports, not real-time alarms |
The asymmetry in the “You want” row is the whole point. A monitoring dashboard trending toward zero is healthy; an analytics dashboard trending toward zero means nobody is coming. Read them with opposite instincts.
graph TD App["Your live Laravel app"] App -->|"exception / slow request"| Sentry["Error monitoring<br/>(Sentry SDK, server-side)"] App -->|"pageview / click"| Analytics["Web analytics<br/>(JS snippet, client-side)"] Sentry --> SD["Issues · stack traces · alerts<br/><i>question: is it broken?</i>"] Analytics --> AD["Funnels · conversions · sources<br/><i>question: is anyone using it?</i>"]Error monitoring — how Sentry actually works
Section titled “Error monitoring — how Sentry actually works”Sentry catches exceptions your app throws in production and packages each into an issue: the error message, a full stack trace, the environment (local / staging / production), the PHP runtime, and a trace ID. Instead of grepping storage/logs/laravel.log after a customer complains, you get the failure pushed to a dashboard the moment it happens.
In Laravel the wiring is small: install the sentry/sentry-laravel package, register Integration::handles() in bootstrap/app.php, point a SENTRY_LARAVEL_DSN at your project, and add a sentry_logs channel to config/logging.php. The DSN (Data Source Name) is the address that tells the SDK where to send events — it is a project key, not a secret on the level of a payment key, but it still lives in .env and a secrets manager, never hard-coded.
One project, many environments
Section titled “One project, many environments”The key design decision: use one Sentry project for the whole app and separate the data by an environment tag (local, staging, production), rather than creating a project per environment. The environment comes from APP_ENV, so the same code reports to the right bucket automatically, and you filter the dashboard by environment:production to see only what’s real.
graph LR Local["local<br/>APP_ENV=local"] --> Proj["One Sentry project"] Staging["staging<br/>APP_ENV=staging"] --> Proj Prod["production<br/>APP_ENV=production"] --> Proj Proj -->|"filter by environment tag"| View["Issues dashboard<br/>(one place, three buckets)"]Sampling — why production sends only a fraction
Section titled “Sampling — why production sends only a fraction”Sentry can record traces (performance data — how long requests and queries take) in addition to errors, and traces are high-volume. The sample rate controls what fraction gets sent. The convention that balances cost against coverage:
| Environment | SENTRY_TRACES_SAMPLE_RATE | Why |
|---|---|---|
| local / staging | 1.0 (100%) | Catch everything while you’re testing — volume is tiny |
| production | 0.1 (10%) | Real traffic is high-volume; 10% controls quota cost while still catching real patterns |
A subtle point beginners miss: errors are not the same as traces. You still capture every exception in production — sampling applies to performance traces, not to the errors themselves. You never want to miss a real bug to save quota.
Privacy — keep user data out of your error tool
Section titled “Privacy — keep user data out of your error tool”Stack traces can accidentally carry personal data (an email in a variable, an IP in the request). The safe default for a GDPR-aware app:
- Set
send_default_pii => falseso the SDK does not attach user emails/IPs automatically. - Enable Sentry’s Data Scrubber with the default scrubbers, and add your own sensitive fields (
email,password,token,api_key,authorization,credit_card,ssn). - Turn on Prevent Storing IP Addresses.
This is the same principle as the security guide — a compliance promise needs a setting behind it. “We don’t store user PII” is only true once these are on.
Alerts — the three that matter
Section titled “Alerts — the three that matter”Monitoring is worthless if nobody looks at the dashboard. Alerts push the signal to you:
- New error (first occurrence) — email the moment an issue is seen for the first time. This is the one that catches the bug before the customer does.
- High-volume spike — email when an issue exceeds a threshold (e.g. >100 events/hour) so a sudden regression gets your attention even if it’s an old issue.
- Slow transaction (optional) — email when a P95 transaction crosses a budget (e.g. >2s) so a creeping performance problem surfaces before users complain.
Web analytics — how visitor tracking works
Section titled “Web analytics — how visitor tracking works”Web analytics answers the other question. A lightweight JavaScript snippet in your page <head> reports each pageview to an analytics service, which assembles them into sessions (one visitor’s journey), funnels (the ordered steps toward a goal), and conversions (the goal completions that actually matter to the business).
Where Sentry lives server-side in your PHP, analytics is client-side — a script tag in the rendered HTML. On a Laravel app there are a few clean ways to inject it:
| Method | What it is | Best when |
|---|---|---|
| Layout include | Paste the snippet directly into the base Blade layout <head> | Simplest; one site, you control the template |
| Config + env flag | Snippet behind config('services.…') driven by a VENDOR_TRACKING_ENABLED env var | You want to toggle tracking per environment without editing Blade |
| View composer / middleware | A service provider or middleware injects the snippet before </head> | Vendor-safe — you avoid hand-editing a template the vendor owns |
The config-flag approach is the one that matters most on a CodeCanyon app, because it lets you keep tracking off on local and staging (you don’t want your own testing polluting the data) and on in production — flipped by a single env var, no code change. On a Workdo/vendor app, prefer injecting via a service provider or the vendor’s own settings field over editing a vendor Blade file directly; that is the same 3-file “safe vendor deviation” shape the analytics setup step walks through (one template hook, one config key, one env var).
Funnels and goals — the part that earns its keep
Section titled “Funnels and goals — the part that earns its keep”Raw pageview counts are vanity metrics. The value is in two constructs:
- A goal is a single action that means success — an account created, a demo requested, a paid plan purchased. You define it by the page it lands on (
/dashboardafter signup) or the element clicked. - A funnel is an ordered sequence of steps toward a goal, so you can see exactly which step loses people.
graph LR S1["Homepage<br/>1,000 visitors"] -->|"60% continue"| S2["Pricing<br/>600"] S2 -->|"40% continue"| S3["Signup<br/>240"] S3 -->|"75% continue"| S4["Account created<br/>180"]The drop from 600 → 240 in that example is the actionable insight: most loss happens at the pricing-to-signup step, so that page is where an improvement pays off most. You can’t see that from a total-visitors number; you can only see it from a funnel.
Privacy — analytics has its own GDPR story
Section titled “Privacy — analytics has its own GDPR story”A privacy-first analytics tool collects no personal data by default: anonymous (hashed) visitor IDs, optional or omitted IP addresses, and often no cookies at all. The practical rule for a solo SaaS:
| Tracking level | What it collects | Cookie banner needed? |
|---|---|---|
| Anonymous (recommended) | Hashed IDs, no IP, no cookies | No — privacy-first by default |
| Basic | Optional IP, hashed cookies | Often yes, depending on jurisdiction |
| Full | All data | Yes — requires explicit consent |
Choosing the anonymous level keeps you GDPR-compliant without a consent banner, which is usually the right trade for a small app. Set a data-retention window (e.g. 90 days) and honor Do-Not-Track, and the analytics tool stops being a compliance liability.
When to reach for which
Section titled “When to reach for which”They overlap just enough to confuse, so anchor on the question you’re actually asking:
| You want to know… | Reach for | Not |
|---|---|---|
| Why did this customer hit an error? | Error monitoring | Analytics (it doesn’t see stack traces) |
| Is the checkout endpoint getting slower? | Error monitoring (performance traces) | Analytics |
| Where do visitors abandon signup? | Analytics (funnels) | Error monitoring (a drop-off isn’t an error) |
| Which traffic source converts best? | Analytics | Error monitoring |
| Did my last deploy introduce a regression? | Error monitoring (release health) | Analytics |
| Should I rebuild the pricing page? | Analytics (the funnel says so) | Error monitoring |
The one-line test: if the thing you’re chasing is a bug, it belongs in error monitoring; if it’s a behavior, it belongs in analytics. A 500 error is a bug. A 50% bounce rate is a behavior. Neither tool can tell you about the other’s domain — that’s why a healthy app runs both.
Where you actually execute this
Section titled “Where you actually execute this”This guide is the why. The hands-on steps live in the setup playbook, in two different phases because they’re two different concerns: