CodeGym /Kurslar /JAVA 25 SELF /Çox axınlı və veb-tətbiqlərdə loqlaşdırma

Çox axınlı və veb-tətbiqlərdə loqlaşdırma

JAVA 25 SELF
Səviyyə, Dərs
Mövcuddur

1. Loqlaşdırmanın axın təhlükəsizliyi

Tək axınlı proqramlarda hər şey sadədir: bir axın loqları yazır, ona heç kim mane olmur. Amma real tətbiqlərdə — veb-servislərdə, mikroxidmətlərdə — eyni vaxtda onlarla və yüzlərlə axın işləyir. Təsəvvür edin, bir neçə nəfər eyni anda bir bloknotun eyni sətirinə qələm ilə yazsaydı — nəticə, yumşaq desək, oxunmaz olardı.

Axın təhlükəsizliyi (thread safety) — bu o zəmanətdir ki, hətta 100500 axın eyni anda loq yazsa belə, mesajlar qarışmır, bir-birinə yapışmır və itmir.

Bu kitabxanalarda necə həyata keçirilib?

Müasir loqlaşdırma kitabxanaları (Log4j 2, Logback, java.util.logging) əvvəlcədən axın təhlükəsiz olacaq şəkildə layihələndirilib. Bu o deməkdir ki:

  • Hər axın logger metodlarını təhlükəsiz çağıra bilər.
  • Kitabxananın daxilində mesajların bir-birinə mane olmaması üçün sinxronizasiya və növbələr istifadə olunur.
  • Hətta bir neçə axın eyni fayla eyni vaxtda yazsa belə, loqlar qarışmır.

VACİB: Logger-in özünü (məsələn, SLF4J və ya Log4j-dan Logger obyekti) istənilən sinifdə static final sahə kimi istifadə etmək olar — bu, axınlarla bağlı problemlərə səbəb olmayacaq.

Nümunə

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MultiThreadedLoggerExample {
    private static final Logger logger = LoggerFactory.getLogger(MultiThreadedLoggerExample.class);

    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                logger.info("Axın {} ismarıc {} yazır", Thread.currentThread().getName(), i);
            }
        };

        Thread t1 = new Thread(task, "Birinci");
        Thread t2 = new Thread(task, "İkinci");
        t1.start();
        t2.start();
    }
}

Loqlarda hər iki axından səliqəli mesajlar görəcəksiniz — qarışıqlıq və üst-üstə düşmə olmadan.

2. Loqlaşdırma konteksti: MDC (Mapped Diagnostic Context)

Təsəvvür edin: tətbiqiniz eyni anda yüzlərlə sorğunu emal edir, hər biri — öz axınında. Loqlarda mesajlar görünür, amma hansının hansı sorğuya aid olduğu aydın deyil. Təkcə “nə baş verdi”ni deyil, həm də bunun kimlə və hansı sorğuda baş verdiyini görmək istəyirsiniz.

MDC (Mapped Diagnostic Context) — cari axınla bağlı loqlara əlavə məlumatı “bərkitməyə” imkan verən xüsusi mexanizmdir. Axının yazdığı bütün mesajlara bu əlavə məlumatlar avtomatik əlavə olunur.

Nümunə: sorğu identifikatorunu loqlaşdıraq

Veb-tətbiqdə hər sorğuya unikal ID (məsələn, UUID) təyin etmək olar. MDC vasitəsilə bu ID sorğuya xidmət göstərən axının yazdığı bütün loqlara avtomatik əlavə olunacaq.

Kodda necə görünür (SLF4J + Logback):

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.util.UUID;

public class MdcExample {
    private static final Logger logger = LoggerFactory.getLogger(MdcExample.class);

    public static void main(String[] args) {
        Runnable task = () -> {
            // Unikal sorğu identifikatoru yaradırıq
            String requestId = UUID.randomUUID().toString();
            MDC.put("requestId", requestId); // MDC-yə əlavə edirik

            logger.info("Sorğunu emal edirik");
            doSomeWork();
            logger.info("Emalı tamamladıq");

            MDC.clear(); // mütləq sonunda təmizləyin!
        };

        Thread t1 = new Thread(task, "Axın-1");
        Thread t2 = new Thread(task, "Axın-2");
        t1.start();
        t2.start();
    }

    static void doSomeWork() {
        logger.debug("İşi yerinə yetiririk...");
    }
}

Loq formatının tənzimlənməsi (məsələn, logback.xml):

<encoder>
    <pattern>%d{HH:mm:ss} [%thread] %-5level %logger{36} [requestId=%X{requestId}] - %msg%n</pattern>
</encoder>

Nəticə:

12:01:23 [Axın-1] INFO  MdcExample [requestId=ad8d...f3] - Sorğunu emal edirik
12:01:23 [Axın-1] DEBUG MdcExample [requestId=ad8d...f3] - İşi yerinə yetiririk...
12:01:23 [Axın-1] INFO  MdcExample [requestId=ad8d...f3] - Emalı tamamladıq

