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

اختبار: إدارة الاستثناءات والتسجيل

اختبر معرفتك بإدارة الأخطاء والتسجيل.

30 سؤالاً - انقر على كل سؤال لكشف الإجابة.


الأسئلة 1-15: إدارة الاستثناءات​

السؤال 1: ما هو @ControllerAdvice؟

الإجابة: مكون يدير الاستثناءات بشكل عام لجميع المتحكمات:

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(404).body(new ErrorResponse(ex.getMessage()));
}
}

السؤال 2: ما الفرق بين @ControllerAdvice و@RestControllerAdvice؟

الإجابة:

  • @ControllerAdvice: يمكن أن يعيد عرض أو كائنات
  • @RestControllerAdvice: يعادل @ControllerAdvice + @ResponseBody

بالنسبة لـ API REST، استخدم @RestControllerAdvice.


السؤال 3: كيف تنشئ استثناء مخصص؟

الإجابة:

public class ResourceNotFoundException extends RuntimeException {

private final String resourceName;
private final Object resourceId;

public ResourceNotFoundException(String resourceName, Object resourceId) {
super(String.format("%s not found with id: %s", resourceName, resourceId));
this.resourceName = resourceName;
this.resourceId = resourceId;
}

// Getters
}

السؤال 4: كيف تربط استثناء بكود HTTP؟

الإجابة:

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ErrorResponse handleNotFound(ResourceNotFoundException ex) {
return new ErrorResponse(ex.getMessage());
}

// أو مع ResponseEntity
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(new ErrorResponse(ex.getMessage()));
}

السؤال 5: كيف تدير أخطاء التحقق؟

الإجابة:

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidation(
MethodArgumentNotValidException ex) {

Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);

return ResponseEntity.badRequest().body(errors);
}

السؤال 6: ما هو ProblemDetail (RFC 7807)؟

الإجابة: تنسيق قياسي لردود خطأ HTTP:

@ExceptionHandler(ResourceNotFoundException.class)
public ProblemDetail handleNotFound(ResourceNotFoundException ex) {
ProblemDetail problem = ProblemDetail.forStatusAndDetail(
HttpStatus.NOT_FOUND, ex.getMessage());
problem.setTitle("Resource Not Found");
problem.setProperty("resourceId", ex.getResourceId());
return problem;
}

نتيجة JSON:

{
"type": "about:blank",
"title": "Resource Not Found",
"status": 404,
"detail": "User not found with id: 123",
"resourceId": 123
}

السؤال 7: كيف تدير جميع الاستثناءات غير المدارة؟

الإجابة:

@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGeneric(Exception ex) {
log.error("Unexpected error", ex);

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(new ErrorResponse("An unexpected error occurred"));
}

السؤال 8: كيف تضيف معلومات إلى رد الخطأ؟

الإجابة:

public class ErrorResponse {
private LocalDateTime timestamp;
private int status;
private String error;
private String message;
private String path;
private Map<String, Object> details;

// Constructeurs, getters
}

@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusiness(
BusinessException ex, HttpServletRequest request) {

ErrorResponse error = ErrorResponse.builder()
.timestamp(LocalDateTime.now())
.status(HttpStatus.BAD_REQUEST.value())
.error("Business Rule Violation")
.message(ex.getMessage())
.path(request.getRequestURI())
.build();

return ResponseEntity.badRequest().body(error);
}

السؤال 9: كيف تستهدف @ControllerAdvice لمتحكمات معينة؟

الإجابة:

// بواسطة التعليق التوضيحي
@ControllerAdvice(annotations = RestController.class)

// بواسطة الحزمة
@ControllerAdvice(basePackages = "com.example.api")

// بواسطة الفئة
@ControllerAdvice(assignableTypes = {UserController.class, OrderController.class})

السؤال 10: كيف تدير الاستثناءات في الفلاتر؟

الإجابة: الفلاتر تأتي قبل المتحكمات، لذا @ControllerAdvice لا ينطبق:

public class ExceptionHandlerFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
try {
chain.doFilter(request, response);
} catch (JwtException ex) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"Invalid token\"}");
}
}
}

السؤال 11: كيف تدير الأخطاء غير المتزامنة؟

الإجابة:

@ControllerAdvice
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

@Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
log.error("Async error in {}: {}", method.getName(), ex.getMessage());
}
}

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler();
}
}

السؤال 12: كيف تثير خطأ 404 لمورد مفقود؟

الإجابة:

@Service
public class UserService {

public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND, "User not found: " + id));
}
}

// أو مع استثناء مخصص
throw new ResourceNotFoundException("User", id);

السؤال 13: كيف تعطل صفحة الخطأ الافتراضية في Spring Boot؟

الإجابة:

server:
error:
whitelabel:
enabled: false

# أو التخصيص
include-message: always
include-binding-errors: always
include-stacktrace: never
include-exception: false

السؤال 14: كيف تدير الأخطاء 404 بشكل عام؟

الإجابة:

