From 8ced8ae66900b3c9faaf93518d61b74d6bb4463c Mon Sep 17 00:00:00 2001 From: Zuev Date: Thu, 19 Mar 2026 03:55:22 +0300 Subject: [PATCH] feat: Integrate OpenTelemetry for distributed tracing in both frontend and backend applications. --- backend/Dockerfile | 4 ++- backend/pom.xml | 7 +++++ .../app/config/tenant/TenantInterceptor.java | 7 +++++ .../db/migration/V2__departmentCreate.sql | 27 ++++++++++++++++--- frontend/script.js | 26 ++++++++++++++++++ 5 files changed, 67 insertions(+), 4 deletions(-) diff --git a/backend/Dockerfile b/backend/Dockerfile index e4b3343..23e1969 100755 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -4,6 +4,7 @@ COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests -B +RUN curl -L -o opentelemetry-javaagent.jar https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar FROM eclipse-temurin:17-jre-alpine @@ -13,5 +14,6 @@ USER spring:spring WORKDIR /app COPY --from=build /app/target/app.jar app.jar +COPY --from=build /app/opentelemetry-javaagent.jar opentelemetry-javaagent.jar EXPOSE 8080 -ENTRYPOINT ["java", "-jar", "app.jar"] +ENTRYPOINT ["java", "-javaagent:opentelemetry-javaagent.jar", "-jar", "app.jar"] diff --git a/backend/pom.xml b/backend/pom.xml index 2a160d5..54b385f 100755 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -56,6 +56,13 @@ h2 runtime + + + + io.opentelemetry + opentelemetry-api + 1.49.0 + diff --git a/backend/src/main/java/com/magistr/app/config/tenant/TenantInterceptor.java b/backend/src/main/java/com/magistr/app/config/tenant/TenantInterceptor.java index 924222b..e85c961 100755 --- a/backend/src/main/java/com/magistr/app/config/tenant/TenantInterceptor.java +++ b/backend/src/main/java/com/magistr/app/config/tenant/TenantInterceptor.java @@ -9,6 +9,8 @@ import org.springframework.web.servlet.HandlerInterceptor; import java.io.IOException; import java.util.Map; +import org.slf4j.MDC; +import io.opentelemetry.api.trace.Span; /** * Interceptor: извлекает поддомен из Host header и кладёт в TenantContext. @@ -48,6 +50,8 @@ public class TenantInterceptor implements HandlerInterceptor { // (нужно чтобы админ мог добавить тенант даже если его домен не настроен) if (path.startsWith("/api/database")) { TenantContext.setCurrentTenant(tenant); + MDC.put("tenant.id", tenant); + Span.current().setAttribute("tenant.id", tenant); log.debug("Database API request, tenant '{}' (no strict check)", tenant); return true; } @@ -66,6 +70,8 @@ public class TenantInterceptor implements HandlerInterceptor { } TenantContext.setCurrentTenant(tenant); + MDC.put("tenant.id", tenant); + Span.current().setAttribute("tenant.id", tenant); log.debug("Resolved tenant '{}' from Host '{}'", tenant, host); return true; } @@ -73,6 +79,7 @@ public class TenantInterceptor implements HandlerInterceptor { @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { TenantContext.clear(); + MDC.remove("tenant.id"); } private String resolveTenant(String host) { diff --git a/backend/src/main/resources/db/migration/V2__departmentCreate.sql b/backend/src/main/resources/db/migration/V2__departmentCreate.sql index 2dd3aba..51a41fb 100644 --- a/backend/src/main/resources/db/migration/V2__departmentCreate.sql +++ b/backend/src/main/resources/db/migration/V2__departmentCreate.sql @@ -127,8 +127,7 @@ COMMENT ON TABLE equipments IS 'Оборудование'; COMMENT ON TABLE classrooms IS 'Аудитории'; COMMENT ON TABLE classroom_equipments IS 'Привязка оборудования к аудиториям'; COMMENT ON TABLE teacher_subjects IS 'Привязка преподавателей к дисциплинам'; -COMMENT ON TABLE equipments IS 'Оборудование'; -COMMENT ON TABLE equipments IS 'Оборудование'; +COMMENT ON TABLE teacher_lesson_types IS 'Типы занятий преподавателя'; COMMENT ON COLUMN users.id IS 'ID пользователя'; @@ -199,4 +198,26 @@ COMMENT ON COLUMN lessons.type_lesson IS 'Тип урока'; COMMENT ON COLUMN lessons.classroom_id IS 'ID аудитории, в которой проходит урок'; COMMENT ON COLUMN lessons.day IS 'День недели, в который проходит урок'; COMMENT ON COLUMN lessons.week IS 'Номер недели, в которой проходит урок'; -COMMENT ON COLUMN lessons.time IS 'Время урока'; \ No newline at end of file +COMMENT ON COLUMN lessons.time IS 'Время урока'; + +COMMENT ON COLUMN departments.id IS 'ID кафедры'; +COMMENT ON COLUMN departments.name IS 'Название кафедры'; +COMMENT ON COLUMN departments.code IS 'Код кафедры'; + +COMMENT ON COLUMN specialties.id IS 'ID специальности'; +COMMENT ON COLUMN specialties.name IS 'Название специальности'; +COMMENT ON COLUMN specialties.specialty_code IS 'Код специальности'; + +COMMENT ON COLUMN teacher_lesson_types.user_id IS 'ID преподавателя'; +COMMENT ON COLUMN teacher_lesson_types.subject_id IS 'ID предмета'; +COMMENT ON COLUMN teacher_lesson_types.lesson_type_id IS 'ID типа занятия'; + +COMMENT ON COLUMN schedule_data.id IS 'ID записи данных расписания'; + +COMMENT ON COLUMN subjects.department_id IS 'ID кафедры'; + +COMMENT ON COLUMN student_groups.department_id IS 'ID кафедры'; + +COMMENT ON COLUMN users.full_name IS 'ФИО пользователя'; +COMMENT ON COLUMN users.job_title IS 'Должность пользователя'; +COMMENT ON COLUMN users.department_id IS 'ID кафедры'; \ No newline at end of file diff --git a/frontend/script.js b/frontend/script.js index 6603b09..d427f1e 100755 --- a/frontend/script.js +++ b/frontend/script.js @@ -1,6 +1,32 @@ (() => { 'use strict'; + // --- OpenTelemetry Frontend Instrumentation --- + // Загружаем OTel Web SDK динамически через esm.sh, чтобы не ломать старый Vanilla JS (без type="module") + import('https://esm.sh/@opentelemetry/sdk-trace-web').then(async ({ WebTracerProvider, BatchSpanProcessor }) => { + const { OTLPTraceExporter } = await import('https://esm.sh/@opentelemetry/exporter-trace-otlp-http'); + const { getWebAutoInstrumentations } = await import('https://esm.sh/@opentelemetry/auto-instrumentations-web'); + const { registerInstrumentations } = await import('https://esm.sh/@opentelemetry/instrumentation'); + const { Resource } = await import('https://esm.sh/@opentelemetry/resources'); + + const exporter = new OTLPTraceExporter({ + url: window.location.origin + '/otel/v1/traces' // Трафик пойдет через ваш Caddy Proxy + }); + + const provider = new WebTracerProvider({ + resource: new Resource({ 'service.name': 'magistr-frontend' }), + }); + + provider.addSpanProcessor(new BatchSpanProcessor(exporter)); + provider.register(); + + registerInstrumentations({ + instrumentations: [getWebAutoInstrumentations()] + }); + console.log("SigNoz (OpenTelemetry) инициализирован во фронтенде."); + }).catch(e => console.error("Ошибка загрузки OTel:", e)); + // ---------------------------------------------- + const form = document.getElementById('login-form'); const usernameInput = document.getElementById('username'); const passwordInput = document.getElementById('password');