Skip to main content
Glama
langfuse-client.ts19.3 kB
import { Langfuse } from 'langfuse'; import { LangfuseProjectConfig } from './types.js'; export class LangfuseAnalyticsClient { private client: Langfuse; private config: LangfuseProjectConfig; constructor(config: LangfuseProjectConfig) { this.config = config; this.client = new Langfuse({ publicKey: config.publicKey, secretKey: config.secretKey, baseUrl: config.baseUrl, }); } /** * Sanitizes URLs for logging to prevent sensitive data exposure */ private sanitizeUrlForLogging(url: string): string { try { const urlObj = new URL(url); // Only log the path and remove sensitive query parameters const sanitizedParams = new URLSearchParams(); // Allow non-sensitive query parameters for debugging const allowedParams = ['limit', 'page', 'view', 'orderBy', 'orderDirection']; for (const [key, value] of urlObj.searchParams) { if (allowedParams.includes(key)) { sanitizedParams.set(key, value); } else { sanitizedParams.set(key, '[REDACTED]'); } } const queryString = sanitizedParams.toString(); return `${urlObj.pathname}${queryString ? '?' + queryString : ''}`; } catch { // If URL parsing fails, return a safe placeholder return '/api/[INVALID_URL]'; } } /** * Handles API errors securely by logging details server-side and returning generic client-side errors */ private async handleApiError(response: Response, operation: string, url?: string): Promise<never> { const errorText = await response.text().catch(() => 'Unable to read error response'); // Sanitize URL for secure logging const sanitizedUrl = url ? this.sanitizeUrlForLogging(url) : ''; // Log detailed error information server-side for debugging (with sanitized URL) console.error(`${operation} API error: ${response.status} ${response.statusText}${sanitizedUrl ? `. URL: ${sanitizedUrl}` : ''}. Response: [REDACTED for security]`); // Return generic error message to client to prevent information disclosure throw new Error(`Failed to ${operation.toLowerCase()}: API returned status ${response.status}`); } getProjectId(): string { return this.config.id; } getConfig(): LangfuseProjectConfig { return { ...this.config }; // Return a copy to maintain encapsulation } async getMetrics(params: { view: 'traces' | 'observations'; from: string; to: string; metrics: Array<{ measure: string; aggregation: string }>; dimensions?: Array<{ field: string }>; filters?: Array<any>; }): Promise<any> { // Use the actual Langfuse metrics API with GET method and query parameter const query = { view: params.view, fromTimestamp: params.from, toTimestamp: params.to, metrics: params.metrics, dimensions: params.dimensions || [], filters: params.filters || [], }; const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const queryParam = encodeURIComponent(JSON.stringify(query)); const response = await fetch(`${this.config.baseUrl}/api/public/metrics?query=${queryParam}`, { method: 'GET', headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Metrics'); } return await response.json(); } async listTraces(params: { page?: number; limit?: number; name?: string; userId?: string; tags?: string[]; filter?: string; // JSON-encoded filter object for advanced filtering orderBy?: string; orderDirection?: 'asc' | 'desc'; fromTimestamp?: string; toTimestamp?: string; }): Promise<any> { const queryParams = new URLSearchParams(); if (params.page) queryParams.append('page', params.page.toString()); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.name) queryParams.append('name', params.name); if (params.userId) queryParams.append('userId', params.userId); // Try using order_by parameter (snake_case) as seen in Python SDK examples if (params.orderBy) { const supportedOrderBy = ['timestamp', 'name', 'totalCost']; if (supportedOrderBy.includes(params.orderBy)) { // Try order_by with direction suffix (e.g., "totalCost DESC") const direction = params.orderDirection === 'asc' ? 'ASC' : 'DESC'; const orderByValue = `${params.orderBy} ${direction}`; queryParams.append('order_by', orderByValue); } } if (params.fromTimestamp) queryParams.append('fromTimestamp', params.fromTimestamp); if (params.toTimestamp) queryParams.append('toTimestamp', params.toTimestamp); if (params.tags) { params.tags.forEach(tag => queryParams.append('tags', tag)); } if (params.filter) { queryParams.append('filter', params.filter); } const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); // Add error logging to debug the issue const url = `${this.config.baseUrl}/api/public/traces?${queryParams}`; const response = await fetch(url, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Traces', url); } return await response.json(); } async getTrace(traceId: string): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/traces/${traceId}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Get Trace'); } return await response.json(); } async listObservations(params: { fromStartTime?: string; toStartTime?: string; limit?: number; page?: number; name?: string; userId?: string; type?: string; traceId?: string; level?: string; environment?: string[]; }): Promise<any> { const queryParams = new URLSearchParams(); if (params.fromStartTime) queryParams.append('fromStartTime', params.fromStartTime); if (params.toStartTime) queryParams.append('toStartTime', params.toStartTime); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.page) queryParams.append('page', params.page.toString()); if (params.name) queryParams.append('name', params.name); if (params.userId) queryParams.append('userId', params.userId); if (params.type) queryParams.append('type', params.type); if (params.traceId) queryParams.append('traceId', params.traceId); if (params.level) queryParams.append('level', params.level); if (params.environment) { params.environment.forEach(env => queryParams.append('environment', env)); } const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/observations?${queryParams}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Observations'); } return await response.json(); } async getDailyMetrics(params: { traceName?: string; userId?: string; tags?: string[]; limit?: number; }): Promise<any> { const queryParams = new URLSearchParams(); if (params.traceName) queryParams.append('traceName', params.traceName); if (params.userId) queryParams.append('userId', params.userId); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.tags) { params.tags.forEach(tag => queryParams.append('tags', tag)); } const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/metrics/daily?${queryParams}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Daily Metrics'); } return await response.json(); } async getObservation(observationId: string): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/observations/${observationId}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Get Observation'); } return await response.json(); } async getHealthStatus(): Promise<any> { const response = await fetch(`${this.config.baseUrl}/api/public/health`, { method: 'GET', }); if (!response.ok) { await this.handleApiError(response, 'Health Check'); } return await response.json(); } async listModels(params: { limit?: number; page?: number; }): Promise<any> { const queryParams = new URLSearchParams(); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.page) queryParams.append('page', params.page.toString()); const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/models?${queryParams}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'List Models'); } return await response.json(); } async getModel(modelId: string): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/models/${modelId}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Get Model'); } return await response.json(); } async listPrompts(params: { limit?: number; page?: number; name?: string; }): Promise<any> { const queryParams = new URLSearchParams(); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.page) queryParams.append('page', params.page.toString()); if (params.name) queryParams.append('name', params.name); const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/v2/prompts?${queryParams}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'List Prompts'); } return await response.json(); } async getPrompt(promptName: string, version?: number, label?: string): Promise<any> { const queryParams = new URLSearchParams(); if (version !== undefined) queryParams.append('version', version.toString()); if (label) queryParams.append('label', label); const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const url = `${this.config.baseUrl}/api/public/v2/prompts/${encodeURIComponent(promptName)}?${queryParams}`; const response = await fetch(url, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Get Prompt'); } return await response.json(); } // Dataset management methods async createDataset(params: { name: string; description?: string; metadata?: any; }): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/v2/datasets`, { method: 'POST', headers: { 'Authorization': authHeader, 'Content-Type': 'application/json', }, body: JSON.stringify(params), }); if (!response.ok) { await this.handleApiError(response, 'Create Dataset'); } return await response.json(); } async listDatasets(params: { page?: number; limit?: number; } = {}): Promise<any> { const queryParams = new URLSearchParams(); if (params.page) queryParams.append('page', params.page.toString()); if (params.limit) queryParams.append('limit', params.limit.toString()); const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/v2/datasets?${queryParams}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'List Datasets'); } return await response.json(); } async getDataset(datasetName: string): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/v2/datasets/${encodeURIComponent(datasetName)}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Get Dataset'); } return await response.json(); } // Dataset item management methods async createDatasetItem(params: { datasetName: string; input?: any; expectedOutput?: any; metadata?: any; sourceTraceId?: string; sourceObservationId?: string; status?: 'ACTIVE' | 'ARCHIVED'; }): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/dataset-items`, { method: 'POST', headers: { 'Authorization': authHeader, 'Content-Type': 'application/json', }, body: JSON.stringify(params), }); if (!response.ok) { await this.handleApiError(response, 'Create Dataset Item'); } return await response.json(); } async listDatasetItems(params: { datasetName?: string; sourceTraceId?: string; sourceObservationId?: string; page?: number; limit?: number; } = {}): Promise<any> { const queryParams = new URLSearchParams(); if (params.datasetName) queryParams.append('datasetName', params.datasetName); if (params.sourceTraceId) queryParams.append('sourceTraceId', params.sourceTraceId); if (params.sourceObservationId) queryParams.append('sourceObservationId', params.sourceObservationId); if (params.page) queryParams.append('page', params.page.toString()); if (params.limit) queryParams.append('limit', params.limit.toString()); const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/dataset-items?${queryParams}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'List Dataset Items'); } return await response.json(); } async getDatasetItem(itemId: string): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/dataset-items/${encodeURIComponent(itemId)}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Get Dataset Item'); } return await response.json(); } async deleteDatasetItem(itemId: string): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/dataset-items/${encodeURIComponent(itemId)}`, { method: 'DELETE', headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Delete Dataset Item'); } return await response.json(); } // Comment management methods async createComment(params: { objectType: 'trace' | 'observation' | 'session' | 'prompt'; objectId: string; content: string; authorUserId?: string; }): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const requestBody = { ...params, projectId: this.config.id, // Add projectId to the request }; const response = await fetch(`${this.config.baseUrl}/api/public/comments`, { method: 'POST', headers: { 'Authorization': authHeader, 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); if (!response.ok) { await this.handleApiError(response, 'Create Comment'); } return await response.json(); } async listComments(params: { page?: number; limit?: number; objectType?: 'trace' | 'observation' | 'session' | 'prompt'; objectId?: string; authorUserId?: string; } = {}): Promise<any> { const queryParams = new URLSearchParams(); if (params.page) queryParams.append('page', params.page.toString()); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.objectType) queryParams.append('objectType', params.objectType); if (params.objectId) queryParams.append('objectId', params.objectId); if (params.authorUserId) queryParams.append('authorUserId', params.authorUserId); const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/comments?${queryParams}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'List Comments'); } return await response.json(); } async getComment(commentId: string): Promise<any> { const authHeader = 'Basic ' + Buffer.from( `${this.config.publicKey}:${this.config.secretKey}` ).toString('base64'); const response = await fetch(`${this.config.baseUrl}/api/public/comments/${encodeURIComponent(commentId)}`, { headers: { 'Authorization': authHeader, }, }); if (!response.ok) { await this.handleApiError(response, 'Get Comment'); } return await response.json(); } async shutdown(): Promise<void> { await this.client.shutdownAsync(); } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/therealsachin/langfuse-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server