spring:
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
@ExceptionHandler(NoHandlerFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(NoHandlerFoundException ex) {
return ResponseEntity.status(404)
.body(new ErrorResponse("Endpoint not found: " + ex.getRequestURL()));
}

السؤال 15: ما هي أفضل الممارسات للاستثناءات؟

الإجابة:

  1. إنشاء استثناءات مخصصة للأعمال
  2. عدم كشف التفاصيل التقنية للعميل
  3. تسجيل الاستثناءات على الخادم
  4. استخدام أكواد HTTP المناسبة
  5. تنسيق رد متسق
  6. تضمين معرف تتبع للتصحيح

الأسئلة 16-30: التسجيل​

السؤال 16: ما هو إطار العمل المستخدم للتسجيل في Spring Boot افتراضيًا؟

الإجابة: SLF4J كواجهة مع Logback كتطبيق.

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

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

أو مع Lombok: @Slf4j


السؤال 17: ما هي مستويات السجل؟

الإجابة: من الأقل إلى الأكثر تفصيلاً:

  1. ERROR: أخطاء حرجة
  2. WARN: مواقف قد تكون مشكلة
  3. INFO: معلومات عامة
  4. DEBUG: تفاصيل للتصحيح
  5. TRACE: تفاصيل دقيقة جدًا
log.error("Payment failed: {}", ex.getMessage());
log.warn("Slow query: {} ms", duration);
log.info("User {} logged in", username);
log.debug("Processing item: {}", item);
log.trace("Entering method with args: {}", Arrays.toString(args));

السؤال 18: كيف تقوم بتكوين مستويات السجل؟

الإجابة:

logging:
level:
root: INFO
com.example: DEBUG
org.springframework.security: TRACE
org.hibernate.SQL: DEBUG

السؤال 19: لماذا تستخدم النماذج بدلاً من الدمج؟

الإجابة: الأداء - يتم بناء الرسالة فقط إذا كان المستوى نشطًا:

// جيد - تقييم كسول
log.debug("Processing user: {}", user);

// سيء - يتم تقييمه دائمًا
log.debug("Processing user: " + user.toString());

السؤال 20: كيف تقوم بتكوين Logback باستخدام ملف XML؟

الإجابة: إنشاء src/main/resources/logback-spring.xml:

<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>

<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
</configuration>

السؤال 21: كيف تكتب السجلات بصيغة JSON؟

الإجابة: باستخدام Logstash encoder:

<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency>
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

السؤال 22: ما هو MDC (Mapped Diagnostic Context)؟

الإجابة: سياق محلي للخيوط لإضافة معلومات إلى السجلات:

// إضافة في بداية الطلب
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("userId", user.getId().toString());

// في النمط
<pattern>%d [%X{requestId}] [%X{userId}] %msg%n</pattern>

// تنظيف في النهاية
MDC.clear();

السؤال 23: كيف تنفذ فلتر MDC؟

الإجابة:

@Component
public class MdcFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
try {
MDC.put("requestId", UUID.randomUUID().toString());
MDC.put("clientIp", request.getRemoteAddr());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}

السؤال 24: كيف تقوم بتكوين سجلات مختلفة لكل ملف تعريف؟

الإجابة:

<!-- logback-spring.xml -->
<springProfile name="dev">
<root level="DEBUG">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>

<springProfile name="prod">
<root level="INFO">
<appender-ref ref="FILE"/>
<appender-ref ref="JSON"/>
</root>
</springProfile>

السؤال 25: كيف تخفي البيانات الحساسة في السجلات؟

الإجابة:

// لا تسجل البيانات الحساسة
log.info("User login: {}", user.getEmail()); // ليس كلمة المرور!

// إخفاء جزئي
String maskedCard = card.substring(0, 4) + "****" + card.substring(12);
log.info("Payment with card: {}", maskedCard);

// نمط الإخفاء في logback
<pattern>%replace(%msg){'password=[^,]*','password=***'}%n</pattern>

السؤال 26: كيف تسجل الطلبات HTTP؟

الإجابة:

@Component
public class RequestLoggingFilter extends CommonsRequestLoggingFilter {

public RequestLoggingFilter() {
setIncludeQueryString(true);
setIncludePayload(true);
setMaxPayloadLength(10000);
setIncludeHeaders(false);
}
}

أو تفعيل في التكوين:

logging:
level:
org.springframework.web.filter.CommonsRequestLoggingFilter: DEBUG

السؤال 27: كيف تسجل الطلبات SQL؟

الإجابة:

logging:
level:
org.hibernate.SQL: DEBUG
org.hibernate.orm.jdbc.bind: TRACE # المعلمات

# أو مع Spring
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true

السؤال 28: كيف تقوم بأرشفة وضغط السجلات؟

الإجابة:

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>logs/app-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
</appender>

السؤال 29: كيف ترسل السجلات إلى خدمة خارجية؟

الإجابة: باستخدام appenders المتخصصة:

<!-- Logstash -->
<appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5000</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>

<!-- أو غير متزامن للأداء -->
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="LOGSTASH"/>
</appender>

السؤال 30: ما هي أفضل الممارسات للتسجيل؟

الإجابة:

  1. استخدام المستوى المناسب للسجل
  2. تضمين السياق (userId، requestId)
  3. عدم تسجيل البيانات الحساسة
  4. استخدام النماذج
  5. تسجيل الاستثناءات مع stacktrace
  6. سجلات منظمة في الإنتاج (JSON)
  7. التدوير والأرشفة
  8. المركزية للخدمات المصغرة

النتيجة​

النتيجةالمستوى
27-30ممتاز - إدارة الاستثناءات والتسجيل متقنة
21-26جيد - بعض المراجعات
15-20متوسط - أعد قراءة الفصول
أقل من 15بحاجة إلى مراجعة

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