Spring Boot - İstisnalar dışında tüm istek ve yanıtları tek bir yerde nasıl kaydedebilirim?


216

Bahar önyükleme ile dinlenme api üzerinde çalışıyorum. Giriş params ile tüm istekleri (örn. GET, POST, vb.), İstek yolu, sorgu dizesi, bu isteğin karşılık gelen sınıf yöntemi, aynı zamanda bu eylemin yanı sıra, başarı ve hataların yanıtı günlüğü gerekiyor.

Örnek olarak:

başarılı istek:

http://example.com/api/users/1

Günlük aşağıdaki gibi görünmelidir:

{
   HttpStatus: 200,
   path: "api/users/1",
   method: "GET",
   clientIp: "0.0.0.0",
   accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
   method: "UsersController.getUser",
   arguments: {
     id: 1 
   },
   response: {
      user: {
        id: 1,
        username: "user123",
        email: "user123@example.com"   
      }
   },
   exceptions: []       
}

Veya hatayla isteyin:

http://example.com/api/users/9999

Günlük şöyle olmalıdır:

    {
       HttpStatus: 404,
       errorCode: 101,                 
       path: "api/users/9999",
       method: "GET",
       clientIp: "0.0.0.0",
       accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
       method: "UsersController.getUser",
       arguments: {
         id: 9999 
       },
       returns: {            
       },
       exceptions: [
         {
           exception: "UserNotFoundException",
           message: "User with id 9999 not found",
           exceptionId: "adhaskldjaso98d7324kjh989",
           stacktrace: ...................    
       ]       
    }

İstek / Yanıt'ın, hem başarılı hem de hata durumunda, bu varlıkla ilgili özel bilgileri içeren tek bir varlık olmasını istiyorum.

Bunu başarmak için ilkbaharda en iyi uygulama nedir, filtrelerle olabilir mi? evet ise, somut bir örnek verebilir misiniz?

(@ControllerAdvice ve @ExceptionHandler ile oynadım, ancak bahsettiğim gibi, tüm başarı ve hata isteklerini tek bir yerde (ve tek günlük) işlemem gerekiyor).


Muhtemelen bir günlüğe kaydetme ServletFilter (örn. Stackoverflow.com/a/2171633/995891 ) aracılığıyla, HandlerInterceptorancak bu, yanıtta belirtildiği gibi yanıtın günlüğe kaydedilmesiyle işe yaramayabilir: concretepage.com/spring/spring-mvc/… - HandlerInterceptor erişimi var (yöntem: "UsersController.getUser"). Bir sunucu uygulaması filtresinde bilinmemektedir.
15:24

1
yine de, uygulama katmanına bir filtre veya herhangi bir çözüm ekleseniz bile, tüm isteği günlüğe kaydetmezsiniz, fe 500 500 Sunucu Hatası günlüğe kaydedilmez, çünkü Uygulama katmanında işlenmeyen bir istisna atılır, istisna yutulduktan sonra varsayılan katıştırılmış tomcat'ler hata sayfasında görüntülenir ve tabii ki günlüğü korumaz. Ayrıca user1817243 yanıtını kontrol ederseniz, herhangi bir istisna durumunda, isteği tekrar günlüğe kaydetmez, ancak istisnayı günlüğe kaydeder (!!).
AntJavaDev

Bu günlük biçimi yazdığınız her karakterle tutarlı olmalı mı? Sizin durumunuzda bir JSON çevirisi en uygun gibi görünüyor: LogClass{ getRequestAndSaveIt()} Gson.toJson(LogClass)as pseudocode
Vale

1
Gelecekteki okuyucular cevabımdan yararlanabilir (bu açıklamada takip edilecek URL) Temel olarak, bu soru hakkında farklı gönderileri bir araya getirebildim. LÜTFEN elle denemeden önce aktüatör yanıtını (aşağıdaki cevaplarda) dikkate alın. Ama gönderdiğim cevap "400, 404, 500" (herhangi bir / tüm) günlüğe kaydedilebiliyor, ancak sipariş önceliğini en düşük önceliğe ayarlıyor (veya koda bakarsanız "8" içinde). stackoverflow.com/questions/10210645/…
granadaCoder

Buradan giriş yaparken bahar dokümanlarını takip ettim: docs.spring.io/spring-boot/docs/current/reference/html/…
T04435

Yanıtlar:


148

Herhangi bir Durdurucu, Filtre, Bileşen, Unsur vb. Yazmayın, bu çok yaygın bir sorundur ve birçok kez çözülmüştür.

Spring Boot'da Aktüatör adı verilen ve HTTP isteği günlüğe kaydedilmesini sağlayan bir modül bulunur . /trace(SB1.x) veya /actuator/httptrace(SB2.0 +) ile eşlenen ve size son 100 HTTP isteğini gösteren bir uç nokta var . Her isteği günlüğe kaydedecek şekilde özelleştirebilir veya bir DB'ye yazabilirsiniz.

İstediğiniz uç noktaları elde etmek için, yay önyükleme marş aktüatörü bağımlılığına ve ayrıca aradığınız uç noktalarını "beyaz listeye eklemek" ve muhtemelen bunun için güvenliği ayarlamanız veya devre dışı bırakmanız gerekir.

Ayrıca, bu uygulama nerede çalışacak? PaaS kullanıyor musunuz? Örneğin, barındırma sağlayıcıları, Heroku, hizmetlerinin bir parçası olarak istek günlüğü sağlar ve o zaman herhangi bir kodlama yapmanız gerekmez .


4
daha fazla ayrıntı? Github.com/spring-projects/spring-boot/tree/master/… ' ı buldum , ancak bunun ötesinde pek bir şey yok.
Tom Howard

16
Bu hata ayıklama için kullanılamaz: kimliği doğrulanmamış istekler (örneğin yay güvenliği ile) günlüğe kaydedilmez.
bekce

11
Aslında Aktüatörün http günlüğü tutma için belirli bir bileşeni yoktur. / trace - yalnızca son N isteği gösterir.
Vladimir Filipchenko

18
@ike_love, aktüatörü dosyaya kaydetme isteğini (POST gövdesi) nasıl saklayacak?

11
İzleme sizin için istek ve yanıt gövdesini günlüğe kaydetmez .... her şey (başlık vb.) Ama bunlar.
Lekkie

94

Spring zaten bu işi yapan bir filtre sunuyor. Aşağıdaki fasulyeyi yapılandırmanıza ekleyin

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
    CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
    loggingFilter.setIncludeClientInfo(true);
    loggingFilter.setIncludeQueryString(true);
    loggingFilter.setIncludePayload(true);
    loggingFilter.setMaxPayloadLength(64000);
    return loggingFilter;
}

Bir kayıt düzeyini değiştirmek unutmayın org.springframework.web.filter.CommonsRequestLoggingFilteriçin DEBUG.


75
Yanıtları değil , yalnızca istekleri günlüğe kaydettiğini unutmayın .
Wim Deblauwe

1
Sadece istekler var. CommonsRequestLoggingFilter kullanarak yanıt gövdeleri nasıl günlüğe kaydedilir?
user2602807

