> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# v0.12.0

Released: November 6, 2026

This release is about seeing what your app does in the cloud. One command reads the logs of every part of an environment. Every request is one trace, from the Next.js frontend through Kong to each service and database query. Each environment gets a dashboard in its cloud's console. It all works the same on GCP, AWS and Azure, with no setup. The Terraform providers move to their latest majors, and background work starts the moment it's queued, on every cloud.

***

## Cloud logs

`infra:logs` reads a deployed environment's logs from your terminal.

- **Every source:** NestJS services, workers, Next.js apps, Kong, migration runs, scheduled jobs, and the load balancer and WAF in front of them.
- **The source list:** without a source name, the command lists the sources and when each one last logged.
- **One timeline:** `--all` merges every source. Each line is tagged `app` (your code), `platform` (deploys, starts, stops, crashes, health checks) or `edge` (requests and WAF blocks).
- **Filters:** `--since`, `--until`, `--level`, `--kind`, `--grep` and `--json`. Times are UTC.
- **No line limit.** Lines print as they arrive, and the filters narrow them down.
- **Log level per service:** `logLevel` in `infrastructure.json`, default `info`. `LOG_LEVEL` in your secrets stays local.

```bash
npx tsdevstack infra:logs --env prod
npx tsdevstack infra:logs auth-service --env prod --since 2h --level error
npx tsdevstack infra:logs --all --env prod --grep <trace id>
```

Per provider:

- **GCP:** Google allows 60 log reads per minute per project. When a read reaches that limit, the command keeps the lines it printed and gives you a Logs Explorer link with the same query. The deploy account gets the Logs Viewer role.
- **AWS:** platform lines come from ECS service events and stopped tasks, with the reason and exit code. Each scheduled job run logs its start, status and duration. The WAF log leaves out the `Authorization`, `Cookie` and frontend token headers.
- **Azure:** Container Apps log to resource-specific tables, and Kong and the Next.js apps add their App Service platform logs. Scheduled job runs log the response and its status.

The MCP server (cli-mcp 0.10.0) has a new tool, `get_logs`. It returns the newest 200 lines of the window and says what it left out, so an agent narrows the query instead of guessing. Logs can hold personal data, so the tool tells the agent to ask you before every read. `get_secret` does the same, and the secrets resources (`tsdevstack://secrets/user`, `tsdevstack://secrets/names`) show key names only. `get_secret` also reads a service's scope (`service`). See [Cloud Logs](/docs/features/cloud-logs.md).

## Traces

Every request is one trace:

- **Next.js frontend:** its server spans and its calls to Kong.
- **Kong:** the request and the call to the service.
- **Services and workers:** the request, calls to other services, BullMQ jobs and messages.
- **Database:** each Prisma query, with the SQL and its `$1` placeholders, never the values.

A queued job and a message handler run in the trace of the request that started them.

Traces go to each cloud's own service: Cloud Trace on GCP, X-Ray through CloudWatch Transaction Search on AWS, Application Insights on Azure. Locally they go to Jaeger. Each service signs in with its own cloud identity, so there are no keys or connection strings to manage. Kong sends its spans to a collector container that runs beside it and keeps its memory under 200 MiB.

- **Logs and traces together:** every line a service logs inside a request carries `trace_id`. `infra:logs --grep <trace id>` finds the request's lines in every service. The GCP console links logs and traces both ways, and AWS lines carry `xray_trace_id`.
- **Sampling:** `tracing.sampleRate` per environment, default 1 (every trace). Kong decides for each request, and the services follow.
- **Your trace IDs stay yours:** Kong drops trace headers from outside clients and keeps the ones from your frontend. The frontend starts a new trace for each browser request.
- **Quiet probes:** health checks and `/metrics` get no spans, in Kong and the Next.js apps too.

The new `@tsdevstack/telemetry` package does the export for nest-common and the Next.js frontends. See [Traces and Dashboards](/docs/features/traces-and-dashboards.md).

:::warning AWS: Transaction Search is account-wide
`infra:deploy` turns on Transaction Search for the AWS account. It changes how X-Ray bills every app in the account, and it stays on after `infra:destroy`.
:::

