انتقل إلى المحتوى الرئيسي

الفصل 3: التسجيل باستخدام SLF4J

مقدمة​

System.out.println() جيد للتصحيح، لكن في الإنتاج لا يمكن استخدامه. SLF4J مع Logback يوفر لك نظام تسجيل احترافي.


🎯 أهداف الفصل​

في نهاية هذا الفصل، ستتمكن من:

  1. ✅ استخدام SLF4J في فصولك
  2. ✅ فهم مستويات السجل
  3. ✅ كتابة سجلات فعالة
  4. ✅ استخدام MDC للتتبع
  5. ✅ تجنب الأخطاء الشائعة

1. SLF4J: واجهة التسجيل​

الهيكلية​

الكود الخاص بك
↓
SLF4J (API/واجهة)
↓
Logback (تنفيذ)
↓
وحدة التحكم / الملف / السحابة

الميزة: تغيير التنفيذ دون تعديل الكود.

Spring Boot يتضمنه افتراضيًا​

<!-- متضمن بالفعل في spring-boot-starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- يحتوي على: spring-boot-starter-logging (SLF4J + Logback) -->

2. إنشاء Logger​

الطريقة التقليدية​

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

@Service
public class UserService {

private static final Logger log = LoggerFactory.getLogger(UserService.class);

public User findById(Long id) {
log.info("Finding user with ID: {}", id);
return userRepository.findById(id).orElse(null);
}
}

باستخدام Lombok @Slf4j (موصى به)​

import lombok.extern.slf4j.Slf4j;

@Slf4j // ← يولد الـ logger تلقائيًا
@Service
public class UserService {

public User findById(Long id) {
log.info("Finding user with ID: {}", id);
return userRepository.findById(id).orElse(null);
}
}

3. مستويات السجل​

التسلسل الهرمي (من الأقل إلى الأكثر حرجًا)​

TRACE → DEBUG → INFO → WARN → ERROR
المستوىالاستخدامالمثال
TRACEتفاصيل دقيقة جدًا"Entering method X with params..."
DEBUGمعلومات التصحيح"Calculated value: 42"
INFOأحداث عادية"User logged in", "Order created"
WARNمشاكل محتملة"Cache miss", "Retrying connection"
ERRORأخطاء"Failed to save", "Connection refused"

الاستخدام​

@Slf4j
@Service
public class OrderService {

public Order processOrder(Order order) {
log.trace("Entering processOrder with order: {}", order);

log.debug("Validating order items: {} items", order.getItems().size());

log.info("Processing order {} for user {}", order.getId(), order.getUserId());

if (order.getTotal() > 10000) {
log.warn("Large order detected: {} - Total: {}", order.getId(), order.getTotal());
}

try {
return saveOrder(order);
} catch (Exception e) {
log.error("Failed to process order {}: {}", order.getId(), e.getMessage(), e);
throw e;
}
}
}

4. أفضل ممارسات التسجيل​

✅ استخدام الـ Placeholders​

// ✅ جيد: Placeholders (أداء مثالي)
log.debug("Processing user {} with email {}", userId, email);

// ❌ سيء: التوصيل (يتم تقييمه دائمًا)
log.debug("Processing user " + userId + " with email " + email);

✅ تسجيل الاستثناءات بشكل صحيح​

try {
riskyOperation();
} catch (Exception e) {
// ✅ جيد: الاستثناء كآخر معلمة (يعرض تتبع المكدس)
log.error("Operation failed for user {}: {}", userId, e.getMessage(), e);

// ❌ سيء: لا يوجد تتبع للمكدس
log.error("Operation failed: " + e.getMessage());

// ❌ سيء: e.printStackTrace() بدلاً من التسجيل
e.printStackTrace();
}

✅ سجلات مفيدة وسياقية​

// ✅ جيد: سياق كافٍ
log.info("Order {} created for user {} - Total: {} - Items: {}",
order.getId(), order.getUserId(), order.getTotal(), order.getItems().size());

// ❌ سيء: لا يوجد سياق
log.info("Order created");

// ❌ سيء: كثير الكلام
log.info("Created order object with ID " + order.getId() + " and this order was created by user with ID " + order.getUserId() + "...");

✅ تجنب ازدحام السجل​

// ❌ سيء: تسجيل في حلقة
for (Item item : items) {
log.debug("Processing item: {}", item); // 1000 سجلات إذا كان هناك 1000 عنصر!
}

// ✅ جيد: ملخص السجل
log.debug("Processing {} items", items.size());
for (Item item : items) {
// معالجة...
}
log.debug("Finished processing items");

5. التحقق من المستوى​

للعمليات المكلفة​

