Spring Boot - Bagaimana cara mencatat semua permintaan dan tanggapan dengan pengecualian di satu tempat?

218

Saya sedang mengerjakan api sisanya dengan spring boot. Saya perlu mencatat semua permintaan dengan params input (dengan metode, mis. GET, POST, dll), jalur permintaan, string kueri, metode kelas yang sesuai dari permintaan ini, juga respons dari tindakan ini, baik keberhasilan maupun kesalahan.

Sebagai contoh:

permintaan yang berhasil:

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

Log harus terlihat seperti ini:

{
   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: "[email protected]"   
      }
   },
   exceptions: []       
}

Atau permintaan dengan kesalahan:

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

Log harus seperti ini:

    {
       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: ...................    
       ]       
    }

Saya ingin Permintaan / Respons menjadi entitas tunggal, dengan informasi khusus yang terkait dengan entitas ini, baik dalam kasus yang berhasil maupun yang salah.

Apa praktik terbaik di musim semi untuk mencapai ini, mungkin dengan filter? jika ya, dapatkah Anda memberikan contoh nyata?

(Saya sudah bermain dengan @ControllerAdvice dan @ExceptionHandler, tetapi seperti yang saya sebutkan, saya perlu menangani semua permintaan kesalahan dan keberhasilan di satu tempat (dan satu log)).

Teimuraz
sumber
Mungkin melalui ServletFilter logging (misalnya stackoverflow.com/a/2171633/995891 ), alternatif HandlerInterceptortetapi yang mungkin tidak bekerja dengan baik dengan logging respon seperti yang disebutkan dalam jawaban: concretepage.com/spring/spring-mvc/... - HandlerInterceptor memiliki akses ke metode (metode: "UsersController.getUser") sekalipun. Itu tidak dikenal dalam filter servlet.
zapl
1
masih, bahkan jika Anda menambahkan filter atau solusi apa pun pada lapisan aplikasi, Anda tidak akan mencatat semua permintaan, karena HTTP 500 Server Error tidak akan dicatat, karena pada saat itu pengecualian yang tidak ditangani akan dilemparkan ke lapisan Aplikasi, tomcat bawaan pada halaman kesalahan akan ditampilkan setelah menelan pengecualian dan ofcourse tidak akan menyimpan log. Juga jika Anda memeriksa jawaban user1817243, jika ada pengecualian dia tidak akan lagi mencatat permintaan tetapi dia akan mencatat pengecualian (!!).
AntJavaDev
Apakah format log itu harus konsisten dengan setiap karakter yang Anda tulis? Sepertinya terjemahan JSON akan optimal dalam kasus Anda: LogClass{ getRequestAndSaveIt()} Gson.toJson(LogClass)as pseudocode
Vale
1
Pembaca yang akan datang dapat mengambil manfaat dari jawaban saya (url untuk mengikuti komentar ini). Pada dasarnya, saya dapat menyatukan dua posting yang berbeda tentang pertanyaan ini. TOLONG pertimbangkan jawaban aktuator (dalam jawaban di bawah) sebelum mencobanya dengan tangan. Tetapi jawaban yang saya posting memungkinkan "400, 404, 500" (apa saja / semua) untuk dicatat, tetapi menetapkan prioritas-urutan ke prioritas terendah (atau di dalam "8" jika Anda melihat kode). stackoverflow.com/questions/10210645/…
granadaCoder
Saya memang mengikuti dokumen musim semi tentang pencatatan dari sini: docs.spring.io/spring-boot/docs/current/reference/html/…
T04435

Jawaban:

149

Jangan menulis Pencegat, Filter, Komponen, Aspek, dll., Ini adalah masalah yang sangat umum dan telah dipecahkan berulang kali.

Spring Boot memiliki modul yang disebut Actuator , yang menyediakan permintaan HTTP untuk keluar dari kotak. Ada titik akhir yang dipetakan ke /trace(SB1.x) atau /actuator/httptrace(SB2.0 +) yang akan menunjukkan kepada Anda 100 permintaan HTTP terakhir. Anda dapat menyesuaikannya untuk mencatat setiap permintaan, atau menulis ke DB.

Untuk mendapatkan titik akhir yang Anda inginkan, Anda memerlukan dependensi spring-boot-starter-actuator, dan juga untuk "memasukkan daftar putih" titik akhir yang Anda cari, dan mungkin mengatur atau menonaktifkan keamanan untuk itu.