At high traffic, traces cost money, and on Azure from moderate traffic on. Set `tracing.sampleRate` to keep a share of them. The cost pages show the numbers for [GCP](/docs/infrastructure/providers/gcp/cost-estimation.md#traces-and-dashboards), [AWS](/docs/infrastructure/providers/aws/cost-estimation.md#traces-and-dashboards) and [Azure](/docs/infrastructure/providers/azure/cost-estimation.md#traces-and-dashboards).

## Dashboards

Each environment gets a dashboard in its cloud's console: Cloud Monitoring on GCP, CloudWatch on AWS, an Azure Monitor workbook on Azure. `infra:deploy` creates it and prints its link. The panels cover the services, Kong and Next.js, the database, Redis, the edge and the error lines.

Each database records statistics per query:

- **GCP:** Cloud SQL Query Insights.
- **AWS:** Database Insights, with Performance Insights on for 7 days (free).
- **Azure:** Query Store on General Purpose and Memory Optimized tiers, and the enhanced Postgres metrics on every tier. Microsoft advises against Query Store on Burstable tiers, so the workbook says why it's off there.

**AWS:** Container Insights is off. The dashboard uses the free ECS metrics, and crashes and restarts show in `infra:logs`. By AWS's pricing example, that saves a small environment about $46-49 a month.

## Background work

Queue processors and message handlers run in workers, which always run. Work starts the moment it's queued, on every cloud.

- **Workers scale on CPU** between `minInstances` and `maxInstances`: at 70% on AWS and Azure, at Cloud Run's 60% on GCP.
- **The auth template** comes with `auth-worker`, which sends the emails.
- **`npm run dev`** starts the workers next to the services, with their output in the same terminal.
- **GCP:** NestJS services with `minInstances` 1 or more run with CPU always on, so their own background work runs between requests too. At the default size (1 vCPU, 512 MiB) that is about $50 a month per service instead of about $10. Set `minInstances: 0` to keep request-based billing and scale to zero.
- **Azure:** services run one instance by default (`minInstances` 1). Set 0 to scale to zero.

Messaging:

- A handler that runs long keeps its messages, so another instance doesn't run them a second time.
- A stream keeps each message until every subscriber group has handled it.
- A service deletes its consumer group on a topic it doesn't subscribe to anymore, about 15 minutes after the deploy.

## Terraform providers

- AWS 6, Google 8 and azurerm 5. Azure adds the azapi provider for the traces rule.
- Provider upgrades are explicit. `infra:plan` and `infra:deploy` stop when the lock file is older than the new versions, and name the command to run: `infra:plan --upgrade`. You review the plan and commit the lock file, and CI uses it.
- **AWS:** the scheduled job function runs on Node.js 24.
- **GCP:** the load balancer stays the Classic Application Load Balancer.

## Local development

- Jaeger 2, Grafana 13 and Prometheus 3.
- Kong sends its spans to Jaeger too, so a request is one trace locally, Kong included.
- The Grafana dashboard sits in a `tsdevstack` folder that you can view without logging in.

## Improvements

- **Azure:** PostgreSQL accepts the current generation: `GP_Standard_D2ds_v5`, `GP_Standard_D4ds_v5` and `MO_Standard_E4ds_v5`. Microsoft grants region access per size series, so a new subscription may get one generation and not the other. [Azure account setup](/docs/infrastructure/providers/azure/account-setup.md#postgresql-region-access) shows how to ask for it.
- **Azure:** Front Door checks the Next.js apps on `/health`, the same path App Service checks.
- **AWS:** Kong's task has 1 GiB by default, room for Kong and its collector. That is about $1.60 a month more per task.
- **AWS:** migration logs are kept 30 days, like the service logs. Groups from earlier versions pick this up on their next migration.
- **`infra:deploy`** ends with the dashboard link and, on Azure, the name servers of the DNS zone.
- **Next.js frontends** log a failed API call in one line: method, path, status and message. Request bodies and headers stay out of the logs.
- **Email links** (confirm email, reset password) carry the token after `#`. Browsers never send that part to a server, so the token stays out of every log, from the load balancer to Kong. Links already sent keep working.
- **Log redaction** covers more names by default: `accessToken`, `refreshToken`, `newPassword`, `currentPassword`, `passwordHash`, `privateKey`, and the request headers that carry credentials (`authorization`, `cookie`, `x-api-key` and the framework's own). The [observability page](/docs/features/observability.md#pii-redaction) lists them and how deep they match.
- **Workers** log and trace under their own name (`auth-worker`).
- **nest-common:** `ObservabilityModule.forRoot()` works like the plain import, with one request line and one metric per request, and its `logging`, `metrics` and `health` options turn each part off.
- **Agent skills** cover workers, messaging, traces, safe error logging in the frontend, and the Azure PostgreSQL sizes.
- **`@tsdevstack/react-bot-detection`:** phone and pen users count as real pointer activity, and pasting or autofilling a one-field form (forgot password) goes through.
- **Dependencies:** follow-redirects 1.16.2, and `@azure/identity` 4.14.1 with msal-node 7. `npm audit` shows no high or critical findings.

## Breaking changes

Pre-1.0, so they ship in a minor version. The upgrade steps below cover each.

- **Terraform lock file:** existing environments run `infra:plan --upgrade` once.
- **nest-common:** `TracingInterceptor` is removed. `createApp` adds the request span as middleware.
- **`kong.user.yml`:** a plugin named `opentelemetry` clashes with the framework's.
- **Queue processors** run in workers only.
- **Messaging:** `maxLen` caps a topic only while it has no subscriber, and the dead-letter stream. Before you remove a service that subscribes to topics, remove its subscriptions and deploy it.
- **GCP:** NestJS services with `minInstances` 1 or more bill per instance, and a `cpu` below 1 fails validation for them (Google's rule for always-on CPU).
- **Azure:** services default to `minInstances` 1, and Next.js apps answer `/health` with 200.
- **Permissions:** GCP projects rerun `infra:bootstrap`. The Azure deploy service principal assigns Monitoring Metrics Publisher. The AWS CI role's policy includes `xray:*`.
- **`@tsdevstack/react-bot-detection`:** `handleFormSubmit` and `analyzeBehavior` take `{ fieldCount }`. The mouse-pattern rule is gone, and the reason "No natural mouse movement detected" reads "No natural pointer activity detected".

## Upgrading an existing project

**1. Packages.**

```bash
npm install -D @tsdevstack/cli@^0.12.0
npm install @tsdevstack/nest-common@^0.12.0 -w <service>
npm update follow-redirects @azure/identity
```

Run the second command for each NestJS service. If your code uses `TracingInterceptor`, delete it: the request span is already there.

**2. Workers.** Move queue processors out of `AppModule` into a worker. For an auth service from the template, copy `src/worker.ts`, `src/worker.module.ts` and `tsconfig.worker.json` from the template, delete `src/processors/processors.module.ts` and its import in `AppModule`, and add the scripts to the service's `package.json`:

```json
"start-worker:dev": "nest start --entryFile worker --watch --path tsconfig.worker.json",
"start-worker": "node dist/worker.js"
```

Then register the worker:

```bash
npx tsdevstack register-detached-worker --name auth-worker --base-service auth-service
```

In the root `package.json`, change `dev` so it starts the workers too:

```json
"dev": "npm run clean:tsbuildinfo && npm run build:libs && NX_STREAM_OUTPUT=true lerna run dev,start-worker:dev --parallel"
```

**3. Next.js frontends.** For a frontend from the template:

```bash
npm install @tsdevstack/telemetry -w <frontend>
```

Add `instrumentation.ts` next to `package.json`:

```typescript
import packageJson from "./package.json";

export async function register(): Promise<void> {
  if (process.env.NEXT_RUNTIME === "nodejs") {
    const { registerTelemetry } = await import("@tsdevstack/telemetry");
    registerTelemetry({
      serviceName: packageJson.name,
      serviceVersion: packageJson.version,
      continueIncomingTraces: false,
    });
  }
}
```

Add the trace interceptor to each Kong client, after `forwardClientIp`:

```typescript
import { injectTraceContext } from "@tsdevstack/telemetry";

authClient.instance.interceptors.request.use(injectTraceContext);
```

Copy `lib/utils/describe-error.ts` and `lib/utils/forward-client-ip.ts` from the template. Log `describeError(error)` instead of the error object in `handleApiError` and in the `console.error` calls under `app/api`.

For the email links, copy `lib/utils/take-link-token.ts`, `app/(public)/confirm/confirm-client.tsx` and `app/(public)/reset-password/reset-password-client.tsx` from the template. The pages read the token after `#` and from `?token=`. Then, in an auth service from the template, change the three links in `src/auth/auth.service.ts` to `/confirm#token=` and `/reset-password#token=`. Ship the frontend first, or both in the same deploy.

On Azure, a Next.js app answers `/health` with 200. The template has the route (`app/health/route.ts`).

**4. `kong.user.yml`.** If it has a plugin named `opentelemetry`, delete it. Kong's tracing comes from the framework.

**5. Local stack.**

```bash
npx tsdevstack sync
```

This moves the local stack to Jaeger 2, Grafana 13 and Prometheus 3 and turns on Kong's spans. To view the Grafana dashboard without logging in, delete `./data/grafana` once.

**6. Cloud accounts.**

- **GCP:** run `npx tsdevstack infra:bootstrap --env <env>`. It adds the Logs Viewer and Monitoring Dashboard Editor roles and turns on the trace, logging and monitoring APIs.
- **AWS:** if you deploy from CI, add `xray:*` to the `CloudWatchLogsAndMetrics` statement of the role's policy. See [AWS CI/CD setup](/docs/infrastructure/providers/aws/cicd.md).
- **Azure:** add Monitoring Metrics Publisher to the roles the deploy service principal may assign (resource group > **Access control** > **Role assignments** > **View/Edit** in the Condition column). See [Azure account setup](/docs/infrastructure/providers/azure/account-setup.md#step-5-grant-permissions).

**7. Each cloud environment.** First run a clean `infra:plan` with your current CLI version. Then:

```bash
npx tsdevstack infra:plan --env <env> --upgrade
npx tsdevstack infra:deploy --env <env>
```

Review the plan, then commit the updated `.terraform.lock.hcl` before you deploy. On Azure, Kong's App Service moves to two containers (Kong and its collector) with this deploy, and `infra:logs` reads the lines written after it.

New projects get all of this from the start.
