/log-drugapi-service-prod — claim-drugnorm-api (PROD) Log Investigator
Ready-to-use investigator for the production claim-drugnorm-api service's logs in
Google Cloud Console (Cloud Logging). When this skill is invoked, you are immediately
equipped to query — the project, resource, and log patterns below are already verified,
don't re-discover them, just query.
This skill is PROD only. There is a parallel SIT service (claim-drugnorm-api-sit) in
the same project — never query it from this skill unless the user explicitly asks to
compare against SIT.
Shape of this service: unlike claim-data-extraction (a batch job over business
dates), claim-drugnorm-api is a synchronous HTTP API — a client calls it, it normalizes a
drug name/code, and returns a response immediately. There is no "business date being
processed" concept in these logs — if the user asks "วันที่ X ประมวลผลหรือยัง", interpret
that as "were there any API calls logged on date X", not a batch-date lookup, and say so
explicitly if it seems like they expect the CDE-style batch semantics.
Step 0: Init
date "+🕐 %H:%M %Z (%A %d %B %Y)"
Step 1: Auth check (do this before every investigation)
gcloud auth print-access-token --project=ocean-life-data-extraction >/dev/null 2>&1 && echo OK || echo REAUTH_NEEDED
- If
OK: proceed straight to querying.
- If
REAUTH_NEEDED: tell the user their gcloud session expired and ask them to run
! gcloud auth login (the ! prefix runs it in their own terminal so the interactive
OAuth browser flow works — this tool cannot do it for them). Wait for confirmation, then
retry the check before continuing.
Access (verified)
- GCP project:
ocean-life-data-extraction (same project as claim-data-extraction)
- Account: the gcloud identity you're authenticated as (check with
gcloud auth list)
- Region:
us-central1
- Resource:
resource.type="cloud_run_revision", resource.labels.service_name="claim-drugnorm-api"
- Service URL:
https://claim-drugnorm-api-k6syfr56xq-uc.a.run.app
- Known endpoint:
POST /api/v2/drugnorm (the actual drug-normalization call)
- Runtime: Python FastAPI/Uvicorn on Cloud Run, autoscaled (scales to zero between calls —
don't read a startup/shutdown cycle as an incident, it's normal idle behavior)
Three log sources under this one resource, each surfaced via logName:
logName suffix |
What it logs |
%2Frequests |
Auto-generated HTTP access log — has a httpRequest struct (method, URL, status, latency, request/response size, remoteIp, userAgent). No request/response body is logged here or anywhere else — you cannot see which drug/code was normalized, only whether the call succeeded and how fast. |
%2Fvarlog%2Fsystem |
Container/instance lifecycle — cold start, autoscaling reason, health-probe results, shutdown |
%2Fstderr / %2Fstdout |
Uvicorn/FastAPI process text lines (INFO: Uvicorn running on..., Application startup complete, etc.) — general process narration, rarely useful for answering business questions |
How to query
# Everything for this service (all log sources), most recent first
gcloud logging read \
'resource.type="cloud_run_revision" AND resource.labels.service_name="claim-drugnorm-api"' \
--project=ocean-life-data-extraction --format="value(timestamp,severity,textPayload)" \
--order=desc --limit=200 --freshness=<Nd>
# Just the HTTP request/response outcomes (this is where "did it work" lives)
gcloud logging read \
'resource.type="cloud_run_revision" AND resource.labels.service_name="claim-drugnorm-api" AND logName:"requests"' \
--project=ocean-life-data-extraction \
--format="table(timestamp,httpRequest.requestMethod,httpRequest.requestUrl,httpRequest.status,httpRequest.latency,httpRequest.remoteIp,httpRequest.userAgent)" \
--order=desc --limit=200 --freshness=<Nd>
# Only actual errors (auto request logs mark 4xx/5xx as WARNING/ERROR severity)
gcloud logging read \
'resource.type="cloud_run_revision" AND resource.labels.service_name="claim-drugnorm-api" AND severity>=WARNING' \
--project=ocean-life-data-extraction --format=json \
--order=desc --limit=100 --freshness=<Nd>
Traffic on this service is low (single-digit to low-tens of calls per day as of 2026-08),
so a --freshness=90d pull with no keyword filter is usually cheap and fine — no need to
narrow aggressively like the CDE worker logs.
Timestamps: timestamp is UTC. Bangkok is UTC+7 — convert before reporting, and state
which timezone you're reporting in.
Known log line / field patterns (don't reverse-engineer, use these)
Request log (%2Frequests) — httpRequest.status meanings observed on this service:
200 — normalized successfully. Legit callers seen so far use curl as the user-agent
and a stable small set of remoteIp values — cross-check against prior 200s from the
same IP/user-agent before assuming a new caller is legitimate.
401 Unauthorized — missing or invalid auth credential on the request. The bulk of 401s
on / and /api/v2/drugnorm are internet background-scan noise: random browser
userAgent strings (Chrome/Edge on Windows) hitting the public Cloud Run URL
unsolicited, unrelated to real usage. Don't treat these as an incident by themselves.
403 Forbidden — credential present but rejected (wrong key/token, not just missing).
Seen occasionally from the same curl clients that also produced 200s nearby in time —
likely a client mid-testing an auth header, not an attacker.
5xx — has not been observed in production as of 2026-08; if you find one, treat it as
a real incident and read the surrounding %2Fstderr lines for a stack trace.
- No
severity>=ERROR entries have been seen at all — if a query surfaces one, don't
assume it's routine, read it in full.
Lifecycle (%2Fvarlog%2Fsystem):
Starting new instance. Reason: AUTOSCALING - ... — cold start due to traffic (scale
from zero); Reason: DEPLOYMENT_ROLLOUT - ... — cold start due to a new deploy, not
traffic
STARTUP HTTP probe succeeded after N attempts for container "..." on port 8080 path "/healthz". — container came up healthy
Default STARTUP TCP probe succeeded after N attempt(s). — lower-level readiness check,
precedes the HTTP probe line
Process (%2Fstderr):
INFO: Started server process [1] → Waiting for application startup. →
Application startup complete. → Uvicorn running on http://0.0.0.0:8080 ... — normal
cold-start sequence, always in this order
INFO: Shutting down → Waiting for application shutdown. →
Application shutdown complete. → Finished server process [1] — normal scale-to-zero
sequence when idle; this is expected, not a crash
Investigation patterns
"วันที่ X มีเรียก API หรือยัง / ผลเป็นยังไง" (were there calls on date X, how did they go):
- Query the request-log table above with
--freshness wide enough to cover date X
(convert to a UTC day-boundary window if precision matters, since timestamp is UTC).
- List every
httpRequest row for that day: status, latency, remoteIp, userAgent.
- Report success/fail counts by status code. Remember: no body is logged, so you cannot
say which drug/code was normalized — only call outcomes.
- If the user seems to expect batch-style "processed N records" semantics, clarify this
service doesn't work that way — each API call is one synchronous request.
"ตอนนี้ระบบยังทำงานอยู่ไหม / เรียกล่าสุดเมื่อไหร่":
- Pull the most recent request-log rows (
--order=desc --limit=5, no filter) and compare
timestamp to now. A long gap is normal — this service scales to zero when idle, it's not
down.
"มี error หรือมีคนพยายามเข้าถึงแบบผิดปกติไหม":
- Filter
severity>=WARNING, then split by httpRequest.userAgent/remoteIp pattern:
browser user-agents hitting / or the API root with no prior 200 history from that
IP = scan noise; curl clients with a mix of 401/403/200 close together in time = a
real client working through auth, not an attack.
- Any
5xx or severity>=ERROR line is unprecedented for this service — flag it clearly
rather than filing it under normal noise, and pull the %2Fstderr lines around the same
insertId/timestamp for a stack trace.
Behavior
- Default to prod only — never silently include the
-sit resource.
- Convert Thai relative dates ("เมื่อวาน", "สัปดาห์ที่แล้ว") to explicit dates before
searching, state the resulting date back if ambiguous, and always note UTC vs Bangkok
time explicitly when reporting a timestamp.
- Be explicit about the logging limitation: this service never logs request/response
bodies, so questions about which drug name/code was processed cannot be answered from
these logs — only call success/failure, timing, and caller metadata.
- Quote the actual
httpRequest fields (status, latency, remoteIp, userAgent) rather than
paraphrasing.
- Read-only, always: this skill only reads Cloud Logging, never modifies the service or
triggers a new deploy/request.
- If
gcloud auth print-access-token fails mid-session (token expired), stop and ask the
user to ! gcloud auth login again rather than guessing or retrying blindly.
1---2name: log-drugapi-service-prod3description: Investigate and answer questions using the PRODUCTION Google Cloud Console logs for the claim-drugnorm-api service (a Cloud Run HTTP API, not a batch job). Use when user says '/log-drugapi-service-prod', asks about a processing date ('วันที่ประมวลผล'), or needs to check request status/errors/latency for claim-drugnorm-api in prod.4---56# /log-drugapi-service-prod — claim-drugnorm-api (PROD) Log Investigator78Ready-to-use investigator for the **production** `claim-drugnorm-api` service's logs in9Google Cloud Console (Cloud Logging). When this skill is invoked, you are immediately10equipped to query — the project, resource, and log patterns below are already verified,11don't re-discover them, just query.1213**This skill is PROD only.** There is a parallel SIT service (`claim-drugnorm-api-sit`) in14the same project — never query it from this skill unless the user explicitly asks to15compare against SIT.1617**Shape of this service**: unlike `claim-data-extraction` (a batch job over business18dates), `claim-drugnorm-api` is a synchronous HTTP API — a client calls it, it normalizes a19drug name/code, and returns a response immediately. There is **no "business date being20processed" concept in these logs** — if the user asks "วันที่ X ประมวลผลหรือยัง", interpret21that as "were there any API calls logged on date X", not a batch-date lookup, and say so22explicitly if it seems like they expect the CDE-style batch semantics.2324---2526## Step 0: Init2728```bash29date "+🕐 %H:%M %Z (%A %d %B %Y)"30```3132---3334## Step 1: Auth check (do this before every investigation)3536```bash37gcloud auth print-access-token --project=ocean-life-data-extraction >/dev/null 2>&1 && echo OK || echo REAUTH_NEEDED38```3940- If `OK`: proceed straight to querying.41- If `REAUTH_NEEDED`: tell the user their gcloud session expired and ask them to run42 `! gcloud auth login` (the `!` prefix runs it in their own terminal so the interactive43 OAuth browser flow works — this tool cannot do it for them). Wait for confirmation, then44 retry the check before continuing.4546---4748## Access (verified)4950- **GCP project**: `ocean-life-data-extraction` (same project as `claim-data-extraction`)51- **Account**: the gcloud identity you're authenticated as (check with `gcloud auth list`)52- **Region**: `us-central1`53- **Resource**: `resource.type="cloud_run_revision"`, `resource.labels.service_name="claim-drugnorm-api"`54- **Service URL**: `https://claim-drugnorm-api-k6syfr56xq-uc.a.run.app`55- Known endpoint: `POST /api/v2/drugnorm` (the actual drug-normalization call)56- Runtime: Python FastAPI/Uvicorn on Cloud Run, autoscaled (scales to zero between calls —57 don't read a startup/shutdown cycle as an incident, it's normal idle behavior)5859Three log sources under this one resource, each surfaced via `logName`:6061| `logName` suffix | What it logs |62|---|---|63| `%2Frequests` | Auto-generated HTTP access log — has a `httpRequest` struct (method, URL, status, latency, request/response size, remoteIp, userAgent). **No request/response body is logged here or anywhere else** — you cannot see which drug/code was normalized, only whether the call succeeded and how fast. |64| `%2Fvarlog%2Fsystem` | Container/instance lifecycle — cold start, autoscaling reason, health-probe results, shutdown |65| `%2Fstderr` / `%2Fstdout` | Uvicorn/FastAPI process text lines (`INFO: Uvicorn running on...`, `Application startup complete`, etc.) — general process narration, rarely useful for answering business questions |6667---6869## How to query7071```bash72# Everything for this service (all log sources), most recent first73gcloud logging read \74 'resource.type="cloud_run_revision" AND resource.labels.service_name="claim-drugnorm-api"' \75 --project=ocean-life-data-extraction --format="value(timestamp,severity,textPayload)" \76 --order=desc --limit=200 --freshness=<Nd>7778# Just the HTTP request/response outcomes (this is where "did it work" lives)79gcloud logging read \80 'resource.type="cloud_run_revision" AND resource.labels.service_name="claim-drugnorm-api" AND logName:"requests"' \81 --project=ocean-life-data-extraction \82 --format="table(timestamp,httpRequest.requestMethod,httpRequest.requestUrl,httpRequest.status,httpRequest.latency,httpRequest.remoteIp,httpRequest.userAgent)" \83 --order=desc --limit=200 --freshness=<Nd>8485# Only actual errors (auto request logs mark 4xx/5xx as WARNING/ERROR severity)86gcloud logging read \87 'resource.type="cloud_run_revision" AND resource.labels.service_name="claim-drugnorm-api" AND severity>=WARNING' \88 --project=ocean-life-data-extraction --format=json \89 --order=desc --limit=100 --freshness=<Nd>90```9192Traffic on this service is low (single-digit to low-tens of calls per day as of 2026-08),93so a `--freshness=90d` pull with no keyword filter is usually cheap and fine — no need to94narrow aggressively like the CDE worker logs.9596**Timestamps**: `timestamp` is UTC. Bangkok is UTC+7 — convert before reporting, and state97which timezone you're reporting in.9899---100101## Known log line / field patterns (don't reverse-engineer, use these)102103**Request log (`%2Frequests`) — `httpRequest.status` meanings observed on this service**:104- `200` — normalized successfully. Legit callers seen so far use `curl` as the user-agent105 and a stable small set of `remoteIp` values — cross-check against prior 200s from the106 same IP/user-agent before assuming a new caller is legitimate.107- `401 Unauthorized` — missing or invalid auth credential on the request. The bulk of 401s108 on `/` and `/api/v2/drugnorm` are **internet background-scan noise**: random browser109 `userAgent` strings (Chrome/Edge on Windows) hitting the public Cloud Run URL110 unsolicited, unrelated to real usage. Don't treat these as an incident by themselves.111- `403 Forbidden` — credential present but rejected (wrong key/token, not just missing).112 Seen occasionally from the same `curl` clients that also produced 200s nearby in time —113 likely a client mid-testing an auth header, not an attacker.114- `5xx` — has not been observed in production as of 2026-08; if you find one, treat it as115 a real incident and read the surrounding `%2Fstderr` lines for a stack trace.116- No `severity>=ERROR` entries have been seen at all — if a query surfaces one, don't117 assume it's routine, read it in full.118119**Lifecycle (`%2Fvarlog%2Fsystem`)**:120- `Starting new instance. Reason: AUTOSCALING - ...` — cold start due to traffic (scale121 from zero); `Reason: DEPLOYMENT_ROLLOUT - ...` — cold start due to a new deploy, not122 traffic123- `STARTUP HTTP probe succeeded after N attempts for container "..." on port 8080 path124 "/healthz".` — container came up healthy125- `Default STARTUP TCP probe succeeded after N attempt(s).` — lower-level readiness check,126 precedes the HTTP probe line127128**Process (`%2Fstderr`)**:129- `INFO: Started server process [1]` → `Waiting for application startup.` →130 `Application startup complete.` → `Uvicorn running on http://0.0.0.0:8080 ...` — normal131 cold-start sequence, always in this order132- `INFO: Shutting down` → `Waiting for application shutdown.` →133 `Application shutdown complete.` → `Finished server process [1]` — normal scale-to-zero134 sequence when idle; this is expected, not a crash135136---137138## Investigation patterns139140**"วันที่ X มีเรียก API หรือยัง / ผลเป็นยังไง" (were there calls on date X, how did they go)**:1411. Query the request-log table above with `--freshness` wide enough to cover date X142 (convert to a UTC day-boundary window if precision matters, since `timestamp` is UTC).1432. List every `httpRequest` row for that day: status, latency, remoteIp, userAgent.1443. Report success/fail counts by status code. Remember: no body is logged, so you cannot145 say *which* drug/code was normalized — only call outcomes.1464. If the user seems to expect batch-style "processed N records" semantics, clarify this147 service doesn't work that way — each API call is one synchronous request.148149**"ตอนนี้ระบบยังทำงานอยู่ไหม / เรียกล่าสุดเมื่อไหร่"**:150- Pull the most recent request-log rows (`--order=desc --limit=5`, no filter) and compare151 timestamp to now. A long gap is normal — this service scales to zero when idle, it's not152 down.153154**"มี error หรือมีคนพยายามเข้าถึงแบบผิดปกติไหม"**:155- Filter `severity>=WARNING`, then split by `httpRequest.userAgent`/`remoteIp` pattern:156 browser user-agents hitting `/` or the API root with no prior 200 history from that157 IP = scan noise; `curl` clients with a mix of 401/403/200 close together in time = a158 real client working through auth, not an attack.159- Any `5xx` or `severity>=ERROR` line is unprecedented for this service — flag it clearly160 rather than filing it under normal noise, and pull the `%2Fstderr` lines around the same161 `insertId`/timestamp for a stack trace.162163---164165## Behavior166167- Default to **prod only** — never silently include the `-sit` resource.168- Convert Thai relative dates ("เมื่อวาน", "สัปดาห์ที่แล้ว") to explicit dates before169 searching, state the resulting date back if ambiguous, and always note UTC vs Bangkok170 time explicitly when reporting a timestamp.171- Be explicit about the logging limitation: this service never logs request/response172 bodies, so questions about *which* drug name/code was processed cannot be answered from173 these logs — only call success/failure, timing, and caller metadata.174- Quote the actual `httpRequest` fields (status, latency, remoteIp, userAgent) rather than175 paraphrasing.176- Read-only, always: this skill only reads Cloud Logging, never modifies the service or177 triggers a new deploy/request.178- If `gcloud auth print-access-token` fails mid-session (token expired), stop and ask the179 user to `! gcloud auth login` again rather than guessing or retrying blindly.