3
Ayrıca bu Exception
BhendiGawaar

Bir istek günlüğü filtresi olduğu için bu bekleniyor. Burada daha fazlası: docs.spring.io/spring/docs/current/javadoc-api/org/…
Yogesh Badke

4
Büyük JSON gövdesine sahipseniz, tüm istek gövdesini günlüğe kaydetmek için yük uzunluğunu büyük bir sayıya ayarlayın. loggingFilter.setMaxPayloadLength (100000);
Venkatesh Nannan

58

javax.servlet.FilterYürütülen java yöntemini günlüğe kaydetme gereksinimi yoksa kullanabilirsiniz .

Ancak bu gereksinimle, handlerMappingiçinde depolanan bilgilere erişmeniz gerekir DispatcherServlet. Bununla birlikte DispatcherServlet, istek / yanıt çiftinin günlük kaydını gerçekleştirmek için geçersiz kılabilirsiniz .

Aşağıda, ihtiyaçlarınıza göre daha da geliştirilebilecek ve benimsenebilecek bir fikir örneği verilmiştir.

public class LoggableDispatcherServlet extends DispatcherServlet {

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (!(request instanceof ContentCachingRequestWrapper)) {
            request = new ContentCachingRequestWrapper(request);
        }
        if (!(response instanceof ContentCachingResponseWrapper)) {
            response = new ContentCachingResponseWrapper(response);
        }
        HandlerExecutionChain handler = getHandler(request);

        try {
            super.doDispatch(request, response);
        } finally {
            log(request, response, handler);
            updateResponse(response);
        }
    }

    private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, HandlerExecutionChain handler) {
        LogMessage log = new LogMessage();
        log.setHttpStatus(responseToCache.getStatus());
        log.setHttpMethod(requestToCache.getMethod());
        log.setPath(requestToCache.getRequestURI());
        log.setClientIp(requestToCache.getRemoteAddr());
        log.setJavaMethod(handler.toString());
        log.setResponse(getResponsePayload(responseToCache));
        logger.info(log);
    }

    private String getResponsePayload(HttpServletResponse response) {
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {

            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                int length = Math.min(buf.length, 5120);
                try {
                    return new String(buf, 0, length, wrapper.getCharacterEncoding());
                }
                catch (UnsupportedEncodingException ex) {
                    // NOOP
                }
            }
        }
        return "[unknown]";
    }

    private void updateResponse(HttpServletResponse response) throws IOException {
        ContentCachingResponseWrapper responseWrapper =
            WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        responseWrapper.copyBodyToResponse();
    }

}

HandlerExecutionChain - istek işleyici ile ilgili bilgileri içerir.

Daha sonra bu dağıtıcıyı aşağıdaki gibi kaydedebilirsiniz:

    @Bean
    public ServletRegistrationBean dispatcherRegistration() {
        return new ServletRegistrationBean(dispatcherServlet());
    }

    @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        return new LoggableDispatcherServlet();
    }

Ve işte günlüklerin örneği:

http http://localhost:8090/settings/test
i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=500, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475814077,"status":500,"error":"Internal Server Error","exception":"java.lang.RuntimeException","message":"org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.RuntimeException","path":"/settings/test"}'}

http http://localhost:8090/settings/params
i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=200, path='/settings/httpParams', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public x.y.z.DTO x.y.z.Controller.params()] and 3 interceptors', arguments=null, response='{}'}

http http://localhost:8090/123
i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=404, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475840592,"status":404,"error":"Not Found","message":"Not Found","path":"/123"}'}

GÜNCELLEME

Hata olması durumunda Spring otomatik hata yönetimi yapar. Bu nedenle, BasicErrorController#erroristek işleyici olarak gösterilir. Orijinal istek işleyiciyi korumak istiyorsanız , orijinal işleyiciyi önbelleğe almak için daha spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971önce #processDispatchResultçağrıldığında bu davranışı geçersiz kılabilirsiniz .


2
yanıt bir akış olduğunda ve akış aramayı desteklemediğinde ne olur? Yukarıdakiler hala çalışacak mı?
Tom Howard

Çağrılan yöntemi umursamıyorum, sadece alınan ve gönderilen veriler. Filtre beni doğru yöne yönlendiriyor gibi görünüyor ve @ ike_love'un yanıtı beni github.com/spring-projects/spring-boot/blob/master/… adresine
Tom Howard

@ AFOMA, baharda kutunun dışında "yanıt kaydı" yok. Bu nedenle, WebRequestTraceFilter veya AbstractRequestLoggingFilter yanıt günlüğü mantığı ekleyerek genişletebilirsiniz.
hahn

Sadece iyi çalışıyor!
Pavel Vlasov

@hahn Bunun için neden Dispatcher sunucu uygulamasını kullandınız? doFilter'da filtre ile aynı giriş eklenemez mi?
BhendiGawaar

39

Seyir defteri kütüphane özellikle günlüğü HTTP istekleri ve yanıtları için yapılır. Özel bir başlangıç ​​kütüphanesi kullanarak Spring Boot'u destekler.

Spring Boot'da günlüğe kaydetmeyi etkinleştirmek için tek yapmanız gereken kütüphaneyi projenizin bağımlılıklarına eklemektir. Örneğin Maven kullandığınızı varsayarsak:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>

Varsayılan olarak günlük çıktısı şöyle görünür:

{
  "origin" : "local",
  "correlation" : "52e19498-890c-4f75-a06c-06ddcf20836e",
  "status" : 200,
  "headers" : {
    "X-Application-Context" : [
      "application:8088"
    ],
    "Content-Type" : [
      "application/json;charset=UTF-8"
    ],
    "Transfer-Encoding" : [
      "chunked"
    ],
    "Date" : [
      "Sun, 24 Dec 2017 13:10:45 GMT"
    ]
  },
  "body" : {
    "thekey" : "some_example"
  },
  "duration" : 105,
  "protocol" : "HTTP/1.1",
  "type" : "response"
}

Ancak isteği işleyen sınıf adını vermez. Kütüphanede, özel günlük kayıtları yazmak için bazı arayüzler bulunur.


4
Minimal bir bahar önyükleme uygulamasına bağımlılık olarak eklendi ve çalıştırmayı denedi - değişiklik yok, uygulamamda hiç günlük çıkışı yok. Bu ihtiyaç duyulan bazı ek bağımlılıklar veya sınıflar olduğunu düşünüyorum? Bir filtre olarak kaydedilmesi de hiçbir şey yapmıyor gibi görünüyor.
eis

1
@ eis Buradaki dokümanlarda açıklandığı gibi bir filtre olarak kaydetmeniz gerekir. github.com/zalando/logbook
Pratik Singhal

2
Seyir defteri dokümanı şöyle diyor: "Seyir defteri, Spring Boot kullanıcıları için uygun bir otomatik yapılandırma ile birlikte gelir. Aşağıdaki tüm parçaları otomatik olarak makul varsayılan değerlerle ayarlar." Ama bu çalışmıyor.
Leos Literak

5
@LeosLiterak Sana eklemem gerekiyor inanıyoruz logging.level.org.zalando.logbook=TRACE adresinden Müşteri application.properties(belirtildiği gibi Readme)
TolkienWASP

