Remote logging

SDKs send logs to Hypertune Edge in the background. This allows you to:

Manually flush logs

You can manually trigger and wait for logs to be flushed with the flushLogs method:

import { NextFetchEvent, NextRequest } from "next/server";
import getHypertune from "./lib/getHypertune";

export async function middleware(
  req: NextRequest,
  context: NextFetchEvent
): Promise<void> {
  const hypertune = await getHypertune();

  const exampleFlag = hypertune.exampleFlag({ fallback: false });
  console.log("Middleware Example Flag:", exampleFlag);

  context.waitUntil(hypertune.flushLogs());
}

This is a requirement in serverless and edge environments like Vercel deployments, Cloudflare Workers, AWS Lambdas, etc, where background SDK tasks like flushing logs aren't guaranteed to execute.

Configuring log flushing

Flush interval

By default, SDKs flush logs to Hypertune Edge every 2 seconds. You can adjust this interval by setting the remoteLogging.flushIntervalMs option. For example, to flush logs every 10 seconds, set this option to 10_000. Setting the value to null disables automatic log flushing.

Logging mode

The logging mode determines which SDK log messages, expression evaluations, A/B test exposures, and analytics events are sent to the remote server. You can control this behaviour using the remoteLogging.mode initialization option:

Mode
Behaviour

Normal

All data is sent to the remote server.

Session

SDK log messages, expression evaluations, and A/B test exposures are deduplicated per session, based on the provided context. However, all analytics events are still sent to the remote server.

Off

No data is sent, making this mode ideal for local development or testing environments.

Logging endpoint

By default, SDKs flush all logs to Hypertune via the following endpoint: https://gcp.fasthorse.workers.dev/logs

You can change this endpoint by setting remoteLogging.endpointUrl in your createSource options:

src/lib/getHypertune.ts
import { createSource } from "../generated/hypertune";

const hypertuneSource = createSource({
  token: process.env.HYPERTUNE_TOKEN!,
  remoteLogging: {
    endpointUrl: "https://yourdomain.com/api/hypertune-logs",
  },
});

export default async function getHypertune() {
  // Get flag updates in serverless environments
  // await hypertuneSource.initIfNeeded();

  return hypertuneSource.root({
    args: {
      context: {
        environment:
          process.env.NODE_ENV === "development"
            ? "development"
            : "production",
        user: { id: "1", name: "Test", email: "[email protected]" },
      },
    },
  });
}

This enables you to send logs to your own server, where you can write them to your data warehouse or analytics platform, and optionally forward them to Hypertune:

/app/api/hypertune-logs/route.ts
import { NextResponse } from "next/server";
import {
  CreateLogsInput,
  prodLogsEndpointUrl,
} from "hypertune/dist/shared";

export const runtime = "edge";
export const dynamic = "force-dynamic";

export async function POST(request: Request) {
  const body = (await request.json()) as CreateLogsInput;

  console.log("events", body.events);
  console.log("exposures", body.exposures);

  // Forward logs to Hypertune
  try {
    await fetch(prodLogsEndpointUrl, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(body),
      cache: "no-store",
    });
  } catch (error) {
    console.error("Error forwarding logs to Hypertune:", error);

    return NextResponse.json(
      { error: "Failed to forward logs to Hypertune" },
      { status: 500 },
    );
  }

  // TODO: If successful, forward logs to own data warehouse too.
  // Return an error response on failure.

  return NextResponse.json({ success: true }, { status: 200 });
}

This setup also helps ensure you don't miss any logs due to ad blockers or network restrictions in the browser.

Last updated