// إذا كان الحساب مكلفًا، تحقق من المستوى أولاً
if (log.isDebugEnabled()) {
String expensiveDetails = calculateExpensiveDetails(order);
log.debug("Order details: {}", expensiveDetails);
}

// بديل مع Java 8 Supplier (Logback 1.3+)
log.atDebug()
.addArgument(() -> calculateExpensiveDetails(order))
.log("Order details: {}");

6. MDC (السياق التشخيصي المعين)​

ما هو MDC؟​

MDC يسمح بإضافة السياق إلى جميع سجلات الطلب (userId، requestId، إلخ).

الاستخدام الأساسي​

import org.slf4j.MDC;

@Component
public class LoggingFilter implements Filter {

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

try {
// إضافة السياق
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("userId", getCurrentUserId());

chain.doFilter(request, response);
} finally {
// مهم: تنظيف دائمًا!
MDC.clear();
}
}
}

النتيجة في السجلات​

2024-01-15 10:30:00 [requestId=abc-123] [userId=42] INFO  OrderService - Processing order
2024-01-15 10:30:01 [requestId=abc-123] [userId=42] INFO PaymentService - Payment processed
2024-01-15 10:30:01 [requestId=abc-123] [userId=42] INFO EmailService - Confirmation sent

فلتر كامل مع MDC​

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class MdcLoggingFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

try {
// معرف الطلب
String requestId = request.getHeader("X-Request-ID");
if (requestId == null) {
requestId = UUID.randomUUID().toString().substring(0, 8);
}
MDC.put("requestId", requestId);

// معرف المستخدم (إذا كان مصادقًا)
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
MDC.put("userId", auth.getName());
}

// المسار
MDC.put("path", request.getRequestURI());

// إضافة requestId في الاستجابة
response.setHeader("X-Request-ID", requestId);

filterChain.doFilter(request, response);

} finally {
MDC.clear();
}
}
}

7. التسجيل في الطبقات المختلفة​

Controller​

@Slf4j
@RestController
@RequestMapping("/api/orders")
public class OrderController {

@PostMapping
public ResponseEntity<Order> create(@RequestBody OrderRequest request) {
log.info("Received order creation request: {} items", request.getItems().size());

Order order = orderService.create(request);

log.info("Order {} created successfully", order.getId());
return ResponseEntity.status(HttpStatus.CREATED).body(order);
}
}

Service​

@Slf4j
@Service
public class OrderService {

public Order create(OrderRequest request) {
log.debug("Creating order for user {}", request.getUserId());

// التحقق
validateItems(request.getItems());

// الحساب
BigDecimal total = calculateTotal(request.getItems());
log.debug("Calculated total: {}", total);

// الحفظ
Order order = orderRepository.save(toOrder(request, total));

log.info("Order {} saved with total {}", order.getId(), total);
return order;
}
}

Repository (اختياري)​

// سجلات JPA/Hibernate عادة ما تكون كافية
// لكن للطلبات المخصصة:

@Slf4j
@Repository
public class CustomOrderRepository {

public List<Order> findComplexQuery(Criteria criteria) {
log.debug("Executing complex query with criteria: {}", criteria);

List<Order> results = executeQuery(criteria);

log.debug("Query returned {} results", results.size());
return results;
}
}

8. إعداد سريع​

application.properties​

# المستوى العام
logging.level.root=INFO

# المستوى حسب الحزمة
logging.level.com.myapp=DEBUG
logging.level.com.myapp.repository=TRACE

# عرض الطلبات SQL
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# تعطيل السجلات المزعجة
logging.level.org.apache.catalina=WARN
logging.level.org.springframework.web=INFO

المستويات الموصى بها​

البيئةRootالتطبيقHibernate
DevINFODEBUGDEBUG
TestWARNINFOWARN
ProdWARNINFOWARN

9. ملخص​

إنشاء Logger​

// باستخدام Lombok (موصى به)
@Slf4j
public class MyClass {
void method() {
log.info("Message");
}
}

// بدون Lombok
private static final Logger log = LoggerFactory.getLogger(MyClass.class);

المستويات​

TRACE < DEBUG < INFO < WARN < ERROR

أفضل الممارسات​

// Placeholders
log.info("User {} logged in", userId);

// استثناء مع تتبع المكدس
log.error("Failed: {}", e.getMessage(), e);

// MDC للسياق
MDC.put("requestId", "abc-123");

🎯 الخطوة التالية​

أنت تعرف كيفية التسجيل. الآن، لنقم بتكوين Logback للتحكم في مكان وكيفية كتابة السجلات.

➡️ الفصل 4: تكوين Logback


⬅️ العودة إلى الوحدة 10