2
Seyir defteri otomatik yapılandırması, bahar önyükleme
v2.0.5

26

application.propertiesGünlük dosyasında istekleri / yanıtları, yöntem url'sini yazdırmak için günlük düzeyi tanımlamıştım

logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate.SQL=INFO
logging.file=D:/log/myapp.log

Spring Boot'u kullanmıştım.


2
Evet, haklısınız - bu, diğer tüm sonuçlarla aynı günlük dosyasına günlük kaydı alma istekleri için geçerli bir yanıttır. Ancak, moreo GET, POST, vb. Ve ayrı dosyaya (anladığım gibi) giriş yapmasını istedi
Manushin Igor

4
Bunu beğendim. sıfır dram
Quirino Gervacio

1
Başlıkların günlüğe dahil edilmesini istiyorsanız, application.properties dosyasına "spring.http.log-request-details = true" eklemeniz gerekir.
jfajunior

20

İşte kullanarak bahar veri geri kalanında böyle yaparız org.springframework.web.util.ContentCachingRequestWrapper ve org.springframework.web.util.ContentCachingResponseWrapper

/**
 * Doogies very cool HTTP request logging
 *
 * There is also {@link org.springframework.web.filter.CommonsRequestLoggingFilter}  but it cannot log request method
 * And it cannot easily be extended.
 *
 * https://mdeinum.wordpress.com/2015/07/01/spring-framework-hidden-gems/
 * http://stackoverflow.com/questions/8933054/how-to-read-and-copy-the-http-servlet-response-output-stream-content-for-logging
 */
public class DoogiesRequestLogger extends OncePerRequestFilter {

  private boolean includeResponsePayload = true;
  private int maxPayloadLength = 1000;

  private String getContentAsString(byte[] buf, int maxLength, String charsetName) {
    if (buf == null || buf.length == 0) return "";
    int length = Math.min(buf.length, this.maxPayloadLength);
    try {
      return new String(buf, 0, length, charsetName);
    } catch (UnsupportedEncodingException ex) {
      return "Unsupported Encoding";
    }
  }

  /**
   * Log each request and respponse with full Request URI, content payload and duration of the request in ms.
   * @param request the request
   * @param response the response
   * @param filterChain chain of filters
   * @throws ServletException
   * @throws IOException
   */
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    StringBuffer reqInfo = new StringBuffer()
     .append("[")
     .append(startTime % 10000)  // request ID
     .append("] ")
     .append(request.getMethod())
     .append(" ")
     .append(request.getRequestURL());

    String queryString = request.getQueryString();
    if (queryString != null) {
      reqInfo.append("?").append(queryString);
    }

    if (request.getAuthType() != null) {
      reqInfo.append(", authType=")
        .append(request.getAuthType());
    }
    if (request.getUserPrincipal() != null) {
      reqInfo.append(", principalName=")
        .append(request.getUserPrincipal().getName());
    }

    this.logger.debug("=> " + reqInfo);

    // ========= Log request and response payload ("body") ========
    // We CANNOT simply read the request payload here, because then the InputStream would be consumed and cannot be read again by the actual processing/server.
    //    String reqBody = DoogiesUtil._stream2String(request.getInputStream());   // THIS WOULD NOT WORK!
    // So we need to apply some stronger magic here :-)
    ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
    ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

    filterChain.doFilter(wrappedRequest, wrappedResponse);     // ======== This performs the actual request!
    long duration = System.currentTimeMillis() - startTime;

    // I can only log the request's body AFTER the request has been made and ContentCachingRequestWrapper did its work.
    String requestBody = this.getContentAsString(wrappedRequest.getContentAsByteArray(), this.maxPayloadLength, request.getCharacterEncoding());
    if (requestBody.length() > 0) {
      this.logger.debug("   Request body:\n" +requestBody);
    }

    this.logger.debug("<= " + reqInfo + ": returned status=" + response.getStatus() + " in "+duration + "ms");
    if (includeResponsePayload) {
      byte[] buf = wrappedResponse.getContentAsByteArray();
      this.logger.debug("   Response body:\n"+getContentAsString(buf, this.maxPayloadLength, response.getCharacterEncoding()));
    }

    wrappedResponse.copyBodyToResponse();  // IMPORTANT: copy content of response back into original response

  }


}

18

Spring AOP'u denemeye aldırmazsanız, bu günlük kaydı için araştırdığım bir şey ve benim için oldukça iyi çalışıyor. Bu henüz tanımlanmamış ve başarısız istek girişimleri günlük istekleri alışkanlık.

Bu üç bağımlılığı ekle

spring-aop, aspectjrt, aspectjweaver

Bunu xml yapılandırma dosyanıza ekleyin <aop:aspectj-autoproxy/>

Nokta kesimi olarak kullanılabilecek bir ek açıklama oluşturun

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface EnableLogging {
ActionType actionType();
}

Günlüğe kaydetmek istediğiniz tüm dinlenme API yöntemlerinize ek açıklama ekleyin

@EnableLogging(actionType = ActionType.SOME_EMPLOYEE_ACTION)
@Override
public Response getEmployees(RequestDto req, final String param) {
...
}

Şimdi Unsur'a geçelim. Bu sınıfın bulunduğu paketi bileşen taraması ile tarayın.