Juga, di mana aplikasi ini berjalan? Apakah Anda akan menggunakan PaaS? Penyedia hosting, Heroku misalnya, memberikan permintaan logging sebagai bagian dari layanan mereka dan Anda tidak perlu melakukan apa pun coding apapun itu.

SergeyB
sumber
4
ada detail lebih lanjut? Saya menemukan github.com/spring-projects/spring-boot/tree/master/… , tetapi tidak banyak di luar itu.
Tom Howard
16
Ini tidak dapat digunakan untuk debugging: permintaan yang tidak diautentikasi (misalnya dengan keamanan pegas) jangan dicatat.
bekce
11
Sebenarnya Actuator tidak memiliki komponen spesifik untuk enebling http logging. / trace - hanya tampilkan permintaan N terakhir.
Vladimir Filipchenko
18
@ike_love, bagaimana cara mengkonfigurasi aktuator sedemikian rupa sehingga permintaan pendataan (juga badan POST) ke file?
11
Trace tidak akan mencatat tubuh permintaan dan respons untuk Anda .... yang lainnya (tajuk dll) kecuali itu.
Lekkie
94

Spring sudah menyediakan filter yang melakukan pekerjaan ini. Tambahkan kacang berikut ke konfigurasi Anda

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

Jangan lupa untuk mengubah level log org.springframework.web.filter.CommonsRequestLoggingFiltermenjadi DEBUG.

Yogesh Badke
sumber
75
Perhatikan bahwa itu tidak mencatat respons, hanya permintaan.
Wim Deblauwe
1
Hanya ada permintaan. Bagaimana cara mencatat badan tanggapan menggunakan CommonsRequestLoggingFilter?
user2602807
3
Juga ini tidak mencatat Pengecualian
BhendiGawaar
Yah, itu diharapkan karena ini adalah filter pencatatan permintaan. Lebih lanjut tentang ini di sini: docs.spring.io/spring/docs/current/javadoc-api/org/…
Yogesh Badke
4
Jika Anda memiliki tubuh JSON besar, atur panjang muatan ke sejumlah besar untuk mencatat seluruh badan permintaan. loggingFilter.setMaxPayloadLength (100000);
Venkatesh Nannan
58

Anda dapat menggunakan javax.servlet.Filterjika tidak ada persyaratan untuk login metode java yang telah dijalankan.

Tapi dengan persyaratan ini Anda harus mengakses informasi yang tersimpan dalam handlerMappingdari DispatcherServlet. Karena itu, Anda dapat mengganti DispatcherServletuntuk menyelesaikan pembuatan log pasangan permintaan / respons.

Di bawah ini adalah contoh ide yang dapat lebih ditingkatkan dan diadaptasi untuk kebutuhan Anda.

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 - Berisi informasi tentang penangan permintaan.

Anda kemudian dapat mendaftarkan dispatcher ini sebagai berikut:

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

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

Dan inilah contoh log:

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"}'}

MEMPERBARUI

Dalam hal kesalahan Spring melakukan penanganan kesalahan otomatis. Oleh karena itu, BasicErrorController#errorditampilkan sebagai penangan permintaan. Jika Anda ingin mempertahankan penangan permintaan asli, maka Anda dapat menimpa perilaku ini spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971sebelum #processDispatchResultdipanggil, untuk men-cache penangan asli.

hahn
sumber
2
apa yang terjadi ketika responsnya adalah aliran dan arus tidak mendukung pencarian? Apakah cara di atas masih berfungsi?
Tom Howard
Saya tidak peduli dengan metode yang dipanggil, hanya data yang diterima dan dikirim. Filter tampaknya mengarahkan saya ke arah yang benar dan respons @ ike_love telah mengarahkan saya ke github.com/spring-projects/spring-boot/blob/master/…
Tom Howard
@ TomHoward AFAIK, tidak ada kotak "respons logging" di musim semi. Oleh karena itu Anda dapat memperluas WebRequestTraceFilter atau AbstractRequestLoggingFilter menambahkan logika pencatatan respons.
hahn
Bekerja dengan baik!
Pavel Vlasov
@ hahn mengapa Anda menggunakan servlet Dispatcher untuk ini? dapatkah login yang sama tidak ditambahkan dengan filter di doFilter?
BhendiGawaar
39

The buku catatan perpustakaan khusus dibuat untuk permintaan HTTP logging dan tanggapan. Ini mendukung Spring Boot menggunakan perpustakaan pemula khusus.

Untuk mengaktifkan login di Boot Spring yang perlu Anda lakukan adalah menambahkan perpustakaan ke dependensi proyek Anda. Misalnya dengan asumsi Anda menggunakan Maven:

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

