genkit icon indicating copy to clipboard operation
genkit copied to clipboard

[JS] Telemetry Not Exported When Flow is Called from Cloud Function

Open mike-4040 opened this issue 10 months ago • 8 comments

Describe the bug Telemetry data is not being exported when a flow is executed from a Firebase Cloud Function. However, traces appear correctly when the same flow is run from the Genkit UI in Firebase Console.

To Reproduce

  1. Enable Firebase telemetry with dev export:
import { enableFirebaseTelemetry } from "@genkit-ai/firebase";


enableFirebaseTelemetry({
  forceDevExport: true
})

export const ai = genkit({
  ...
});
  1. Define a flow using defineFlow():
export const chatFlow = ai.defineFlow(
  {
    name: "chatFlow",
    inputSchema: InputSchemaZod,
    outputSchema: OutputSchemaZod,
  },
  async (input) => {
    ...
  }
);
  1. Running the flow from Genkit UI → Traces appear correctly in Firebase Console / Genkit
  2. Running the flow from Cloud Function → No traces appear in Firebase Console / Genkit.

Expected behavior

Traces should be visible in Firebase Console / Genkit when the flow is executed from a Cloud Function.

Runtime (please complete the following information):

  • OS: MacOS
  • Version 15.3.1

Node version

v22.12.0

mike-4040 avatar Mar 10 '25 19:03 mike-4040

cc: @trevor-rex

mike-4040 avatar Mar 10 '25 19:03 mike-4040

I think I've found what is going on: We use Sentry, here is a conflict in OpenTelemetry configuration between Sentry and Firebase Telemetry.

mike-4040 avatar Mar 12 '25 03:03 mike-4040

"Happy" to see that i'm not the only one. Now the question is : is a workaround or fix possible ?

descampsk avatar May 13 '25 14:05 descampsk

I am not sure that this is a valid solution, but I am using manual instrumentation using newrelic:

  1. newRelic.startSegment() to trace performance
  2. newRelic.recordCustomEvent() to track token usage and other metrics

mike-4040 avatar May 13 '25 15:05 mike-4040

@mike-4040 Hey! Where does the newRelic come from? Could you share your workaround's code please?

OlekVano avatar Sep 18 '25 07:09 OlekVano

My setup is very simple:

  1. NewRelicManager
/**
 * NewRelicManager is a singleton class that manages the New Relic instance.
 * It ensures that the New Relic instance is only created once and provides a
 * convenient way to access it using a getter.
 */
export class NewRelicManager {
  private static _newRelic: NewRelic | undefined;

  private constructor() {
    // Private constructor to prevent instantiation
  }

  public static get newRelic(): NewRelic | undefined {
    if (!process.env.NEW_RELIC_APP_NAME) {
      errorHandler(
        new InternalError("NewRelicManager-NEW_RELIC_APP_NAME-notSet")
      );

      return undefined;
    }

    if (!process.env.NEW_RELIC_LICENSE_KEY) {
      errorHandler(new InternalError("newRelicManager-licenseKey-notSet"));

      return undefined;
    }

    if (!NewRelicManager._newRelic) {
      // eslint-disable-next-line @typescript-eslint/no-require-imports
      NewRelicManager._newRelic = require("newrelic");
    }

    return NewRelicManager._newRelic;
  }
}
  1. nrRecordAIInvocation() helper
/**
 * Represents an AI invocation event recorded in New Relic.
 *
 * - Additional attributes must be **string**, **number**, or **boolean**.
 * - Key names **must be shorter than 255 characters**.
 */
interface NREventAIInvocation {
  inputTokens: number;
  modelName: string;
  outputTokens: number;
  taskName: FlowName;
  [key: string]: string | number | boolean;
}

/**
 * Records an AI invocation event as a custom event in New Relic.
 *
 * - If **New Relic is not enabled**, this function will do nothing.
 * - The event includes standard fields (`inputTokens`, `outputTokens`, etc.)
 *   along with any additional attributes.
 */
export function nrRecordAIInvocation(attributes: NREventAIInvocation) {
  const { newRelic } = NewRelicManager;

  if (!newRelic) {
    return;
  }

  newRelic.recordCustomEvent(NRCustomEventType.AI_INVOCATION, attributes);
}
  1. nrStartSegment() helper
/**
 * Starts a New Relic segment `name` and executes the `fn()` with the given `input`.
 *
 * - If **New Relic is not enabled**, the `fn()` will execute directly.
 * - If **New Relic is enabled**, the `fn()` will execute within a segment.
 * - The `fn()` should accept a single input parameter, for simplicity.
 *
 * @param name - The name for the segment.
 * @param fn - The function to execute within the segment.
 * @param input - The input to pass to the function
 * @returns The result of the function execution.
 *
 * @example
 * ```typescript
 * const result = await nrStartSegment(
 *  "mySegmentName",
 *  myAsyncFunction,
 *  myInput
 * );
 * ```
 * @example
 * If you need to invoke a object method, you can use the following pattern:
 * ```typescript
 * const result = await nrStartSegment(
 *  "myObject.someMethod",
 *  (input) => myObject.someMethod(input),
 *  myInput
 * );
 * ```
 */
export async function nrStartSegment<TI, TO>(
  name: string,
  fn: (input: TI) => Promise<TO>, // function to execute
  input: TI
): Promise<TO> {
  const { newRelic } = NewRelicManager;

  if (!newRelic) {
    return fn(input);
  }

  return newRelic.startSegment(name, true, () => fn(input));
}

And then use them:

      // track flow performance
      const summary = await nrStartSegment(
        FLOW_NAMES.chatSummaryFlow, // just a name
        chatSummaryFlow, // flow
        { messages } // flow input/argiments
      );

// Inside the flow
// track token usage
const { output, usage } = await ai.generate({
        model,
        prompt,
        output: {
          schema: OutputSchema,
        },
      });

      nrRecordAIInvocation({
        inputTokens: usage.inputTokens || 0,
        modelName: model.name,
        outputTokens: usage.outputTokens || 0,
        taskName: FLOW_NAMES.chatSummaryFlow,
      });

We use newrelic for here because we like it better then sentry for performance monitoring, but sentry have a very similar functionality, so you can use manual/custom instrumentation from sentry.

mike-4040 avatar Sep 18 '25 15:09 mike-4040

@mike-4040 Thank you for sharing the code

OlekVano avatar Sep 19 '25 10:09 OlekVano

For people using Sentry and Genkit and want traces exported to Genkit without disabling Sentry, I tried this and it seems to work for me: https://github.com/firebase/genkit/issues/2904#issuecomment-3509943920

eddyleelin avatar Nov 10 '25 07:11 eddyleelin