@Aspect
@Component
public class Aspects {

@AfterReturning(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", returning = "result")
public void auditInfo(JoinPoint joinPoint, Object result, EnableLogging enableLogging, Object reqArg, String reqArg1) {

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
            .getRequest();

    if (result instanceof Response) {
        Response responseObj = (Response) result;

    String requestUrl = request.getScheme() + "://" + request.getServerName()
                + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
                + "?" + request.getQueryString();

String clientIp = request.getRemoteAddr();
String clientRequest = reqArg.toString();
int httpResponseStatus = responseObj.getStatus();
responseObj.getEntity();
// Can log whatever stuff from here in a single spot.
}


@AfterThrowing(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", throwing="exception")
public void auditExceptionInfo(JoinPoint joinPoint, Throwable exception, EnableLogging enableLogging, Object reqArg, String reqArg1) {

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
            .getRequest();

    String requestUrl = request.getScheme() + "://" + request.getServerName()
    + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
    + "?" + request.getQueryString();

    exception.getMessage();
    exception.getCause();
    exception.printStackTrace();
    exception.getLocalizedMessage();
    // Can log whatever exceptions, requests, etc from here in a single spot.
    }
}

@AfterReturning tavsiyesi, eşleşen bir yöntem yürütmesi normal olarak döndüğünde çalışır.

@AfterThrowing tavsiyesi, bir istisna atarak eşleşen bir yöntem yürütme tamamlandığında çalışır.

Ayrıntılı olarak okumak istiyorsanız bunu okuyun. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html


1
Bu, HTTP düzeyinde gerçekte alınan ve gönderilenleri değil, yöntem çağrısını günlüğe kaydeder.
Tom Howard

1
BODY talebi nasıl yazılır? Benim durumumda POST BODY. request.getReader veya getInputStream üzerinde akışı kapalı olduğunu hatası alıyorum.

13

Spring boot bassed uygulamasına Aktüatörler ekledikten sonra /trace, son talep bilgileriyle birlikte uç noktaya sahipsiniz . Bu son nokta temel alarak çalışıp, TraceRepository ve varsayılan uygulamasıdır InMemoryTraceRepository son 100 aramaları kaydeder. Bunu, bu arayüzü kendiniz uygulayarak değiştirebilir ve bir bahar fasulyesi olarak kullanılabilir hale getirebilirsiniz. Örneğin, tüm istekleri günlüğe kaydetmek için (ve yine de varsayılan uygulamayı /traceuç noktada bilgi sunmak için temel depolama alanı olarak kullanmak için ) Bu tür bir uygulama kullanıyorum:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
import org.springframework.boot.actuate.trace.Trace;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;


@Component
public class LoggingTraceRepository implements TraceRepository {

  private static final Logger LOG = LoggerFactory.getLogger(LoggingTraceRepository.class);
  private final TraceRepository delegate = new InMemoryTraceRepository();

  @Override
  public List<Trace> findAll() {
    return delegate.findAll();
  }

  @Override
  public void add(Map<String, Object> traceInfo) {
    LOG.info(traceInfo.toString());
    this.delegate.add(traceInfo);
  }
}

Bu traceInfoharita formun bu tür istek ve yanıt hakkında temel bilgileri içerir: {method=GET, path=/api/hello/John, headers={request={host=localhost:8080, user-agent=curl/7.51.0, accept=*/*}, response={X-Application-Context=application, Content-Type=text/plain;charset=UTF-8, Content-Length=10, Date=Wed, 29 Mar 2017 20:41:21 GMT, status=200}}}. Burada yanıt içeriği yok.

DÜZENLE! POST verilerinin günlüğe kaydedilmesi

Sen geçersiz kılarak POST verilerine erişebilir WebRequestTraceFilter ama bunun iyi bir fikir (örneğin tüm yüklenen dosya içeriği günlükleri gidecek) İşte örnek kod, ama sanmıyorum yok kullanmak:

package info.fingo.nuntius.acuate.trace;

import org.apache.commons.io.IOUtils;
import org.springframework.boot.actuate.trace.TraceProperties;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.actuate.trace.WebRequestTraceFilter;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;

@Component
public class CustomWebTraceFilter extends WebRequestTraceFilter {

  public CustomWebTraceFilter(TraceRepository repository, TraceProperties properties) {
    super(repository, properties);
}

  @Override
  protected Map<String, Object> getTrace(HttpServletRequest request) {
    Map<String, Object> trace = super.getTrace(request);
    String multipartHeader = request.getHeader("content-type");
    if (multipartHeader != null && multipartHeader.startsWith("multipart/form-data")) {
        Map<String, Object> parts = new LinkedHashMap<>();
        try {
            request.getParts().forEach(
                    part -> {
                        try {
                            parts.put(part.getName(), IOUtils.toString(part.getInputStream(), Charset.forName("UTF-8")));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
            );
        } catch (IOException | ServletException e) {
            e.printStackTrace();
        }
        if (!parts.isEmpty()) {
            trace.put("multipart-content-map", parts);
        }
    }
    return trace;
  }
}

1
POST gövdesi ne olacak?
Pavel Vyazankin

@dart Sizin için bir örnek ekledim
Piotr Chowaniec

1
Ben böyle bir şey yapıyordum, ama sorun cevap organının mevcut olmaması TraceRepository, buna nasıl erişebiliriz?
Amir Pashazadeh

@AmirPashazadeh geçersiz kılmanız gerekiyor, protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)ancak bu filtrenin ne zaman yürütüldüğünden emin değilim - istek aşamasında olabilir, bu nedenle yanıt gövdesi orada hazır olmayacaktır.
Piotr Chowaniec

1
@Kekar ​​2.0'dan beri HttpTraceRepository (TraceRepository yerine) var
Piotr Chowaniec

12

Bu kod bir Spring Boot uygulamasında benim için çalışıyor - sadece bir filtre olarak kaydedin

    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.util.Collection;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Locale;
    import java.util.Map;
    import javax.servlet.*;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.commons.io.output.TeeOutputStream;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;

    @Component
    public class HttpLoggingFilter implements Filter {

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

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                             FilterChain chain) throws IOException, ServletException {
            try {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;

                Map<String, String> requestMap = this
                        .getTypesafeRequestMap(httpServletRequest);
                BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(
                        httpServletRequest);
                BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(
                        httpServletResponse);

                final StringBuilder logMessage = new StringBuilder(
                        "REST Request - ").append("[HTTP METHOD:")
                        .append(httpServletRequest.getMethod())
                        .append("] [PATH INFO:")
                        .append(httpServletRequest.getServletPath())
                        .append("] [REQUEST PARAMETERS:").append(requestMap)
                        .append("] [REQUEST BODY:")
                        .append(bufferedRequest.getRequestBody())
                        .append("] [REMOTE ADDRESS:")
                        .append(httpServletRequest.getRemoteAddr()).append("]");

                chain.doFilter(bufferedRequest, bufferedResponse);
                logMessage.append(" [RESPONSE:")
                        .append(bufferedResponse.getContent()).append("]");
                log.debug(logMessage.toString());
            } catch (Throwable a) {
                log.error(a.getMessage());
            }
        }

        private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
            Map<String, String> typesafeRequestMap = new HashMap<String, String>();
            Enumeration<?> requestParamNames = request.getParameterNames();
            while (requestParamNames.hasMoreElements()) {
                String requestParamName = (String) requestParamNames.nextElement();
                String requestParamValue;
                if (requestParamName.equalsIgnoreCase("password")) {
                    requestParamValue = "********";
                } else {
                    requestParamValue = request.getParameter(requestParamName);
                }
                typesafeRequestMap.put(requestParamName, requestParamValue);
            }
            return typesafeRequestMap;
        }

        @Override
        public void destroy() {
        }

        private static final class BufferedRequestWrapper extends
                HttpServletRequestWrapper {

            private ByteArrayInputStream bais = null;
            private ByteArrayOutputStream baos = null;
            private BufferedServletInputStream bsis = null;
            private byte[] buffer = null;

            public BufferedRequestWrapper(HttpServletRequest req)
                    throws IOException {
                super(req);
                // Read InputStream and store its content in a buffer.
                InputStream is = req.getInputStream();
                this.baos = new ByteArrayOutputStream();
                byte buf[] = new byte[1024];
                int read;
                while ((read = is.read(buf)) > 0) {
                    this.baos.write(buf, 0, read);
                }
                this.buffer = this.baos.toByteArray();
            }

            @Override
            public ServletInputStream getInputStream() {
                this.bais = new ByteArrayInputStream(this.buffer);
                this.bsis = new BufferedServletInputStream(this.bais);
                return this.bsis;
            }

            String getRequestBody() throws IOException {
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                        this.getInputStream()));
                String line = null;
                StringBuilder inputBuffer = new StringBuilder();
                do {
                    line = reader.readLine();
                    if (null != line) {
                        inputBuffer.append(line.trim());
                    }
                } while (line != null);
                reader.close();
                return inputBuffer.toString().trim();
            }

        }

        private static final class BufferedServletInputStream extends
                ServletInputStream {

            private ByteArrayInputStream bais;

            public BufferedServletInputStream(ByteArrayInputStream bais) {
                this.bais = bais;
            }

            @Override
            public int available() {
                return this.bais.available();
            }

            @Override
            public int read() {
                return this.bais.read();
            }

            @Override
            public int read(byte[] buf, int off, int len) {
                return this.bais.read(buf, off, len);
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        }

        public class TeeServletOutputStream extends ServletOutputStream {

            private final TeeOutputStream targetStream;

            public TeeServletOutputStream(OutputStream one, OutputStream two) {
                targetStream = new TeeOutputStream(one, two);
            }

            @Override
            public void write(int arg0) throws IOException {
                this.targetStream.write(arg0);
            }

            public void flush() throws IOException {
                super.flush();
                this.targetStream.flush();
            }

            public void close() throws IOException {
                super.close();
                this.targetStream.close();
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {

            }
        }

        public class BufferedResponseWrapper implements HttpServletResponse {

            HttpServletResponse original;
            TeeServletOutputStream tee;
            ByteArrayOutputStream bos;

            public BufferedResponseWrapper(HttpServletResponse response) {
                original = response;
            }

            public String getContent() {
                return bos.toString();
            }

            public PrintWriter getWriter() throws IOException {
                return original.getWriter();
            }

            public ServletOutputStream getOutputStream() throws IOException {
                if (tee == null) {
                    bos = new ByteArrayOutputStream();
                    tee = new TeeServletOutputStream(original.getOutputStream(),
                            bos);
                }
                return tee;

            }

            @Override
            public String getCharacterEncoding() {
                return original.getCharacterEncoding();
            }

            @Override
            public String getContentType() {
                return original.getContentType();
            }

            @Override
            public void setCharacterEncoding(String charset) {
                original.setCharacterEncoding(charset);
            }

            @Override
            public void setContentLength(int len) {
                original.setContentLength(len);
            }

            @Override
            public void setContentLengthLong(long l) {
                original.setContentLengthLong(l);
            }

            @Override
            public void setContentType(String type) {
                original.setContentType(type);
            }

            @Override
            public void setBufferSize(int size) {
                original.setBufferSize(size);
            }

            @Override
            public int getBufferSize() {
                return original.getBufferSize();
            }

            @Override
            public void flushBuffer() throws IOException {
                tee.flush();
            }

            @Override
            public void resetBuffer() {
                original.resetBuffer();
            }

            @Override
            public boolean isCommitted() {
                return original.isCommitted();
            }

            @Override
            public void reset() {
                original.reset();
            }

            @Override
            public void setLocale(Locale loc) {
                original.setLocale(loc);
            }

            @Override
            public Locale getLocale() {
                return original.getLocale();
            }

            @Override
            public void addCookie(Cookie cookie) {
                original.addCookie(cookie);
            }

            @Override
            public boolean containsHeader(String name) {
                return original.containsHeader(name);
            }

            @Override
            public String encodeURL(String url) {
                return original.encodeURL(url);
            }

            @Override
            public String encodeRedirectURL(String url) {
                return original.encodeRedirectURL(url);
            }

            @SuppressWarnings("deprecation")
            @Override
            public String encodeUrl(String url) {
                return original.encodeUrl(url);
            }

            @SuppressWarnings("deprecation")
            @Override
            public String encodeRedirectUrl(String url) {
                return original.encodeRedirectUrl(url);
            }

            @Override
            public void sendError(int sc, String msg) throws IOException {
                original.sendError(sc, msg);
            }

            @Override
            public void sendError(int sc) throws IOException {
                original.sendError(sc);
            }

            @Override
            public void sendRedirect(String location) throws IOException {
                original.sendRedirect(location);
            }

            @Override
            public void setDateHeader(String name, long date) {
                original.setDateHeader(name, date);
            }

            @Override
            public void addDateHeader(String name, long date) {
                original.addDateHeader(name, date);
            }

            @Override
            public void setHeader(String name, String value) {
                original.setHeader(name, value);
            }

            @Override
            public void addHeader(String name, String value) {
                original.addHeader(name, value);
            }

            @Override
            public void setIntHeader(String name, int value) {
                original.setIntHeader(name, value);
            }

            @Override
            public void addIntHeader(String name, int value) {
                original.addIntHeader(name, value);
            }

            @Override
            public void setStatus(int sc) {
                original.setStatus(sc);
            }

            @SuppressWarnings("deprecation")
            @Override
            public void setStatus(int sc, String sm) {
                original.setStatus(sc, sm);
            }

            @Override
            public String getHeader(String arg0) {
                return original.getHeader(arg0);
            }

            @Override
            public Collection<String> getHeaderNames() {
                return original.getHeaderNames();
            }

            @Override
            public Collection<String> getHeaders(String arg0) {
                return original.getHeaders(arg0);
            }

            @Override
            public int getStatus() {
                return original.getStatus();
            }

        }
    }

Yanıt günlüğü için iyi çalışır - günlükleri bayt sayısına bir sınır koymak zorunda kaldı, aksi takdirde Intellij günlük konsolu çıktısını çöker.
Adam

String getContent () {if (bos == null) {return String.format ("% s çok erken çağrıldı", BufferedResponseWrapper.class.getCanonicalName ()); } byte [] bytes = bos.toByteArray (); return new String (Arrays.copyOf (bayt, 5000)) + "...."; }
Adam

Ayrıca günlük kaydı çevresinde bir "log.isTraceEnabled ()" anahtarı koymaya değer.
Adam

6
Ne güzel olur, Java HttpServletResponse'ye bazı varsayılan yöntemler eklediğinde, bu kadar büyük bir uygulama yazmamız gerekmiyor.
Adam

1
artı bir ithalat beyanları dahil
granadaCoder

7

İşte benim çözümüm (Spring 2.0.x)

Maven bağımlılığını ekleyin:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Düzenleme application.properties ve aşağıdaki satırı ekleyin:

management.endpoints.web.exposure.include=* 

İlkbahar önyükleme uygulamanız başlatıldığında, şu url'yi arayarak en son 100 http isteğini izleyebilirsiniz: http: // localhost: 8070 / aktüatör / httptrace


7

Şu anda Spring Boot, isteklerin ve yanıtların günlüklerini almak için Aktüatör özelliğine sahiptir.

Ancak günlükleri Aspect (AOP) kullanarak da alabilirsiniz.

: Aspect gibi ek açıklamalar sağlar @Before, @AfterReturning, @AfterThrowingvb

@Beforeisteği @AfterReturninggünlüğe kaydeder, yanıtı @AfterThrowinggünlüğe kaydeder ve hata iletisini günlüğe kaydeder, Tüm uç noktaların günlüğüne ihtiyacınız olmayabilir, böylece paketlere bazı filtreler uygulayabilirsiniz.

İşte bazı örnekler :

İstek için:

@Before("within(your.package.where.endpoints.are..*)")
    public void endpointBefore(JoinPoint p) {
        if (log.isTraceEnabled()) {
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
            Object[] signatureArgs = p.getArgs();


            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {

                if (signatureArgs[0] != null) {
                    log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                }
            } catch (JsonProcessingException e) {
            }
        }
    }

Burada @Before("within(your.package.where.endpoints.are..*)")paket yolu var. Bu paket içindeki tüm uç noktalar günlüğü oluşturur.

Yanıt için:

@AfterReturning(value = ("within(your.package.where.endpoints.are..*)"),
            returning = "returnValue")
    public void endpointAfterReturning(JoinPoint p, Object returnValue) {
        if (log.isTraceEnabled()) {
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {
                log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
            } catch (JsonProcessingException e) {
                System.out.println(e.getMessage());
            }
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
        }
    }

Burada @AfterReturning("within(your.package.where.endpoints.are..*)")paket yolu var. Bu paket içindeki tüm uç noktalar günlüğü oluşturur. Ayrıca Object returnValueyanıtı içerir.

İstisna için:

@AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e")
public void endpointAfterThrowing(JoinPoint p, Exception e) throws DmoneyException {
    if (log.isTraceEnabled()) {
        System.out.println(e.getMessage());

        e.printStackTrace();


        log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
    }
}

Burada @AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e") paket yolu var. Bu paket içindeki tüm uç noktalar günlüğü oluşturur. Ayrıca Exception ehata yanıtı içerir.

İşte tam kod:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(1)
@Component
@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
public class EndpointAspect {
    static Logger log = Logger.getLogger(EndpointAspect.class);

    @Before("within(your.package.where.is.endpoint..*)")
    public void endpointBefore(JoinPoint p) {
        if (log.isTraceEnabled()) {
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
            Object[] signatureArgs = p.getArgs();


            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {

                if (signatureArgs[0] != null) {
                    log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                }
            } catch (JsonProcessingException e) {
            }
        }
    }

    @AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
            returning = "returnValue")
    public void endpointAfterReturning(JoinPoint p, Object returnValue) {
        if (log.isTraceEnabled()) {
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {
                log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
            } catch (JsonProcessingException e) {
                System.out.println(e.getMessage());
            }
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
        }
    }


    @AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
    public void endpointAfterThrowing(JoinPoint p, Exception e) throws Exception {
        if (log.isTraceEnabled()) {
            System.out.println(e.getMessage());

            e.printStackTrace();


            log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
        }
    }
}

Burada kullanarak @ConditionalOnExpression("${endpoint.aspect.enabled:true}")günlüğü etkinleştirebilir / devre dışı bırakabilirsiniz. Sadece eklemek endpoint.aspect.enabled:trueiçine application.propertyve günlüğü kontrol

AOP hakkında daha fazla bilgi için buraya tıklayın:

AOP hakkında bahar rıhtımları

AOP hakkında örnek makale


1
new ObjectMapper()pahalı, herkes için bir haritacı paylaşmak daha iyi
Sam

Evet tabi. Bu demo kodudur. Üretimde en iyi uygulamaları takip etmeliyiz.
Md. Sajedul Karim

5

Ayrıca HandlerInterceptorAdapter, yalnızca öncesi / yalnızca sonrası önleme araçlarının basitleştirilmiş bir uygulaması için özel bir Bahar önleme aracı da yapılandırabilirsiniz :

@Component
public class CustomHttpInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle (final HttpServletRequest request, final HttpServletResponse response,
            final Object handler)
            throws Exception {

        // Logs here

        return super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {
        // Logs here
    }
}

Ardından, istediğiniz kadar önleyici kaydedersiniz:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    CustomHttpInterceptor customHttpInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customHttpInterceptor).addPathPatterns("/endpoints");
    }

}

Not: @Robert tarafından belirtildiği gibi , özel uygulamalarına dikkat etmeniz gerekir ve uygulamanız kullanılır. HttpServletRequestHttpServletResponse

Örneğin, kullanarak uygulamalar için ShallowEtagHeaderFilter, yanıt uygulaması olacağını ContentCachingResponseWrapperederdiniz, böylece:

@Component
public class CustomHttpInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomHttpInterceptor.class);

    private static final int MAX_PAYLOAD_LENGTH = 1000;

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {
        final byte[] contentAsByteArray = ((ContentCachingResponseWrapper) response).getContentAsByteArray();

        LOGGER.info("Request body:\n" + getContentAsString(contentAsByteArray, response.getCharacterEncoding()));
    }

    private String getContentAsString(byte[] buf, String charsetName) {
        if (buf == null || buf.length == 0) {
            return "";
        }

        try {
            int length = Math.min(buf.length, MAX_PAYLOAD_LENGTH);

            return new String(buf, 0, length, charsetName);
        } catch (UnsupportedEncodingException ex) {
            return "Unsupported Encoding";
        }
    }

}

4

@ hahn'ın cevabı , benim için çalışması için biraz değişiklik gerektirdi, ancak bu, elde edebileceğim en özelleştirilebilir şey.

Benim için işe yaramadı, çünkü muhtemelen bir HandlerInterceptorAdapter [??] var ama bu sürümde sunucudan kötü bir yanıt almaya devam ettim. İşte benim modifikasyonum.

public class LoggableDispatcherServlet extends DispatcherServlet {

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        long startTime = System.currentTimeMillis();
        try {
            super.doDispatch(request, response);
        } finally {
            log(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response),
                    System.currentTimeMillis() - startTime);
        }
    }

    private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, long timeTaken) {
        int status = responseToCache.getStatus();
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("httpStatus", status);
        jsonObject.addProperty("path", requestToCache.getRequestURI());
        jsonObject.addProperty("httpMethod", requestToCache.getMethod());
        jsonObject.addProperty("timeTakenMs", timeTaken);
        jsonObject.addProperty("clientIP", requestToCache.getRemoteAddr());
        if (status > 299) {
            String requestBody = null;
            try {
                requestBody = requestToCache.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
            } catch (IOException e) {
                e.printStackTrace();
            }
            jsonObject.addProperty("requestBody", requestBody);
            jsonObject.addProperty("requestParams", requestToCache.getQueryString());
            jsonObject.addProperty("tokenExpiringHeader",
                    responseToCache.getHeader(ResponseHeaderModifierInterceptor.HEADER_TOKEN_EXPIRING));
        }
        logger.info(jsonObject);
    }
}

uygulamanız savaş veya kavanoz olarak paketlenmiş mi? Java.io.FileNotFoundException hatasını almaya devam ediyorum: ServletContext kaynağı açılamadı [/WEB-INF/loggingDispatcherServlet-servlet.xml]
Mayank Madhav

4

Birisi hala buna ihtiyaç duyarsa, Spring HttpTrace Aktüatör ile basit bir uygulamadır. Ama üstte de söyledikleri gibi, bedenleri kaydetmez.

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
import org.springframework.stereotype.Repository;

@Slf4j
@Repository
public class LoggingInMemoryHttpTraceRepository extends InMemoryHttpTraceRepository {
    public void add(HttpTrace trace) {
        super.add(trace);
        log.info("Trace:" + ToStringBuilder.reflectionToString(trace));
        log.info("Request:" + ToStringBuilder.reflectionToString(trace.getRequest()));
        log.info("Response:" + ToStringBuilder.reflectionToString(trace.getResponse()));
    }
}

4

Gerçek cevap için lütfen aşağıdaki bağlantıya bakın https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

Yukarıda belirtilen çözümden bazı değişiklikler yapıldığında, kayıt cihazı seviyesi bilgi ise, istek ve yanıt konsolda ve dosyada da oturum açacaktır. konsolda veya dosyada yazdırabiliriz.

@Component
public class LoggingFilter extends OncePerRequestFilter {

private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
        MediaType.valueOf("text/*"),
        MediaType.APPLICATION_FORM_URLENCODED,
        MediaType.APPLICATION_JSON,
        MediaType.APPLICATION_XML,
        MediaType.valueOf("application/*+json"),
        MediaType.valueOf("application/*+xml"),
        MediaType.MULTIPART_FORM_DATA
        );
Logger log = LoggerFactory.getLogger(ReqAndResLoggingFilter.class);
private static final Path path = Paths.get("/home/ramesh/loggerReq.txt");
private static BufferedWriter writer = null;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        writer = Files.newBufferedWriter(path, Charset.forName("UTF-8"));
    if (isAsyncDispatch(request)) {
        filterChain.doFilter(request, response);
    } else {
        doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
    }
    }finally {
        writer.close();
    }
}

protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
    try {
        beforeRequest(request, response);
        filterChain.doFilter(request, response);
    }
    finally {
        afterRequest(request, response);
        response.copyBodyToResponse();
    }
}

protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
    if (log.isInfoEnabled()) {
        logRequestHeader(request, request.getRemoteAddr() + "|>");
    }
}

protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
    if (log.isInfoEnabled()) {
        logRequestBody(request, request.getRemoteAddr() + "|>");
        logResponse(response, request.getRemoteAddr() + "|<");
    }
}

private void logRequestHeader(ContentCachingRequestWrapper request, String prefix) throws IOException {
    String queryString = request.getQueryString();
    if (queryString == null) {
        printLines(prefix,request.getMethod(),request.getRequestURI());
        log.info("{} {} {}", prefix, request.getMethod(), request.getRequestURI());
    } else {
        printLines(prefix,request.getMethod(),request.getRequestURI(),queryString);
        log.info("{} {} {}?{}", prefix, request.getMethod(), request.getRequestURI(), queryString);
    }
    Collections.list(request.getHeaderNames()).forEach(headerName ->
    Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
    log.info("{} {}: {}", prefix, headerName, headerValue)));
    printLines(prefix);
    printLines(RequestContextHolder.currentRequestAttributes().getSessionId());
    log.info("{}", prefix);

    log.info(" Session ID: ", RequestContextHolder.currentRequestAttributes().getSessionId());
}

private void printLines(String ...args) throws IOException {

    try {
    for(String varArgs:args) {
            writer.write(varArgs);
            writer.newLine();
    }
        }catch(IOException ex){
            ex.printStackTrace();
    }

}

private void logRequestBody(ContentCachingRequestWrapper request, String prefix) {
    byte[] content = request.getContentAsByteArray();
    if (content.length > 0) {
        logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix);
    }
}

private void logResponse(ContentCachingResponseWrapper response, String prefix) throws IOException {
    int status = response.getStatus();
    printLines(prefix, String.valueOf(status), HttpStatus.valueOf(status).getReasonPhrase());
    log.info("{} {} {}", prefix, status, HttpStatus.valueOf(status).getReasonPhrase());
    response.getHeaderNames().forEach(headerName ->
    response.getHeaders(headerName).forEach(headerValue ->
    log.info("{} {}: {}", prefix, headerName, headerValue)));
    printLines(prefix);
    log.info("{}", prefix);
    byte[] content = response.getContentAsByteArray();
    if (content.length > 0) {
        logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix);
    }
}

private void logContent(byte[] content, String contentType, String contentEncoding, String prefix) {
    MediaType mediaType = MediaType.valueOf(contentType);
    boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
    if (visible) {
        try {
            String contentString = new String(content, contentEncoding);
            Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> {
                try {
                    printLines(line);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            });
//              log.info("{} {}", prefix, line));
        } catch (UnsupportedEncodingException e) {
            log.info("{} [{} bytes content]", prefix, content.length);
        }
    } else {

        log.info("{} [{} bytes content]", prefix, content.length);
    }
}

private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
    if (request instanceof ContentCachingRequestWrapper) {
        return (ContentCachingRequestWrapper) request;
    } else {
        return new ContentCachingRequestWrapper(request);
    }
}

private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
    if (response instanceof ContentCachingResponseWrapper) {
        return (ContentCachingResponseWrapper) response;
    } else {
        return new ContentCachingResponseWrapper(response);
    }
}
} 

Dosyadaki Çıktı:

127.0.0.1|>
POST
/createUser
127.0.0.1|>
session Id:C0793464532E7F0C7154913CBA018B2B
Request:
{
  "name": "asdasdas",
  "birthDate": "2018-06-21T17:11:15.679+0000"
}
127.0.0.1|<
200
OK
127.0.0.1|<
Response:
{"name":"asdasdas","birthDate":"2018-06-21T17:11:15.679+0000","id":4}

1
Harika bir cevap, tek öneri tüm çıktıyı bir tamponda toplamak ve tek bir ifadede oturum açmak olacaktır.
Mike

2

İstek yükünüzün yalnızca bir bölümünü görüyorsanız setMaxPayloadLength, istek gövdesinde yalnızca 50 karakter göstermesi varsayılan olarak işlevi çağırmanız gerekir . Ayrıca, setIncludeHeaderskimlik başlıklarınızı günlüğe kaydetmek istemiyorsanız false olarak ayarlamak iyi bir fikirdir!

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
    CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
    loggingFilter.setIncludeClientInfo(false);
    loggingFilter.setIncludeQueryString(false);
    loggingFilter.setIncludePayload(true);
    loggingFilter.setIncludeHeaders(false);
    loggingFilter.setMaxPayloadLength(500);
    return loggingFilter;
}

Ben bahar mvc kullanmaya çalışıyorum ve benim için çalışmıyor, Bu fasulye kayıt ve logger ekleme dışında herhangi bir ek ayar gereklidir?
Noman Akhtar

1

Tomcat'i önyükleme uygulamanızda kullanırsanız, burada sizin org.apache.catalina.filters.RequestDumperFilteriçin bir sınıf yolundadır. (ancak "tek bir yerde istisnalar" sağlamaz).


1

aşağıda yapıştırılan kod testlerimle çalışır ve [github projemden] [1] indirilebilir, bir üretim projesine dayalı bir çözüm uyguladıktan sonra paylaşabilirsiniz.

@Configuration
public class LoggingFilter extends GenericFilterBean {

    /**
     * It's important that you actually register your filter this way rather then just annotating it
     * as @Component as you need to be able to set for which "DispatcherType"s to enable the filter
     * (see point *1*)
     * 
     * @return
     */
    @Bean
    public FilterRegistrationBean<LoggingFilter> initFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());

        // *1* make sure you sett all dispatcher types if you want the filter to log upon
        registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));

        // *2* this should put your filter above any other filter
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    }

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

        ContentCachingRequestWrapper wreq = 
            new ContentCachingRequestWrapper(
                (HttpServletRequest) request);

        ContentCachingResponseWrapper wres = 
            new ContentCachingResponseWrapper(
                (HttpServletResponse) response);

        try {

            // let it be ...
            chain.doFilter(wreq, wres);

            // makes sure that the input is read (e.g. in 404 it may not be)
            while (wreq.getInputStream().read() >= 0);

            System.out.printf("=== REQUEST%n%s%n=== end request%n",
                    new String(wreq.getContentAsByteArray()));

            // Do whatever logging you wish here, in this case I'm writing request 
            // and response to system out which is probably not what you wish to do
            System.out.printf("=== RESPONSE%n%s%n=== end response%n",
                    new String(wres.getContentAsByteArray()));

            // this is specific of the "ContentCachingResponseWrapper" we are relying on, 
            // make sure you call it after you read the content from the response
            wres.copyBodyToResponse();

            // One more point, in case of redirect this will be called twice! beware to handle that
            // somewhat

        } catch (Throwable t) {
            // Do whatever logging you whish here, too
            // here you should also be logging the error!!!
            throw t;
        }

    }
}

