Remote logging

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

  • See how often different parts of your flag logic are evaluated, e.g. to see how often different targeting rules are evaluated and passed in realtime

  • Log split exposures, e.g. for A/B tests, multivariate tests, and AI loops

  • See any SDK errors from the Hypertune UI

Change the flush interval

By default, SDKs flush logs to Hypertune Edge every 2 seconds. To change this interval, set remoteLogging.flushIntervalMs in your createSource options. For example, to flush logs every 10 seconds, set it to 10_000. To disable automatic log flushing, set it to null.

src/components/AppHypertuneProvider.tsx
import { HypertuneProvider } from '../generated/hypertune.react'

export default function AppHypertuneProvider({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <HypertuneProvider
      createSourceOptions={{
        token: import.meta.env.VITE_HYPERTUNE_TOKEN!,
        remoteLogging: { flushIntervalMs: 10_000 },
      }}
      rootArgs={{
        context: {
          environment:
            process.env.NODE_ENV === 'development'
              ? 'development'
              : 'production',
          user: {
            id: 'e23cc9a8-0287-40aa-8500-6802df91e56a',
            name: 'Example User',
            email: '[email protected]',
          },
        },
      }}
    >
      {children}
    </HypertuneProvider>
  )
}

Manually flush logs

To manually trigger and wait for logs to be flushed, use the flushLogs method:

app/api/route.ts
import { NextResponse } from 'next/server'
import getHypertune from '@/lib/getHypertune'

export const runtime = 'edge'

export async function GET() {
  const hypertune = await getHypertune({ isRouteHandler: true })

  const exampleFlag = hypertune.exampleFlag({ fallback: false })

  await hypertune.flushLogs()

  return NextResponse.json({ exampleFlag })
}

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.

Change the remote logging endpoint

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

To change this endpoint, set remoteLogging.endpointUrl in your createSource options:

src/components/AppHypertuneProvider.tsx
import { HypertuneProvider } from '../generated/hypertune.react'

export default function AppHypertuneProvider({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <HypertuneProvider
      createSourceOptions={{
        token: import.meta.env.VITE_HYPERTUNE_TOKEN!,
        remoteLogging: {
          endpointUrl:
            'https://yourdomain.com/api/hypertune-logs',
        },
      }}
      rootArgs={{
        context: {
          environment:
            process.env.NODE_ENV === 'development'
              ? 'development'
              : 'production',
          user: {
            id: 'e23cc9a8-0287-40aa-8500-6802df91e56a',
            name: 'Example User',
            email: '[email protected]',
          },
        },
      }}
    >
      {children}
    </HypertuneProvider>
  )
}

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

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

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 lose any data due to ad blockers or network restrictions in the browser.

Change the remote logging mode

The remote logging mode determines how the SDK sends flag evaluations, experiment exposures, analytics events, and error logs to Hypertune Edge or your own endpoint. There are three remote logging modes:

Remote logging mode
Behaviour

normal

All logs are sent.

session

Logs are deduplicated per session based on the provided Context. However, all analytics events are sent.

off

No logs are sent.

The default remote logging mode is based on the environment:

Environment
Default
Why

Server

normal

No need for special treatment of logs.

Browser

session

Ensures that logs aren't generated in every render.

Next.js server

off

Prefetching and caching of pages and layouts can cause logs for flag evaluations, experiment exposures, and analytics events generated on Next.js servers to differ from actual user behaviour. To ensure accuracy, remote logging is enabled by default on the client (in the browser) only.

However, since Route Handlers aren't subject to the same prefetching and caching patterns, remote logging can be enabled for them.

To change the remote logging mode, set remoteLogging.mode in your createSource options:

src/components/AppHypertuneProvider.tsx
import { HypertuneProvider } from '../generated/hypertune.react'

export default function AppHypertuneProvider({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <HypertuneProvider
      createSourceOptions={{
        token: import.meta.env.VITE_HYPERTUNE_TOKEN!,
        remoteLogging: { mode: 'off' },
      }}
      rootArgs={{
        context: {
          environment:
            process.env.NODE_ENV === 'development'
              ? 'development'
              : 'production',
          user: {
            id: 'e23cc9a8-0287-40aa-8500-6802df91e56a',
            name: 'Example User',
            email: '[email protected]',
          },
        },
      }}
    >
      {children}
    </HypertuneProvider>
  )
}

Or change the remote logging mode with the setRemoteLoggingMode method:

lib/getHypertune.ts
import 'server-only'
import { unstable_noStore as noStore } from 'next/cache'
import { createSource } from '@/generated/hypertune'

const hypertuneSource = createSource({
  token: process.env.NEXT_PUBLIC_HYPERTUNE_TOKEN!,
})

export default async function getHypertune({
  isRouteHandler = false,
}: {
  isRouteHandler?: boolean
} = {}) {
  noStore()

  await hypertuneSource.initIfNeeded()

  hypertuneSource.setRemoteLoggingMode(
    isRouteHandler ? 'normal' : 'off'
  )

  return hypertuneSource.root({
    args: {
      context: {
        environment: process.env.NODE_ENV,
        user: {
          id: 'e23cc9a8-0287-40aa-8500-6802df91e56a',
          name: 'Example User',
          email: '[email protected]',
        },
      },
    },
  })
}

Last updated