feat: add OpenTelemetry observability, Faro frontend monitoring, remove legacy Next.js app
- Add OpenTelemetry SDK with tracing, metrics, and OTLP export for API and worker - Integrate Grafana Faro for frontend real-user monitoring - Instrument health checks, database, Bull queues, and HTTP exception filter - Add Grafana dashboard JSON for service overview - Remove deprecated apps/web-nj (Next.js) — fully replaced by Vite+React frontend - Update nginx config with OTEL collector proxy - Minor UI fixes in schema viewer, category components, and subscription flow Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { getFaro } from "./faro";
|
||||
|
||||
const API_URL = "/api";
|
||||
|
||||
type RequestOptions = {
|
||||
@@ -29,11 +31,15 @@ class ApiClient {
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
throw new ApiError(
|
||||
const error = new ApiError(
|
||||
data?.error?.message || "İstek başarısız",
|
||||
data?.error?.code || "UNKNOWN",
|
||||
res.status,
|
||||
);
|
||||
getFaro()?.api.pushError(error, {
|
||||
context: { method, path, statusCode: String(res.status) },
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data.data !== undefined ? data.data : data;
|
||||
@@ -65,11 +71,15 @@ class ApiClient {
|
||||
const data = await res.json();
|
||||
|
||||
if (!res.ok) {
|
||||
throw new ApiError(
|
||||
const error = new ApiError(
|
||||
data?.error?.message || "Yükleme başarısız",
|
||||
data?.error?.code || "UNKNOWN",
|
||||
res.status,
|
||||
);
|
||||
getFaro()?.api.pushError(error, {
|
||||
context: { method: "POST", path, statusCode: String(res.status) },
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data.data !== undefined ? data.data : data;
|
||||
|
||||
59
apps/web/src/lib/faro.ts
Normal file
59
apps/web/src/lib/faro.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { Faro } from "@grafana/faro-web-sdk";
|
||||
|
||||
let faro: Faro | null = null;
|
||||
|
||||
export async function initFaro() {
|
||||
if (import.meta.env.VITE_FARO_ENABLED !== "true") return;
|
||||
|
||||
const collectorUrl = import.meta.env.VITE_FARO_COLLECTOR_URL;
|
||||
if (!collectorUrl) {
|
||||
console.warn("[faro] VITE_FARO_COLLECTOR_URL is required when Faro is enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { initializeFaro, getWebInstrumentations } = await import("@grafana/faro-web-sdk");
|
||||
const { TracingInstrumentation } = await import("@grafana/faro-web-tracing");
|
||||
|
||||
faro = initializeFaro({
|
||||
url: collectorUrl,
|
||||
app: {
|
||||
name: "saseweb",
|
||||
version: "2.0.0",
|
||||
environment: import.meta.env.MODE,
|
||||
},
|
||||
instrumentations: [
|
||||
...getWebInstrumentations({ captureConsole: false }),
|
||||
new TracingInstrumentation({
|
||||
instrumentationOptions: {
|
||||
propagateTraceHeaderCorsUrls: [/\/api\//],
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
console.log("[faro] Frontend observability initialized");
|
||||
} catch (err) {
|
||||
console.warn("[faro] Initialization failed:", (err as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
export function getFaro() {
|
||||
return faro;
|
||||
}
|
||||
|
||||
/** Start a programmatic user action (auto-completes 100ms after last linked event) */
|
||||
export function startAction(
|
||||
name: string,
|
||||
attributes?: Record<string, string>,
|
||||
) {
|
||||
faro?.api.startUserAction(name, attributes);
|
||||
}
|
||||
|
||||
/** Push a Faro event (for non-action tracking like page-level events) */
|
||||
export function pushEvent(
|
||||
name: string,
|
||||
attributes?: Record<string, string>,
|
||||
) {
|
||||
faro?.api.pushEvent(name, attributes);
|
||||
}
|
||||
Reference in New Issue
Block a user