Skip to main content

Structured logging

Log custom events from the server using the log() function:
import { log } from '@synthetiq/app-framework/server';

log({
  source: 'app',
  type: 'task_completed',
  priority: task.priority,
  userId: ctx.userId,
  traceId: ctx.traceId,
});
Every log entry requires source and type. Beyond that, you can include any fields you want — these become available for log filtering and metric dimensioning. Logs are searchable in the monitoring dashboard at /admin/monitoring/logs.

Client-side logging

Log events from the frontend using the logs.ingest tRPC procedure:
const trpc = getTrpc();

trpc.logs.ingest.mutate({
  type: 'button_clicked',
  traceId: getTraceId(),
  timestamp: Date.now(),
  data: {
    buttonId: 'export',
    page: '/tasks',
  },
});
FieldTypeRequiredDescription
typestringYesEvent type identifier
traceIdstringYesTrace ID from getTraceId()
timestampnumberYesDate.now() at time of event
pathstringNoPage path
methodstringNoHTTP method
statusnumberNoStatus code
dataobjectNoCustom fields for metric filtering and dimensioning
The framework automatically sets source: "app" and attaches the authenticated user’s ID.

metrics.json schema

Defined at the app root. Configures how metrics are derived from log entries and how charts are rendered on the monitoring dashboard. Validated at build time.
{
  "metrics": [],
  "dimensions": {},
  "charts": []
}

Metrics

Each metric definition derives a metric from matching log entries:
FieldTypeRequiredDescription
namestringYesMetric name
unitstringYesCount, Milliseconds, Seconds, or Bytes
valuenumber or stringYesA literal number (e.g., 1) or a log field name to read the value from (e.g., "ms")
dimensionsstring[]YesArray of dimension keys to group by
filterobjectNoLog field → array of accepted values. Only matching log entries contribute to this metric
{
  "name": "TaskCompletions",
  "unit": "Count",
  "value": 1,
  "dimensions": ["priority"],
  "filter": { "type": ["task_completed"] }
}

Dimensions

Dimensions define how metric values are grouped. Each dimension maps a log field to a set of labeled buckets:
FieldTypeRequiredDescription
fieldstringYesThe log field to read
bucketsobjectNoLabel → numeric range [min, max] or exact string match
{
  "priority": {
    "field": "priority",
    "buckets": {
      "High": "high",
      "Medium": "medium",
      "Low": "low"
    }
  },
  "statusCategory": {
    "field": "status",
    "buckets": {
      "Success": [200, 399],
      "Warning": [400, 499],
      "Error": [500, 599]
    }
  }
}
Buckets bound cardinality — log values that don’t match any bucket are excluded from that dimension.

Charts

Each chart definition renders a chart on the monitoring dashboard:
FieldTypeRequiredDescription
titlestringYesChart title
metricstringYesMetric name to visualize
statstringNoStatistic type (see below). Defaults to Sum for Count, Average for other units
chartTypestringNoline (default) or bar
splitBystringNoDimension key to split series by. Bar charts with splitBy render as stacked bars
tabstringNoDashboard tab name. Default is application. Use the same tab value to group charts together
colorstringNoColor hint (e.g., "error")
transformobjectNoTransform the data (e.g., { "type": "percentage", "numerator": ["Error"], "denominator": ["Success", "Error"] })
overlayMetricsarrayNoAdditional metrics to overlay on the same chart (e.g., [{ "metric": "WorkerCapacity", "label": "Capacity" }])
seriesStylesobjectNoPer-series styling (e.g., { "Capacity": { "borderDash": [6, 3] } })
namespacestringNoCloudWatch namespace for infrastructure metrics (e.g., "AWS/ECS")
fixedDimensionsobjectNoFixed CloudWatch dimension values for infrastructure metrics
unitstringNoDisplay unit override (e.g., "Percent")

Stat types

StatDescription
SumTotal value (default for Count unit)
AverageMean value (default for Milliseconds, Seconds, Bytes)
MinimumLowest value
MaximumHighest value
SampleCountNumber of data points
p5050th percentile
p9090th percentile
p9595th percentile
p9999th percentile

Built-in metrics