0

Tüm istekleri giriş parametreleri ve gövdeyle kaydetmek için filtreleri ve önleyicileri kullanabiliriz . Ancak bir filtre veya önleme aracı kullanırken, istek gövdesini birden çok kez yazdıramayız. Daha iyi bir yol bahar-AOP kullanabilmemiz. Bunu kullanarak kayıt mekanizmasını uygulamadan ayırabiliriz. AOP günlük kullanılabilir Giriş ve çıkış arasında , her yöntemin uygulamasında.

Benim çözümüm:

 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Pointcut;
 import org.aspectj.lang.reflect.CodeSignature;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 import com.fasterxml.jackson.databind.ObjectMapper;
 @Aspect
 @Component
public class LoggingAdvice {
private static final Logger logger = 
LoggerFactory.getLogger(LoggingAdvice.class);

//here we can provide any methodName, packageName, className 
@Pointcut(value = "execution(* com.package.name.*.*.*(..) )")
public void myPointcut() {

}

@Around("myPointcut()")
public Object applicationLogger(ProceedingJoinPoint pjt) throws Throwable {
    ObjectMapper mapper = new ObjectMapper();
    String methodName = pjt.getSignature().getName();
    String className = pjt.getTarget().getClass().toString();
    String inputParams = this.getInputArgs(pjt ,mapper);
    logger.info("method invoked from " + className + " : " + methodName + "--Request Payload::::"+inputParams);
    Object object = pjt.proceed();
    try {
        logger.info("Response Object---" + mapper.writeValueAsString(object));
    } catch (Exception e) {
    }
    return object;
}

private String getInputArgs(ProceedingJoinPoint pjt,ObjectMapper mapper) {
    Object[] array = pjt.getArgs();
    CodeSignature signature = (CodeSignature) pjt.getSignature();

    StringBuilder sb = new StringBuilder();
    sb.append("{");
    int i = 0;
    String[] parameterNames = signature.getParameterNames();
    int maxArgs = parameterNames.length;
    for (String name : signature.getParameterNames()) {
        sb.append("[").append(name).append(":");
        try {
            sb.append(mapper.writeValueAsString(array[i])).append("]");
            if(i != maxArgs -1 ) {
                sb.append(",");
            }
        } catch (Exception e) {
            sb.append("],");
        }
        i++;
    }
    return sb.append("}").toString();
}

}


