fix(ui): keep non-OTEL logging callbacks in the Add Callback list

The Add Logging Callback dropdown was filtering out langfuse (v2 SDK),
otel (Open Telemetry), and the other non-OTEL callbacks along with the
OTEL trace destinations, so those integrations disappeared from the UI.
Restrict the destination filtering to the OTEL trace-destination
backends only (arize, langfuse_otel, weave_otel, generic), so non-OTEL
logging callbacks keep their existing global-callback path.

Also relabel the langfuse_otel destination to "Langfuse OTEL" so it is
distinct from the Langfuse SDK entry, collapse the duplicate
destination-id set into a single LOGGING_BACKEND_IDS, and format the
logging UI files.
This commit is contained in:
Yucheng Zhu 2026-07-29 16:19:46 -07:00
parent 738a37613e
commit 440277c97b
7 changed files with 15 additions and 38 deletions

View file

@ -28,16 +28,10 @@ const AccessControlFields: React.FC<AccessControlFieldsProps> = ({ value = {}, o
return (
<>
<Form.Item
label="Global"
tooltip="Routing scope: traces from every team and org export to this destination."
>
<Form.Item label="Global" tooltip="Routing scope: traces from every team and org export to this destination.">
<Switch checked={isGlobal} onChange={(global) => onChange({ ...value, global })} />
</Form.Item>
<Form.Item
label="Teams"
tooltip="Routing scope: only these teams' traffic exports to this destination."
>
<Form.Item label="Teams" tooltip="Routing scope: only these teams' traffic exports to this destination.">
<Select
mode="multiple"
allowClear
@ -50,10 +44,7 @@ const AccessControlFields: React.FC<AccessControlFieldsProps> = ({ value = {}, o
style={{ width: "100%" }}
/>
</Form.Item>
<Form.Item
label="Organizations"
tooltip="Routing scope: only these orgs' traffic exports to this destination."
>
<Form.Item label="Organizations" tooltip="Routing scope: only these orgs' traffic exports to this destination.">
<Select
mode="multiple"
allowClear

View file

@ -2,20 +2,13 @@ import { CredentialAccess } from "../Settings/LoggingAndAlerts/LoggingCallbacks/
import { credentialCreateCall } from "../networking";
import { LOGGING_DESTINATION_BACKENDS } from "./loggingDestinationFields";
// The set of OTEL backend ids that are created as logging destinations (credentials),
// not as global config callbacks. The unified Add modal branches on this.
// The OTEL trace-destination backend ids. These are managed as admin-owned logging
// destinations (credentials): the unified Add modal branches on this set, and they never
// appear as global callback rows or dropdown options. The `langfuse` v2 SDK logger and
// the generic `otel` callback are deliberately not here; they keep their existing
// global-callback behavior untouched.
export const LOGGING_BACKEND_IDS: ReadonlySet<string> = new Set(LOGGING_DESTINATION_BACKENDS.map((b) => b.id));
// Callback ids that must not surface as global callbacks. Per LIT-3850 OTEL is admin-
// owned and routed per identity via trace destinations, and the legacy Langfuse/OTEL
// callback paths (`langfuse` v2 SDK, `langfuse_otel` v1, the generic `otel` callback)
// are deprecated, so these are only ever destinations -- never callback rows or options.
export const NON_CALLBACK_LOGGING_IDS: ReadonlySet<string> = new Set([
...LOGGING_DESTINATION_BACKENDS.map((b) => b.id),
"langfuse",
"otel",
]);
export const backendLabel = (id?: string): string =>
LOGGING_DESTINATION_BACKENDS.find((b) => b.id === id)?.label ?? id ?? "-";

View file

@ -27,7 +27,7 @@ export interface LoggingDestinationBackend {
export const LOGGING_DESTINATION_BACKENDS: LoggingDestinationBackend[] = [
{
id: "langfuse_otel",
label: "Langfuse",
label: "Langfuse OTEL",
fields: [
{
name: "langfuse_host",

View file

@ -258,7 +258,6 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
loggingExporterBadges.map((exporter, index) => (
<Badge key={index} variant="secondary">
{exporter.name}
</Badge>
))
) : (
@ -360,7 +359,6 @@ const OrganizationInfoView: React.FC<OrganizationInfoProps> = ({
{loggingExporterBadges.map((exporter, index) => (
<Badge key={index} variant="secondary">
{exporter.name}
</Badge>
))}
</div>

View file

@ -137,8 +137,8 @@ describe("Settings", () => {
});
it("should display edit modal with fields when edit is clicked", async () => {
// Use a config callback that is NOT a logging-destination backend (langfuse,
// arize, etc. are filtered from the table via NON_CALLBACK_LOGGING_IDS and
// Use a config callback that is NOT a logging-destination backend (arize,
// langfuse_otel, etc. are filtered from the table via LOGGING_BACKEND_IDS and
// edited through the destination flow instead). Datadog is a plain config
// callback, so it still renders a row with the legacy Test/Edit/Delete actions.
const mockCallback = {

View file

@ -47,12 +47,7 @@ import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams";
import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import EditLoggingCredentialModal from "./logging_credentials/EditLoggingCredentialModal";
import AccessControlFields from "./logging_credentials/AccessControlFields";
import {
backendLabel,
createLoggingCredential,
LOGGING_BACKEND_IDS,
NON_CALLBACK_LOGGING_IDS,
} from "./logging_credentials/loggingCredentialApi";
import { backendLabel, createLoggingCredential, LOGGING_BACKEND_IDS } from "./logging_credentials/loggingCredentialApi";
import { LOGGING_DESTINATION_BACKENDS } from "./logging_credentials/loggingDestinationFields";
import { parseErrorMessage } from "./shared/errorUtils";
interface SettingsPageProps {
@ -675,7 +670,7 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
<TabPanels>
<TabPanel>
<LoggingCallbacksTable
callbacks={[...callbacks.filter((c) => !NON_CALLBACK_LOGGING_IDS.has(c.name)), ...destinationRows]}
callbacks={[...callbacks.filter((c) => !LOGGING_BACKEND_IDS.has(c.name)), ...destinationRows]}
availableCallbacks={allCallbacks}
isLoading={isLoadingCallbacks}
readOnly={!isProxyAdmin}
@ -840,7 +835,7 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
>
<CallbackSelector
callbackConfigs={[
...callbackConfigs.filter((c: { id: string }) => !NON_CALLBACK_LOGGING_IDS.has(c.id)),
...callbackConfigs.filter((c: { id: string }) => !LOGGING_BACKEND_IDS.has(c.id)),
...LOGGING_DESTINATION_BACKENDS.map((b) => ({ id: b.id, displayName: b.label, logo: "" })),
]}
selectedCallback={selectedCallback}

View file

@ -239,7 +239,7 @@ export function KeyEditView({
mcp_tool_permissions: keyData.object_permission?.mcp_tool_permissions || {},
throttle_on_budget_exceeded: keyData.metadata?.throttle_on_budget_exceeded || false,
logging_settings: extractLoggingSettings(keyData.metadata),
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
disabled_callbacks: Array.isArray(keyData.metadata?.litellm_disabled_callbacks)
? mapInternalToDisplayNames(keyData.metadata.litellm_disabled_callbacks)
: [],
access_group_ids: keyData.access_group_ids || [],