The framework ships with default metrics that require no configuration:
MetricUnitDescription
RequestCountCountHTTP requests, service calls, and page views
LatencyMillisecondsRequest and service call duration
WorkflowJobCountCountWorkflow job events (started, completed, failed)
WorkflowJobDurationMillisecondsWorkflow job execution time
WorkflowStepCountCountWorkflow step events
WorkerDemandCountCurrent worker demand
WorkerCapacityCountTotal worker capacity
Default charts are included for request count by status, error rate, latency percentiles, workflow job status, and infrastructure metrics (CPU, memory, task count).

Default metrics.json

Every scaffolded app includes the following metrics.json:
{
  "metrics": [
    {
      "name": "RequestCount",
      "unit": "Count",
      "value": 1,
      "dimensions": ["type", "statusCategory"],
      "filter": { "type": ["http_request", "service_call", "page_view"] }
    },
    {
      "name": "Latency",
      "unit": "Milliseconds",
      "value": "ms",
      "dimensions": ["type"],
      "filter": { "type": ["http_request", "service_call"] }
    },
    {
      "name": "WorkerDemand",
      "unit": "Count",
      "value": "demand",
      "dimensions": [],
      "filter": { "type": ["worker_scaling"] }
    },
    {
      "name": "WorkerCapacity",
      "unit": "Count",
      "value": "totalCapacity",
      "dimensions": [],
      "filter": { "type": ["worker_scaling"] }
    },
    {
      "name": "WorkflowJobCount",
      "unit": "Count",
      "value": 1,
      "dimensions": ["workflowEvent", "statusCategory"],
      "filter": { "type": ["workflow_job"] }
    },
    {
      "name": "WorkflowJobDuration",
      "unit": "Milliseconds",
      "value": "ms",
      "dimensions": ["workflowEvent"],
      "filter": { "type": ["workflow_job"] }
    },
    {
      "name": "WorkflowStepCount",
      "unit": "Count",
      "value": 1,
      "dimensions": ["workflowEvent", "statusCategory"],
      "filter": { "type": ["workflow_step"] }
    }
  ],
  "dimensions": {
    "type": {
      "field": "type",
      "buckets": {
        "HTTP Request": "http_request",
        "Service Call": "service_call",
        "Page View": "page_view",
        "Workflow Job": "workflow_job",
        "Workflow Step": "workflow_step"
      }
    },
    "statusCategory": {
      "field": "status",
      "buckets": {
        "Success": [200, 399],
        "Warning": [400, 499],
        "Error": [500, 599]
      }
    },
    "workflowEvent": {
      "field": "event",
      "buckets": {
        "Started": "started",
        "Completed": "completed",
        "Failed": "failed",
        "Retry": "retry"
      }
    }
  },
  "charts": [
    {
      "title": "Request Count by Status",
      "metric": "RequestCount",
      "stat": "Sum",
      "chartType": "bar",
      "splitBy": "statusCategory"
    },
    {
      "title": "Request Count by Type",
      "metric": "RequestCount",
      "stat": "Sum",
      "chartType": "line",
      "splitBy": "type"
    },
    {
      "title": "Error Rate",
      "metric": "RequestCount",
      "stat": "Sum",
      "chartType": "line",
      "splitBy": "statusCategory",
      "color": "error",
      "transform": {
        "type": "percentage",
        "numerator": ["Error"],
        "denominator": ["Success", "Warning", "Error"]
      }
    },
    {
      "title": "Average Latency by Type",
      "metric": "Latency",
      "stat": "Average",
      "chartType": "line",
      "splitBy": "type"
    },
    {
      "title": "P95 Latency by Type",
      "metric": "Latency",
      "stat": "p95",
      "chartType": "line",
      "splitBy": "type"
    },
    {
      "title": "Max Latency by Type",
      "metric": "Latency",
      "stat": "Maximum",
      "chartType": "line",
      "splitBy": "type"
    },
    {
      "title": "App CPU Utilization",
      "metric": "CPUUtilization",
      "stat": "Average",
      "chartType": "line",
      "unit": "Percent",
      "tab": "infrastructure",
      "namespace": "AWS/ECS",
      "fixedDimensions": { "ClusterName": "${CLUSTER_NAME}", "ServiceName": "${APP_SERVICE_NAME}" }
    },
    {
      "title": "App Memory Utilization",
      "metric": "MemoryUtilization",
      "stat": "Average",
      "chartType": "line",
      "unit": "Percent",
      "tab": "infrastructure",
      "namespace": "AWS/ECS",
      "fixedDimensions": { "ClusterName": "${CLUSTER_NAME}", "ServiceName": "${APP_SERVICE_NAME}" }
    },
    {
      "title": "App Task Count",
      "metric": "RunningTaskCount",
      "stat": "Average",
      "chartType": "line",
      "unit": "Count",
      "tab": "infrastructure",
      "namespace": "ECS/ContainerInsights",
      "fixedDimensions": { "ClusterName": "${CLUSTER_NAME}", "ServiceName": "${APP_SERVICE_NAME}" },
      "overlayMetrics": [{ "metric": "DesiredTaskCount", "label": "Desired" }],
      "seriesStyles": { "Desired": { "borderDash": [6, 3] } }
    },
    {
      "title": "Worker CPU Utilization",
      "metric": "CPUUtilization",
      "stat": "Average",
      "chartType": "line",
      "unit": "Percent",
      "tab": "infrastructure",
      "namespace": "AWS/ECS",
      "fixedDimensions": { "ClusterName": "${CLUSTER_NAME}", "ServiceName": "${WORKER_SERVICE_NAME}" }
    },
    {
      "title": "Worker Memory Utilization",
      "metric": "MemoryUtilization",
      "stat": "Average",
      "chartType": "line",
      "unit": "Percent",
      "tab": "infrastructure",
      "namespace": "AWS/ECS",
      "fixedDimensions": { "ClusterName": "${CLUSTER_NAME}", "ServiceName": "${WORKER_SERVICE_NAME}" }
    },
    {
      "title": "Worker Task Count",
      "metric": "RunningTaskCount",
      "stat": "Average",
      "chartType": "line",
      "unit": "Count",
      "tab": "infrastructure",
      "namespace": "ECS/ContainerInsights",
      "fixedDimensions": { "ClusterName": "${CLUSTER_NAME}", "ServiceName": "${WORKER_SERVICE_NAME}" },
      "overlayMetrics": [{ "metric": "DesiredTaskCount", "label": "Desired" }],
      "seriesStyles": { "Desired": { "borderDash": [6, 3] } }
    },
    {
      "title": "Worker Demand vs Capacity",
      "metric": "WorkerDemand",
      "stat": "Maximum",
      "chartType": "line",
      "unit": "Count",
      "tab": "infrastructure",
      "overlayMetrics": [{ "metric": "WorkerCapacity", "label": "Capacity" }],
      "seriesStyles": { "Capacity": { "borderDash": [6, 3] } }
    },
    {
      "title": "Workflow Jobs by Status",
      "metric": "WorkflowJobCount",
      "stat": "Sum",
      "chartType": "bar",
      "splitBy": "statusCategory",
      "tab": "workflows"
    },
    {
      "title": "Workflow Jobs by Event",
      "metric": "WorkflowJobCount",
      "stat": "Sum",
      "chartType": "line",
      "splitBy": "workflowEvent",
      "tab": "workflows"
    },
    {
      "title": "Workflow Job Failure Rate",
      "metric": "WorkflowJobCount",
      "stat": "Sum",
      "chartType": "line",
      "splitBy": "statusCategory",
      "color": "error",
      "tab": "workflows",
      "transform": {
        "type": "percentage",
        "numerator": ["Error"],
        "denominator": ["Success", "Error"]
      }
    },
    {
      "title": "Workflow Job Duration (P95)",
      "metric": "WorkflowJobDuration",
      "stat": "p95",
      "chartType": "line",
      "splitBy": "workflowEvent",
      "tab": "workflows"
    },
    {
      "title": "Workflow Steps by Status",
      "metric": "WorkflowStepCount",
      "stat": "Sum",
      "chartType": "bar",
      "splitBy": "statusCategory",
      "tab": "workflows"
    },
    {
      "title": "Workflow Steps by Event",
      "metric": "WorkflowStepCount",
      "stat": "Sum",
      "chartType": "line",
      "splitBy": "workflowEvent",
      "tab": "workflows"
    }
  ]
}

Monitoring

The dashboard at /admin/monitoring/dashboard displays all configured charts organized by tab. Logs are searchable at /admin/monitoring/logs with filtering by source, type, user, and trace ID. For more information on viewing metrics and logs for deployed apps, see the Monitoring documentation. Metrics and logs are also available through the observability API for external monitoring integrations.