Vacib!

  • MDC yalnız bir axın çərçivəsində işləyir. İşi başqa axına ötürürsünüzsə (məsələn, axın hovuzu vasitəsilə), MDC dəyərlərini əl ilə ötürməlisiniz (və ya bunu avtomatik edən xüsusi kitabxanalardan istifadə etməlisiniz).
  • MDC-ni təmizləməyi unutmayın! Təmizləməsəniz, məlumatlar eyni axında növbəti sorğuya “sızıb” çata bilər (məsələn, veb-serverin axın hovuzunda). MDC.clear()-i mütləq finally-blokunda çağırın.

3. Veb-tətbiqlərdə loqlaşdırma

Veb-tətbiq sadəcə işə düşüb işləyən proqram deyil. Bu, həqiqi konveyerdir: sorğular gəlir, emal olunur, cavablar göndərilir. Və bütün bunlar — eyni vaxtda, yüzlərlə. Burada loqlaşdırma lüks deyil, zərurətdir!

Veb-tətbiqlərdə nəyi loqlaşdırmalı?

  • HTTP-sorğular və cavablar: metod, URL, parametrlər, cavab statusu, emal müddəti.
  • Xətalar və istisnalar: bütün gözlənilməz nasazlıqlar, stack trace.
  • Biznes hadisələri: qeydiyyat, giriş, sifarişin rəsmiləşdirilməsi, ödəniş və s.
  • Texniki detallar: verilənlər bazası və xarici servislərlə qarşılıqlı əlaqə, əməliyyatların icra vaxtı.

Əsas qayda: elə loqlaşdırın ki, bir aydan sonra, gecə saat 3-də nəsə sıradan çıxanda nə baş verdiyini anlaya biləsiniz.

Nümunə: HTTP-sorğunun loqlaşdırılması (Spring Boot)

Ən sadə yol — hər daxil olan sorğunu loqlaşdıran filtr və ya aspektdən istifadə etməkdir.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.UUID;

@Component
public class RequestLoggingFilter implements Filter {
    private static final Logger logger = LoggerFactory.getLogger(RequestLoggingFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        String requestId = UUID.randomUUID().toString();
        MDC.put("requestId", requestId);

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        logger.info("Sorğu: {} {}", httpRequest.getMethod(), httpRequest.getRequestURI());

        long start = System.currentTimeMillis();
        try {
            chain.doFilter(request, response); // zəncir üzrə (kontrollerə) davam
        } finally {
            long duration = System.currentTimeMillis() - start;
            logger.info("Cavab göndərildi, emal vaxtı: {} ms", duration);
            MDC.clear();
        }
    }
}

Xətaların və istisnaların loqlaşdırılması

Veb-freymvörklərdə (məsələn, Spring) bütün gözlənilməz nasazlıqları səliqəli loqlaşdırmaq üçün xüsusi xəta emalçılarından (@ExceptionHandler) istifadə olunur.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    public String handleException(Exception ex) {
        logger.error("Xəta baş verdi: ", ex); // tam stack trace ilə log edirik!
        return "error"; // xəta səhifəsini qaytarırıq
    }
}

Veb-freymvörklərlə inteqrasiya

Demək olar ki, bütün müasir veb-freymvörklər (Spring, Jakarta EE, Micronaut və s.) logger-lərlə hazır şəkildə inteqrasiya olunur. Adətən layihəyə SLF4J/Logback asılılığını əlavə etmək kifayətdir — və standart mesajlar (tətbiqin startı, sorğuların emalı, xətalar) avtomatik loqlaşdırılacaq.

4. Praktika: Çox axınlı tapşırıqda loqlaşdırma nümunəsi

Gəlin tədris tətbiqimizə (məsələn, sifarişlərin emalı xidməti) çox axınlı emal əlavə edək və loqlaşdırmanın başımızı itirməməyə necə kömək etdiyinə baxaq.

Nümunə: bir neçə axında MDC ilə sifarişlərin emalı

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class OrderProcessingApp {
    private static final Logger logger = LoggerFactory.getLogger(OrderProcessingApp.class);

    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 1; i <= 5; i++) {
            final int orderId = i;
            executor.submit(() -> {
                String requestId = UUID.randomUUID().toString();
                MDC.put("requestId", requestId);

                try {
                    logger.info("Sifariş {} emalına başlayırıq", orderId);
                    processOrder(orderId);
                    logger.info("Sifariş {} uğurla emal olundu", orderId);
                } catch (Exception ex) {
                    logger.error("Sifarişin emalı zamanı xəta: " + orderId, ex);
                } finally {
                    MDC.clear();
                }
            });
        }
        executor.shutdown();
    }

    static void processOrder(int orderId) throws InterruptedException {
        if (orderId % 2 == 0) {
            throw new RuntimeException("Cüt nömrəli sifariş üçün xəta simulyasiyası");
        }
        Thread.sleep(500); // işin imitasiya olunması
    }
}

Nə baş verir:

  • Hər sifariş ayrı axında emal olunur.
  • Hər axın üçün unikal requestId yaradılır (MDC vasitəsilə).
  • Bir sifarişlə bağlı bütün loqları bu identifikator üzrə tapmaq mümkündür.
  • Xətalar tam stack ilə loqlaşdırılır.