Secara default output logging terlihat seperti ini:

{
  "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"
}

Namun itu tidak menampilkan nama kelas yang menangani permintaan. Perpustakaan memang memiliki beberapa antarmuka untuk menulis logger kustom.

sihaya
sumber
4
ditambahkan sebagai ketergantungan pada aplikasi booting minimal dan mencoba menjalankan - tidak ada perubahan, tidak ada keluaran logging sama sekali di aplikasi saya. Saya pikir ada beberapa dependensi atau kelas tambahan yang dibutuhkan ini? Mendaftarkannya sebagai filter juga tidak melakukan apa-apa.
eis
1
@ ya Anda harus mendaftarkannya sebagai filter seperti yang dijelaskan dalam dokumen di sini. github.com/zalando/logbook
Pratik Singhal
2
Logbook doc mengatakan: "Logbook dilengkapi dengan konfigurasi otomatis yang nyaman untuk pengguna Boot Spring. Ia mengatur semua bagian berikut secara otomatis dengan default yang masuk akal." Tapi itu tidak berhasil.
Leos Literak
5
@LeosLiterak Saya percaya Anda perlu menambahkan logging.level.org.zalando.logbook=TRACE ke Anda application.properties(seperti yang dinyatakan dalam Readme)
TolkienWASP
2
Konfigurasi otomatis buku log tampaknya tidak berfungsi untuk spring-boot v2.0.5
Yashveer Rana
26

Saya telah menetapkan level login application.propertiesuntuk mencetak permintaan / tanggapan, url metode dalam file log

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

Saya telah menggunakan Spring Boot.

Ankit
sumber
2
Ya, Anda benar - ini adalah jawaban yang valid untuk mendapatkan permintaan masuk ke file log yang sama dengan semua hasil lainnya. Namun, @moreo diminta untuk log GET, POST, dll. Dan ke file terpisah (seperti yang saya mengerti)
Manushin Igor
4
Saya suka yang ini. drama nol
Quirino Gervacio
1
Jika Anda ingin header dimasukkan dalam log, maka Anda harus menambahkan: "spring.http.log-request-details = true" ke file application.properties Anda.
jfajunior
20

Inilah cara saya melakukannya di pegas data rest dengan menggunakan org.springframework.web.util.ContentCachingRequestWrapper dan 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

  }


}
Robert
sumber
18

Jika Anda tidak keberatan mencoba Spring AOP, ini adalah sesuatu yang telah saya jelajahi untuk tujuan logging dan ini bekerja cukup baik untuk saya. Itu tidak akan mencatat permintaan yang belum ditentukan dan gagal upaya permintaan.

Tambahkan tiga dependensi ini

spring-aop, aspectjrt, aspectjweaver

Tambahkan ini ke file konfigurasi xml Anda <aop:aspectj-autoproxy/>

Buat anotasi yang dapat digunakan sebagai titik potong

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

Sekarang beri catatan semua metode API lainnya yang ingin Anda catat

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

Sekarang ke Aspek. memindai komponen yang ada di kelas ini.

@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 advice berjalan ketika eksekusi metode yang cocok kembali secara normal.

@AfterThrowing advice berjalan ketika eksekusi metode yang cocok keluar dengan melemparkan pengecualian.

Jika Anda ingin membaca secara detail bacalah ini. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

Angsa abu-abu
sumber
1
Ini mencatat permohonan metode, bukan apa yang sebenarnya diterima dan dikirim di tingkat HTTP.
Tom Howard
1
Bagaimana cara menulis permintaan BODY? Dalam kasus saya itu adalah TUBUH POS. on request.getReader atau getInputStream Saya mendapatkan kesalahan bahwa aliran ditutup.
13

Setelah menambahkan Aktuator ke aplikasi bass boot spring, Anda memiliki /tracetitik akhir yang tersedia dengan informasi permintaan terbaru. Titik akhir ini berfungsi berdasarkan TraceRepository dan implementasi default adalah InMemoryTraceRepository yang menyimpan 100 panggilan terakhir. Anda dapat mengubah ini dengan mengimplementasikan antarmuka ini sendiri dan menjadikannya tersedia sebagai Spring bean. Sebagai contoh untuk mencatat semua permintaan untuk log (dan masih menggunakan implementasi default sebagai penyimpanan dasar untuk melayani info pada /tracetitik akhir) Saya menggunakan implementasi semacam ini:

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);
  }
}

Ini traceInfopeta berisi informasi dasar tentang permintaan dan respon dalam jenis bentuk: {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}}}. Tidak ada konten tanggapan di sini.

