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:
Sase Dev
2026-02-16 17:20:49 +00:00
parent 87d8eea298
commit dcbeb83ccc
113 changed files with 2745 additions and 7351 deletions

View File

@@ -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
View 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);
}