Skip to main content

Log Service

The api-gateway exposes log ingestion endpoints that forward structured log entries to Loki for centralized observability.

EndpointConsumerLoki job label
POST /v1/logsWeb / frontendfrontend_app
POST /v1/logs/mobileMobile appmobile_app

Base URLs​

EnvironmentURL
Staginghttps://gw-staging.uts.network
Productionhttps://gw.uts.network

POST /v1/logs​

Ingest a log entry from a web / frontend client.

Authentication​

Validates the request by resolving the user ID from the GraphQL profile service using the provided Authorization header. Returns 401 Unauthorized if the user cannot be resolved.

Authorization Header
Authorization: <user-token>

Request body​

Request body
{
"message": "User tapped checkout",
"level": "INFO",
"trade_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"timestamp": 1748649600000,
"labels": {
"app": "xperise-web",
"platform": "web",
"env": "production"
},
"data": {
"screen": "checkout"
}
}
FieldTypeRequiredDescription
messagestringYesHuman-readable log message.
levelstringYesSeverity: DEBUG, INFO, WARN, ERROR.
trade_idstringNoTrace / correlation ID for distributed tracing.
timestampnumberNoUnix timestamp in milliseconds. Defaults to server time when omitted or 0.
labelsobjectNoKey-value pairs added as Loki labels or metadata (see Label routing).
dataany JSONNoArbitrary structured payload stored in the log line.

cURL example​

note

This endpoint only accepts POST. Running curl without -X POST sends a GET request and returns 404.

cURL — POST /v1/logs
curl -X POST https://gw-staging.uts.network/v1/logs \
-H "Content-Type: application/json" \
-H "Authorization: <user-token>" \
-d '{
"message": "User tapped checkout",
"level": "INFO",
"timestamp": 1748649600000,
"labels": {
"app": "xperise-web",
"platform": "web",
"env": "staging"
},
"data": {
"screen": "checkout"
}
}'

TypeScript example​

sendLog.ts
async function sendLog(
message: string,
level: "DEBUG" | "INFO" | "WARN" | "ERROR",
data?: Record<string, unknown>
) {
await fetch("https://gw.uts.network/v1/logs", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: userToken,
},
body: JSON.stringify({
message,
level,
timestamp: Date.now(),
labels: {
app: "xperise-web",
platform: "web",
env: import.meta.env.MODE,
},
data,
}),
});
}

// Usage
sendLog("Payment failed", "ERROR", { code: "CARD_DECLINED", amount: 150000 });

Error responses​

StatusBodyReason
400{"error":"message is required"}message field missing.
400{"error":"level is required"}level field missing.
400{"error":"Invalid JSON payload"}Request body is not valid JSON.
401{"error":"Unauthorized"}Token missing or user could not be resolved.
500{"error":"Failed to push log"}Loki push failed.

POST /v1/logs/mobile​

Ingest a log entry from a mobile client.

Authentication​

Uses a daily-rotating bearer token. Contact the backend team to obtain the token generation mechanism.

Authorization Header
Authorization: Bearer <daily-token>

Request body​

Same schema as POST /v1/logs.

cURL example​

cURL — POST /v1/logs/mobile
curl -X POST https://gw-staging.uts.network/v1/logs/mobile \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <daily-token>" \
-d '{
"message": "App launched",
"level": "INFO",
"timestamp": 1748649600000,
"labels": {
"app": "xperise-mobile",
"platform": "ios",
"env": "staging"
},
"data": {
"screen": "home"
}
}'

Error responses​

StatusBodyReason
400{"error":"message is required"}message field missing.
400{"error":"level is required"}level field missing.
400{"error":"Invalid JSON payload"}Request body is not valid JSON.
401{"error":"Missing or invalid Authorization header"}Bearer token missing.
401{"error":"Unauthorized"}Token does not match today's expected value.
500{"error":"Failed to push log"}Loki push failed.

Label routing​

Low-cardinality keys are indexed as Loki stream labels; everything else is stored as metadata inside the JSON log line.

Label keyIndexed in Loki
appYes
platformYes
envYes
levelYes
jobYes
anything elseNo (stored in metadata)

Querying logs in Grafana / Loki​

Grafana / Loki
# All frontend errors
{job="frontend_app", level="ERROR"}

# All mobile errors
{job="mobile_app", level="ERROR"}

# Search by message content
{job="frontend_app"} |= "Payment failed"

# Filter by trace ID in the log line
{job="frontend_app"} | json | trace_id="4bf92f3577b34da6a3ce929d0e0e4736"

# Filter by user
{job="frontend_app"} | json | user_id="42"