SEDANG! Mencatat data POST

Anda dapat mengakses data POST dengan mengganti WebRequestTraceFilter , tetapi jangan berpikir itu adalah ide yang bagus (mis. Semua konten file yang diunggah akan masuk ke log) Berikut adalah contoh kode, tetapi jangan menggunakannya:

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;
  }
}
Piotr Chowaniec
sumber
1
Bagaimana dengan badan POST?
Pavel Vyazankin
@dart Saya sudah menambahkan contoh untuk Anda
Piotr Chowaniec
1
Saya melakukan sesuatu seperti ini, tetapi masalahnya adalah badan respon tidak tersedia TraceRepository, bagaimana kita bisa mengaksesnya?
Amir Pashazadeh
@AmirPashazadeh Anda harus mengganti protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)tetapi saya tidak yakin kapan filter ini dijalankan - mungkin pada tahap permintaan, jadi badan tanggapan tidak akan siap di sana.
Piotr Chowaniec
1
@Kekar ​​Sejak 2.0 ada HttpTraceRepository (bukan TraceRepository)
Piotr Chowaniec
12

Kode ini berfungsi untuk saya dalam aplikasi Boot Musim Semi - cukup daftarkan sebagai filter

    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();
            }

        }
    }
pengguna1817243
sumber
Ini bekerja dengan baik untuk logging respons - walaupun saya harus membatasi jumlah byte yang di-log-nya, jika tidak maka akan menghapus output konsol logging Intellij.
Adam
String getContent () {if (bos == null) {return String.format ("disebut% s terlalu dini", BufferedResponseWrapper.class.getCanonicalName ()); } byte [] bytes = bos.toByteArray (); mengembalikan String baru (Arrays.copyOf (bytes, 5000)) + "...."; }
Adam
Anda juga perlu memasukkan "log.isTraceEnabled ()" di sekitar pencatatan juga.
Adam
6
Apa yang akan keren adalah jika Java menambahkan beberapa metode default ke HttpServletResponse sehingga kita tidak perlu menulis implementasi yang begitu besar.
Adam
1
ditambah satu untuk memasukkan pernyataan impor
granadaCoder
8

Saat ini Spring Boot memiliki fitur Actuator untuk mendapatkan log permintaan dan tanggapan.

Tetapi Anda juga bisa mendapatkan log menggunakan Aspect (AOP).

Aspek menyediakan Anda dengan penjelasan seperti: @Before, @AfterReturning, @AfterThrowingdll

@Beforemencatat permintaan, @AfterReturningmencatat respons dan @AfterThrowingmencatat pesan kesalahan, Anda mungkin tidak memerlukan semua log titik akhir, sehingga Anda dapat menerapkan beberapa filter pada paket.

Berikut ini beberapa contohnya :

Untuk Permintaan:

@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) {
            }
        }
    }

Di sini @Before("within(your.package.where.endpoints.are..*)")ada jalur paket. Semua titik akhir dalam paket ini akan menghasilkan log.

Untuk Respon:

@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");
        }
    }

Di sini @AfterReturning("within(your.package.where.endpoints.are..*)")ada jalur paket. Semua titik akhir dalam paket ini akan menghasilkan log. Juga Object returnValueberisi respons.

Untuk Pengecualian:

@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());
    }
}

Di sini @AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e") ada jalur paket. Semua titik akhir dalam paket ini akan menghasilkan log. Juga Exception emengandung respons kesalahan.

Ini kode lengkapnya:

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());
        }
    }
}

Di sini, menggunakan @ConditionalOnExpression("${endpoint.aspect.enabled:true}")Anda dapat mengaktifkan / menonaktifkan log. tambahkan saja endpoint.aspect.enabled:trueke application.propertydan kontrol log

Info lebih lanjut tentang kunjungan AOP di sini:

Spring dok tentang AOP

Contoh artikel tentang AOP

Md. Sajedul Karim
sumber
1
new ObjectMapper()mahal, lebih baik bagikan satu mapper untuk semua
Sam
Ya, tentu. Ini adalah kode demo. Dalam produksi kita harus mengikuti praktik terbaik.
Md. Sajedul Karim
7

Di sini solusi saya (Spring 2.0.x)

Tambahkan ketergantungan maven:

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

Edit application.properties dan tambahkan baris berikut:

management.endpoints.web.exposure.include=* 

Setelah aplikasi booting musim semi dimulai, Anda dapat melacak permintaan 100 http terbaru dengan memanggil url ini: http: // localhost: 8070 / actuator / httptrace