0

Spring boot Config sunucunuz yapılandırılmışsa, sınıf için Debug logger'ı etkinleştirmeniz yeterlidir:

Http11InputBuffer.Http11InputBuffer.java

Hata ayıklamalar, her istek için tüm istekleri ve yanıtları günlüğe kaydeder


-1

Yalnızca 400 ile sonuçlanan istekleri günlüğe kaydetmek için:

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;

/**
 * Implementation is partially copied from {@link AbstractRequestLoggingFilter} and modified to output request information only if request resulted in 400.
 * Unfortunately {@link AbstractRequestLoggingFilter} is not smart enough to expose {@link HttpServletResponse} value in afterRequest() method.
 */
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {

    public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request [";

    public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]";

    private final boolean includeQueryString = true;
    private final boolean includeClientInfo = true;
    private final boolean includeHeaders = true;
    private final boolean includePayload = true;

    private final int maxPayloadLength = (int) (2 * FileUtils.ONE_MB);

    private final String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX;

    private final String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX;

    /**
     * The default value is "false" so that the filter may log a "before" message
     * at the start of request processing and an "after" message at the end from
     * when the last asynchronously dispatched thread is exiting.
     */
    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

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

        final boolean isFirstRequest = !isAsyncDispatch(request);
        HttpServletRequest requestToUse = request;

        if (includePayload && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request, maxPayloadLength);
        }

        final boolean shouldLog = shouldLog(requestToUse);

        try {
            filterChain.doFilter(requestToUse, response);
        } finally {
            if (shouldLog && !isAsyncStarted(requestToUse)) {
                afterRequest(requestToUse, response, getAfterMessage(requestToUse));
            }
        }
    }

    private String getAfterMessage(final HttpServletRequest request) {
        return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix);
    }

    private String createMessage(final HttpServletRequest request, final String prefix, final String suffix) {
        final StringBuilder msg = new StringBuilder();
        msg.append(prefix);
        msg.append("uri=").append(request.getRequestURI());

        if (includeQueryString) {
            final String queryString = request.getQueryString();
            if (queryString != null) {
                msg.append('?').append(queryString);
            }
        }

        if (includeClientInfo) {
            final String client = request.getRemoteAddr();
            if (StringUtils.hasLength(client)) {
                msg.append(";client=").append(client);
            }
            final HttpSession session = request.getSession(false);
            if (session != null) {
                msg.append(";session=").append(session.getId());
            }
            final String user = request.getRemoteUser();
            if (user != null) {
                msg.append(";user=").append(user);
            }
        }

        if (includeHeaders) {
            msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders());
        }

        if (includeHeaders) {
            final ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
            if (wrapper != null) {
                final byte[] buf = wrapper.getContentAsByteArray();
                if (buf.length > 0) {
                    final int length = Math.min(buf.length, maxPayloadLength);
                    String payload;
                    try {
                        payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
                    } catch (final UnsupportedEncodingException ex) {
                        payload = "[unknown]";
                    }
                    msg.append(";payload=").append(payload);
                }
            }
        }
        msg.append(suffix);
        return msg.toString();
    }

    private boolean shouldLog(final HttpServletRequest request) {
        return true;
    }

    private void afterRequest(final HttpServletRequest request, final HttpServletResponse response, final String message) {
        if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) {
            logger.warn(message);
        }
    }

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.