Observability and Debugging
Week 12 · Seeing inside Project Pulse after it ships
Week 12 · Seeing inside Project Pulse after it ships
You cannot fix what you cannot see.
"Production telemetry is not collected anywhere yet." Project Pulse's architecture document, until November 9, 2026
How would you have noticed?
Monitoring
A few numbers you picked in advance. Tells you that something is wrong.
Observability
Asking new questions of what the system already records. Tells you why, even for a problem you never imagined.
Install the port before the breakdown, not during it.
| Metric | Trace | Log |
|---|---|---|
| http.server.requests | An instructor invites two students | Sent {} of {} weekly reminders |
| jvm.memory.used | A user logs in | Could not send an invitation email |
| hikaricp.connections.pending | A student adds an activity | Unhandled exception |
"We have no errors in production." "How do you know?" "We don't log."
| Endpoint | Production | Your laptop |
|---|---|---|
| health | status only | each check |
| info | version and commit | plus Java and OS |
| metrics, prometheus | not exposed | admin only |
| loggers, env, beans | not exposed | admin only |
curl https://projectpulse.team/actuator/health
curl https://projectpulse.team/actuator/info
curl https://projectpulse.team/actuator/metricsThe third one answers 404. Is something broken?
spring-boot-starter-actuator the /actuator endpoints
spring-boot-starter-micrometer-metrics counts and times
micrometer-registry-prometheus the Prometheus format
spring-boot-micrometer-tracing-brave trace IDs and spans
spring-boot-starter-zipkin sends spans to Zipkin/actuator/metrics
A window for one person's spot check. No history.
A collector
Keeps the history. Pulls from a private endpoint, or the app pushes to it.
The public site does not serve its metrics.
Find your platform's log stream before the first incident.
09:02:14 ERROR [6af11fb6...-0860298d...] UserInvitationService : Could not send an invitation email for section 2 of course 1
{"log":{"level":"ERROR", "logger":"team.projectpulse.user.userinvitation.UserInvitationService"},
"message":"Could not send an invitation email for section 2 of course 1", "traceId":"6af11fb6..."}That is how you find one failure among thousands of lines.
curl -X POST localhost/actuator/loggers/team.projectpulse.user \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' -d '{"configuredLevel":"DEBUG"}'/actuator/metrics/http.server.requests
/actuator/metrics/http.server.requests?tag=uri:/api/v1/students&tag=status:500Counter
Only goes up: invitations sent
Gauge
A value right now: threads alive
Timer
How long, and how often: @Timed
| Signal | Project Pulse metric |
|---|---|
| Latency | http.server.requests, by uri and status |
| Traffic | http.server.requests count |
| Errors | outcome SERVER_ERROR, plus wrong answers |
| Saturation | memory, threads, pending database connections |
Machine metrics
Memory, threads. Is the system struggling?
Business metrics
Reports submitted before Monday's deadline. Is it doing its job?
try {
this.emailService.sendInvitationEmail(userInvitation);
meterRegistry.counter("invitations.sent", "outcome", "success").increment();
} catch (RuntimeException e) {
LOGGER.error("Could not send an invitation email for section {} of course {}", sectionId, courseId, e);
meterRegistry.counter("invitations.sent", "outcome", "failure").increment();
}HTTP 200 OK, body {"error": "database on fire"}. Error rate: 0%.
| Source | Misses |
|---|---|
| The platform's request and 5xx counts | wrong answers with a 200 |
| An outside probe, every minute | what the health check doesn't check |
| The app's own metric | requests that never arrived; restarts |
management:
metrics:
distribution:
percentiles:
http.server.requests: 0.95A percentile cannot be averaged across instances.
curl -s -o /dev/null -w '%{http_code} %{content_type}' https://projectpulse.team/health
200 text/html: the single-page app, not the health check.
Check the content type, too. An SLI that cannot fail measures nothing.
In the top trace, where did the 165 milliseconds go?
@Observed(name = "email.send", contextualName = "send invitation email")
public void sendInvitationEmail(UserInvitation userInvitation) {No span, no answer. Spring Boot does not time queries or mail on its own.
The outage is always in the span you didn't add.
management:
observations:
annotations:
enabled: truemanagement:
tracing:
sampling:
probability: 0.1A student reports one failed request. What can you still find?
The diagnostic port is a port for anyone else, too.
/actuator/env: the most helpful endpoint on the internet. It answers every question, for everyone.
management:
endpoints:
web:
exposure:
include: health,info # the base for every profile
endpoint:
health:
show-details: when-authorized # details for an admin only
roles: admin
info:
git:
mode: simple # the commit, not the JVM version
java:
enabled: false
os:
enabled: falseShould a broken mail server make the whole app "down"?
Liveness
Is the process stuck? Restart it.
/actuator/health/liveness
Readiness
Can it serve traffic now? Send requests elsewhere until it can.
/actuator/health/readiness
Point a platform's probe at one of these, never at /actuator/health.
Alive but not ready: me at 8 a.m. before coffee. Don't restart me, just wait.
Project Pulse on a laptop · you call out where to look
| Path | Requests | Looking for |
|---|---|---|
/.gitlab-ci.yml |
21 | Secrets in a pipeline file |
/.env.openai, /.env.aws |
13 | API keys |
/.git/hooks/post-receive, /.hg/requires |
14 | The repository |
/l9owrv.php, /well.php, /wp-admin.php |
24 | Someone else's back door |
/vite.config.js, /webpack.json, … |
43 | How the frontend is built |
/robots.txt |
8 | Nothing bad |
itemCount"Users get a 500 on the report page." The agent will guess, fluently.
Give it the stack trace, the request, and the commit that deployed at 9:02.
Site reliability engineer
Chooses what to watch, carries the pager, runs incidents, writes the blameless postmortem, sets SLOs.
Also called
DevOps engineer, platform engineer, production engineer.
Delegate
Scan thousands of log lines. Correlate a spike with a deploy. Draft the structured-logging config.
Keep
What to watch, what to promise the client, what may never appear in a log.
Asked to "add logging," an agent logs request bodies, emails, and tokens. Grep your logs for an email address.
Our alert for the mail server is sent by email. What could go wrong?
let op = toscalar(requests
| where timestamp > ago(2d) and resultCode == "500"
and name == "POST /api/v1/evaluations"
| take 1 | project operation_Id);
union requests, dependencies, exceptions, traces
| where operation_Id == op
| project timestamp, itemType, name, resultCode, message
| order by timestamp asc"Here is the request, the SQL, and the error from production. Explain the failure and propose a test that reproduces it. Do not fix it yet."
The SLI called that day a success. The 0.05% was one student's work.
Pick the indicator, the target, and the window. What does your number miss?
Checkpoint 3 next week: your SLO with a measured value, and where the number comes from.