Enrico Giurin
sumber
5

Anda juga dapat mengonfigurasi interseptor Spring khusus HandlerInterceptorAdapteruntuk implementasi yang disederhanakan dari interceptors pre-only / post-only:

@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
    }
}

Kemudian, Anda mendaftarkan pencegat sebanyak yang Anda inginkan:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    CustomHttpInterceptor customHttpInterceptor;

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

}

Catatan: sama seperti yang dinyatakan oleh @Robert , Anda perlu memperhatikan implementasi spesifik dan aplikasi Anda menggunakan. HttpServletRequestHttpServletResponse

Misalnya, untuk aplikasi yang menggunakan ShallowEtagHeaderFilter, implementasi respons akan menjadi ContentCachingResponseWrapper, jadi Anda harus:

@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";
        }
    }

}
bosco
sumber
4

@ hahn menjawab diperlukan sedikit modifikasi agar bisa bekerja untuk saya, tetapi sejauh ini adalah hal yang paling dapat disesuaikan yang bisa saya dapatkan.

Itu tidak bekerja untuk saya, mungkin karena saya juga memiliki HandlerInterceptorAdapter [??] tapi saya terus mendapat respons buruk dari server dalam versi itu. Ini modifikasi saya.

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);
    }
}
AA_PV
sumber
Apakah aplikasi Anda dikemas sebagai perang atau toples? Saya terus mendapatkan kesalahan java.io.FileNotFoundException: Tidak dapat membuka sumber daya ServletContext [/WEB-INF/loggingDispatcherServlet-servlet.xml]
Mayank Madhav
4

Jika seseorang masih membutuhkannya di sini adalah implementasi sederhana dengan Spring HttpTrace Actuator. Tetapi seperti yang mereka katakan di atas, ia tidak mencatat tubuh.

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()));
    }
}
Donz
sumber
4

Silakan merujuk ke tautan di bawah ini untuk jawaban aktual https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

Membuat beberapa perubahan dari solusi yang disebutkan di atas, permintaan dan tanggapan akan masuk konsol dan file juga jika tingkat logger adalah info. kita dapat mencetak di konsol atau file.

@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);
    }
}
} 

Output dalam File:

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}
Ramesh
sumber
1
Jawaban yang bagus, hanya saran untuk mengumpulkan semua output ke buffer dan login dalam satu pernyataan.
Mike
2

Jika Anda hanya melihat sebagian dari payload permintaan Anda, Anda perlu memanggil setMaxPayloadLengthfungsi karena defaultnya hanya menampilkan 50 karakter di badan permintaan Anda. Juga, menetapkan setIncludeHeaderske false adalah ide yang bagus jika Anda tidak ingin mencatat tajuk auth!

@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;
}
Chris Paika
sumber
Saya mencoba untuk menggunakannya di musim semi mvc dan itu tidak berfungsi untuk saya, diperlukan pengaturan tambahan selain mendaftar kacang ini dan menambahkan logger?
Noman Akhtar
1

jika Anda menggunakan Tomcat di aplikasi boot Anda di sini ada org.apache.catalina.filters.RequestDumperFilterdi jalur kelas untuk Anda. (tapi itu tidak akan memberi Anda "pengecualian di satu tempat").

Yura
sumber
1

kode yang ditempelkan di bawah berfungsi dengan pengujian saya dan dapat diunduh dari [proyek github] saya [1], dibagikan setelah menerapkan solusi berdasarkan pada itu pada proyek produksi.

@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;
        }

    }
}
Andrea Saba
sumber
0

Untuk mencatat semua permintaan dengan parameter input dan isi, kita dapat menggunakan filter dan interseptor . Tetapi saat menggunakan filter atau interseptor, kami tidak dapat mencetak badan permintaan beberapa kali. Cara yang lebih baik adalah kita bisa menggunakan spring-AOP. Dengan menggunakan ini kita dapat memisahkan mekanisme logging dari aplikasi. AOP dapat digunakan untuk mencatat Input dan output masing - masing metode dalam aplikasi.

Solusi saya adalah:

 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();
}

}

Rushikesh Kulkarni
sumber
0

Jika Anda telah mengonfigurasi server Spring boot Config, cukup aktifkan Debug logger untuk kelas:

Http11InputBuffer.Http11InputBuffer.java

Debug akan mencatat semua permintaan dan respons untuk setiap permintaan

Narendra
sumber
-1

Untuk mencatat permintaan yang menghasilkan hanya 400:

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);
        }
    }

}
Ihor M.
sumber