Loq formatı requestId-i göstərmək üçün tənzimlənib.

5. Vacib nüanslar və xüsusiyyətlər

  • Axınlar, hovuzlar və MDC. Axın hovuzları ilə işləyirsinizsə (yəqin ki, işləyirsiniz), yadda saxlayın: hovuzdakı axınlar təkrar istifadə olunur! MDC-ni təmizləməyi unutsaq, bir sorğunun məlumatları digərinin loqlarına düşə bilər. İşin sonunda həmişə MDC.clear() çağırın.
  • MDC və asinxron tapşırıqlar. Asinxron veb-freymvörklərdə (məsələn, Spring WebFlux) MDC həmişə “hazırdan” işləmir, çünki sorğunun emalı axınlar arasında dəyişə bilər. Belə hallarda xüsusi genişlənmələr və ya adapterlər mövcuddur.
  • Mikroxidmətlərdə loqlaşdırma. Mikroxidmət arxitekturasında təkcə lokal sorğu identifikatoru deyil, servislararası ötürülən qlobal identifikator (traceId) da loqlaşdırılır. Bu, sorğunun bütün sistem üzrə yolunu izləməyə imkan verir (distributed tracing). Bunun üçün tez-tez Zipkin, Jaeger, OpenTelemetry kimi sistemlərdən istifadə olunur.

6. Demonstrasiya: System.out.println və loqlaşdırma arasındakı fərq

System.out.println — sadəcə sətri konsola çap edir. Çox axınlı mühitdə:

  • Mesajlar bir-birinə qarışa bilər.
  • Zaman, axın, səviyyə, kontekst haqqında məlumat yoxdur.
  • Çıxışı fayla yönləndirmək, formatlamaq, səviyyəyə görə filtr etmək olmur.

Logger — strukturlu mesajlar yazır, axınları və səviyyələri nəzərə alır, formatı idarə edir, müxtəlif çıxış yerlərini (fayl, konsol, şəbəkə) dəstəkləyir.

Müqayisə nümunəsi

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PrintVsLogger {
    private static final Logger logger = LoggerFactory.getLogger(PrintVsLogger.class);

    public static void main(String[] args) {
        Runnable task = () -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("System.out: " + Thread.currentThread().getName() + " addım " + i);
                logger.info("Logger: addım {}", i);
            }
        };
        new Thread(task, "T1").start();
        new Thread(task, "T2").start();
    }
}

Nəticə:

  • System.out — mesajlar qarışa bilər, zaman və səviyyə olmadan.
  • Logger — hər mesajda zaman, axın, səviyyə olur; filtrasiya etmək və lazımi məlumatı tez tapmaq mümkündür.

7. Çox axınlı və veb-tətbiqlərdə loqlaşdırmada tipik səhvlər

Səhv №1: System.out.println-dən logger əvəzinə istifadə. Çox axınlı mühitdə bu, konsolda “qarışıqlığa”, mesajları filtrləmək imkanının olmamasına və kontekst haqqında məlumatın itirilməsinə gətirib çıxarır.

Səhv №2: MDC-nin gözardı edilməsi və ya səhv istifadəsi. Sorğu/istifadəçi identifikatorunu ötürmək üçün MDC istifadə olunmasa, loqlar mənasızlaşır — xətanın hansı sorğuya aid olduğunu anlamaq mümkün olmur. MDC-ni təmizləməyi unutsaq, məlumatlar başqa sorğuya “sızıb” gedə bilər.

Səhv №3: Logger-in lokal dəyişən kimi yaradılması. Daha yaxşısı, private static final Logger istifadə etməkdir — beləliklə logger sinif üçün bir dəfə yaradılır, yaddaş israf olunmur və səhv riski azalır.

Səhv №4: Həssas məlumatların loqlaşdırılması. Parollar, bank kartları, şəxsi məlumatlar loqlara düşməməlidir — bu, təhlükəsizlik qaydalarının pozulmasıdır!

Səhv №5: Yalnız "INFO" və ya yalnız "ERROR" səviyyəsindən istifadə. Uyğun səviyyələrdən istifadə edin: sazlama üçün DEBUG, biznes hadisələri üçün INFO, xətalar üçün ERROR. Hər şeyi bir səviyyə ilə yazmayın — əks halda loqlar mənasını itirir.

Səhv №6: İstisnaların stack trace-lərinin loqlaşdırılmaması. Yalnız logger.error("Xəta: " + ex.getMessage()) yazılsa, xətanın səbəbi barədə məlumat itir. Həmişə tam istisnanı loqlaşdırın: logger.error("Xəta", ex).

Səhv №7: Axın təhlükəsiz olmayan özəl (samopisnı) loqgerlər. Kimsə sinxronizasiya olmadan “öz loqgerini” yazmağa qərar verərsə — çox axınlı mühitdə bu, demək olar ki, loqların itirilməsi və ya korlanması ilə nəticələnəcək.

1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Oyun serveri
Oyun serveri
1
Tapşırıq
JAVA 25 SELF, səviyyə, dərs
Bağlanıb
Dəstək xidməti
Dəstək xidməti
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION