Structured Logging with Pino

Keep app-owned Pino logs while adding Agentuity services beside them

When an Agentuity app already writes JSON process logs and your platform reads stdout, keep Pino as the process logger. Agentuity services can still run in the same server-only code.

npm install pino

Create an App Logger

Keep the logger in a normal server module. The module can be imported by routes, workers, scripts, or server functions.

typescriptsrc/lib/logger.ts
import pino from 'pino';
 
export const appLogger = pino({
  level: process.env.LOG_LEVEL ?? 'info',
});

Add Request and Domain Fields

Create child loggers at the boundary that knows the request ID, user ID, order ID, or tenant ID. Those fields stay on every log line emitted by that child.

typescriptsrc/lib/checkout.ts
import { appLogger } from './logger';
 
interface CheckoutInput {
  readonly requestId: string;
  readonly orderId: string;
  readonly userId: string;
  readonly totalCents: number;
}
 
export async function acceptCheckout(
  input: CheckoutInput
): Promise<{ readonly accepted: boolean }> {
  const checkoutLogger = appLogger.child({
    requestId: input.requestId,
    orderId: input.orderId,
    userId: input.userId,
  });
 
  if (input.totalCents <= 0) {
    checkoutLogger.warn({ totalCents: input.totalCents }, 'Checkout rejected');
    return { accepted: false };
  }
 
  checkoutLogger.info({ totalCents: input.totalCents }, 'Checkout accepted');
  return { accepted: true };
}

This path does not require an Agentuity integration. Keep shipping stdout through the log drain, collector, or platform agent your app already uses.

Format Logs During Local Development

Keep prettifying out of app code. When you want readable local output, install pino-pretty as a dev tool and pipe the process output through its CLI:

npm install -D pino-pretty
bun run src/smoke-pino.ts | bunx pino-pretty

Use the raw JSON output for log drains, collectors, and deployed runtimes unless your platform expects a different format.

Add Agentuity Beside It

When the same route also needs Agentuity, keep the service client in server-only code and keep Pino as the process logger. Use Key-Value Storage for exact-key state, Database for relational data, or AI Gateway for model routing.

Use @agentuity/telemetry instead when you want Agentuity's logger and telemetry export setup to own the log path.

Test the Function

This smoke test keeps the logger app-owned and checks the function result. The log line still writes to stdout.

typescriptsrc/lib/checkout.test.ts
import { test, expect } from 'bun:test';
import { acceptCheckout } from './checkout';
 
test('accepts positive checkout totals', async () => {
  await expect(
    acceptCheckout({
      requestId: 'req_123',
      orderId: 'ord_123',
      userId: 'user_123',
      totalCents: 4900,
    })
  ).resolves.toEqual({ accepted: true });
});

Next Steps

  • Logging: choose between app-owned logs and Agentuity telemetry logs
  • Tracing: add spans around slow or multi-step work