{"id":831,"date":"2020-03-30T05:00:00","date_gmt":"2020-03-30T05:00:00","guid":{"rendered":"http:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-5-a2d164711554\/"},"modified":"2021-02-04T01:47:26","modified_gmt":"2021-02-04T01:47:26","slug":"simple-spring-boot-service-to-kubernetes-application-step-5-a2d164711554","status":"publish","type":"post","link":"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-5-a2d164711554\/","title":{"rendered":"Logging, Tracing and Error Handling"},"content":{"rendered":"\n<p class=\"graf graf--p graf-after--h3\" id=\"bc83\">Now that we\u2019ve got an almost fully functional webservice we\u2019re going to make sure that we can debug it properly before moving forward to deployment. In this article we\u2019re going to describe how to setup logging.<\/p>\n\n\n\n<h3 class=\"graf graf--h3 graf-after--p wp-block-heading\" id=\"647e\">Logging<\/h3>\n\n\n\n<p class=\"graf graf--p graf-after--h3\" id=\"baa9\">We need to start by actually logging something. Spring boot comes packaged with aop and aspectj and we\u2019ll be using these libraries to setup automatic logging around the inputs and outputs of every method.<\/p>\n\n\n\n<h4 class=\"graf graf--h4 graf-after--p wp-block-heading\" id=\"58e7\">Create the AspectJ Configuration<\/h4>\n\n\n\n<p class=\"graf graf--p graf-after--h4\" id=\"d73b\">In the source code base create a java class called <code class=\"markup--code markup--p-code\">LoggingApect <\/code>in <code class=\"markup--code markup--p-code\">com.brianrook.medium.customer.config<\/code>. Add this content:<\/p>\n\n\n\n<pre id=\"0afd\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>package com.brianrook.medium.customer.config;\n\nimport com.fasterxml.jackson.core.JsonProcessingException;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.ObjectWriter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.StopWatch;\n\nimport java.io.Writer;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@Aspect\n@Component\n@Slf4j\npublic class LoggingAspect {\n\n    ObjectMapper om = new ObjectMapper();\n\n    @Around(\"execution(* com.brianrook.medium.customer..*(..)))\")\n    public Object profileAllMethods(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {\n        logMethodInvocationAndParameters(proceedingJoinPoint);\n\n        final StopWatch stopWatch = new StopWatch();\n\n        \/\/Measure method execution time\n        stopWatch.start();\n        Object result = proceedingJoinPoint.proceed();\n        stopWatch.stop();\n\n        \/\/Log method execution time\n        logMethodResultAndParameters(proceedingJoinPoint, result, stopWatch.getTotalTimeMillis());\n\n        return result;\n    }\n\n    private void logMethodResultAndParameters(ProceedingJoinPoint proceedingJoinPoint,\n                                              Object result, long totalTimeMillis) {\n        try {\n            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();\n            String className = methodSignature.getDeclaringType().getSimpleName();\n            String methodName = methodSignature.getName();\n            ObjectWriter writer = om.writer();\n<em class=\"markup--em markup--pre-em\">log<\/em>.info(\"&lt;- {}.{} returns:{}.  Execution time: {}ms\",\n                    className,\n                    methodName,\n                    writer.writeValueAsString(result),\n                    totalTimeMillis);\n        } catch (JsonProcessingException e) {\n<em class=\"markup--em markup--pre-em\">log<\/em>.error(\"unable to write log value: {}\", e.getMessage(), e);\n        }\n    }\n\n\n    private void logMethodInvocationAndParameters(ProceedingJoinPoint jp) {\n        try {\n            String&#91;] argNames = ((MethodSignature) jp.getSignature()).getParameterNames();\n            Object&#91;] values = jp.getArgs();\n            Map&lt;String, Object&gt; params = new HashMap&lt;&gt;();\n            if (argNames!=null &amp;&amp; argNames.length != 0) {\n                for (int i = 0; i &lt; argNames.length; i++) {\n                    params.put(argNames&#91;i], values&#91;i]);\n                }\n            }\n            ObjectWriter writer = om.writer();\n            String className = jp.getSignature().getDeclaringType().getSimpleName();\n            String methodName = jp.getSignature().getName();\n<em class=\"markup--em markup--pre-em\">log<\/em>.info(\"-&gt; {}.{} invocation.  params: {}\",\n                    className,\n                    methodName,\n                    writer.writeValueAsString(params));\n        } catch (JsonProcessingException e) {\n<em class=\"markup--em markup--pre-em\">log<\/em>.error(\"unable to write log value: {}\", e.getMessage(), e);\n        }\n\n    }\n}<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote graf graf--blockquote graf-after--pre is-layout-flow wp-block-quote-is-layout-flow\" id=\"3611\"><p>This code is leveraging aspectj and spring to intercept every method call and execute some code that we\u2019ve defined. Since spring is creating interface proxies for you behind the scenes this interception is very easy. Here we\u2019re logging the method call, the method parameters, the returned result as well as an execution time for profiling. We\u2019ve currently got it turned on for every method in our project that we\u2019ve created. However, we could easily create an annotation and use that in a filter as well.<\/p><\/blockquote>\n\n\n\n<p class=\"graf graf--p graf-after--blockquote\" id=\"cbf1\">If we start this up in the IDE and hit the endpoint again with postman, we should see logging that looks like this:<\/p>\n\n\n\n<pre id=\"3e42\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>2020-03-29 12:10:18.871  INFO &#91;customer,76d8b46086af4970,76d8b46086af4970,false] 7004 --- &#91;io-10000-exec-2] c.b.m.customer.config.LoggingAspect      : -&gt; CustomerController.saveCustomer invocation.  params: {\"customerDTO\":{\"customerId\":null,\"firstName\":\"Brian\",\"lastName\":\"Rook\",\"phoneNumber\":\"(303)555-1212\",\"email\":\"<a class=\"markup--anchor markup--pre-anchor\" href=\"mailto:testuser@gmail.com\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"mailto:testuser@gmail.com\">testuser@gmail.com<\/a>\"}}\n2020-03-29 12:10:18.881  INFO &#91;customer,76d8b46086af4970,76d8b46086af4970,false] 7004 --- &#91;io-10000-exec-2] c.b.m.customer.config.LoggingAspect      : -&gt; CustomerService.saveCustomer invocation.  params: {\"customer\":{\"customerId\":null,\"firstName\":\"Brian\",\"lastName\":\"Rook\",\"phoneNumber\":\"(303)555-1212\",\"email\":\"<a class=\"markup--anchor markup--pre-anchor\" href=\"mailto:testuser@gmail.com\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"mailto:testuser@gmail.com\">testuser@gmail.com<\/a>\"}}\n2020-03-29 12:10:18.888  INFO &#91;customer,76d8b46086af4970,76d8b46086af4970,false] 7004 --- &#91;io-10000-exec-2] c.b.m.customer.config.LoggingAspect      : -&gt; CrudRepository.save invocation.  params: {}\n2020-03-29 12:10:18.943  INFO &#91;customer,76d8b46086af4970,76d8b46086af4970,false] 7004 --- &#91;io-10000-exec-2] c.b.m.customer.config.LoggingAspect      : &lt;- CrudRepository.save returns:{\"customerId\":1,\"firstName\":\"Brian\",\"lastName\":\"Rook\",\"phoneNumber\":\"(303)555-1212\",\"email\":\"<a class=\"markup--anchor markup--pre-anchor\" href=\"mailto:testuser@gmail.com\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"mailto:testuser@gmail.com\">testuser@gmail.com<\/a>\"}.  Execution time: 51ms\n2020-03-29 12:10:18.943  INFO &#91;customer,76d8b46086af4970,76d8b46086af4970,false] 7004 --- &#91;io-10000-exec-2] c.b.m.customer.config.LoggingAspect      : &lt;- CustomerService.saveCustomer returns:{\"customerId\":1,\"firstName\":\"Brian\",\"lastName\":\"Rook\",\"phoneNumber\":\"(303)555-1212\",\"email\":\"<a class=\"markup--anchor markup--pre-anchor\" href=\"mailto:testuser@gmail.com\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"mailto:testuser@gmail.com\">testuser@gmail.com<\/a>\"}.  Execution time: 61ms\n2020-03-29 12:10:18.951  INFO &#91;customer,76d8b46086af4970,76d8b46086af4970,false] 7004 --- &#91;io-10000-exec-2] c.b.m.customer.config.LoggingAspect      : &lt;- CustomerController.saveCustomer returns:{\"headers\":{},\"body\":{\"customerId\":1,\"firstName\":\"Brian\",\"lastName\":\"Rook\",\"phoneNumber\":\"(303)555-1212\",\"email\":\"<a class=\"markup--anchor markup--pre-anchor\" href=\"mailto:testuser@gmail.com\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"mailto:testuser@gmail.com\">testuser@gmail.com<\/a>\"},\"statusCode\":\"CREATED\",\"statusCodeValue\":201}.  Execution time: 72ms<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"6a27\">Now we can search and filter the logs when we get into production. You can see that sleuth and zipkin are turned on because the zipkin trace and span ids are present in the logs:<\/p>\n\n\n\n<pre id=\"0735\" class=\"wp-block-preformatted graf graf--pre graf-after--p\">[customer,76d8b46086af4970,76d8b46086af4970,false]<\/pre>\n\n\n\n<h4 class=\"graf graf--h4 graf-after--pre wp-block-heading\" id=\"7b48\">Access Logs<\/h4>\n\n\n\n<p class=\"graf graf--p graf-after--h4\" id=\"3a9e\">We also want to emit the access log from tomcat. This is a configuration setting in application.yaml<\/p>\n\n\n\n<pre id=\"b258\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>server:\n  port: 10000\n  tomcat:\n  accesslog:\n    enabled: true\n    pattern: '%h %l %u %t \\\"%r\\\" %s %b zipkin:&#91;%{X-B3-TraceId}i, %{X-B3-SpanId}i, %{X-B3-ParentSpanId}i]'\n    directory: c:\\workspace\\logs\\customer\n    prefix: access_log\n    suffix: .log\n    buffered: false<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"7e8e\">we\u2019re going to write out the zipkin trace and span ids into the logs as well, so we\u2019ll need to do some configuration into the tomcat access log writer to write the zipkin data into the headers.<\/p>\n\n\n\n<p class=\"graf graf--p graf-after--p\" id=\"991e\">in <code class=\"markup--code markup--p-code\">com.brianrook.medium.customer.config<\/code> create 2 files.<\/p>\n\n\n\n<p class=\"graf graf--p graf-after--p\" id=\"c182\">First <code class=\"markup--code markup--p-code\">SleuthValve<\/code><\/p>\n\n\n\n<pre id=\"536a\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>package com.brianrook.medium.customer.config;\n\nimport brave.Span;\nimport brave.Tracer;\nimport org.apache.catalina.Valve;\nimport org.apache.catalina.connector.Request;\nimport org.apache.catalina.connector.Response;\nimport org.apache.catalina.valves.ValveBase;\nimport org.apache.tomcat.util.buf.MessageBytes;\nimport org.apache.tomcat.util.http.MimeHeaders;\nimport org.springframework.stereotype.Component;\n\nimport javax.servlet.ServletException;\nimport java.io.IOException;\n\n@Component\nclass SleuthValve extends ValveBase {\n    private final Tracer tracer;\n\n    public SleuthValve(Tracer tracer){\n        this.tracer = tracer;\n    }\n\n    @Override\n    public void invoke(Request request, Response response) throws IOException, ServletException {\n\n<em class=\"markup--em markup--pre-em\">enrichWithSleuthHeaderWhenMissing<\/em>(tracer, request);\n\n        Valve next = getNext();\n        if (null == next) {\n            return;\n        }\n        next.invoke(request, response);\n    }\n\n    private static void enrichWithSleuthHeaderWhenMissing(final Tracer tracer, final Request request) {\n        String header = request.getHeader(\"X-B3-TraceId\");\n        if (null == header) {\n\n            org.apache.coyote.Request coyoteRequest = request.getCoyoteRequest();\n            MimeHeaders mimeHeaders = coyoteRequest.getMimeHeaders();\n\n            Span span = tracer.newTrace();\n\n<em class=\"markup--em markup--pre-em\">addHeader<\/em>(mimeHeaders, \"X-B3-TraceId\", span.context().traceIdString());\n<em class=\"markup--em markup--pre-em\">addHeader<\/em>(mimeHeaders, \"X-B3-SpanId\", span.context().traceIdString());\n        }\n    }\n\n    private static void addHeader(MimeHeaders mimeHeaders, String traceIdName, String value) {\n        MessageBytes messageBytes = mimeHeaders.addValue(traceIdName);\n        messageBytes.setString(value);\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"35c8\">and then AccessLogSleuthConfiguration<\/p>\n\n\n\n<pre id=\"07bb\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>package com.brianrook.medium.customer.config;\n\nimport org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;\nimport org.springframework.boot.web.server.WebServerFactoryCustomizer;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\npublic class AccessLogSleuthConfiguration implements WebServerFactoryCustomizer&lt;TomcatServletWebServerFactory&gt; {\n\n    private SleuthValve sleuthValve;\n    public AccessLogSleuthConfiguration(SleuthValve sleuthValve) {\n        this.sleuthValve = sleuthValve;\n    }\n    @Override\n    public void customize(TomcatServletWebServerFactory factory) {\n        factory.addContextCustomizers(context -&gt; context.getPipeline().addValve(sleuthValve));\n    }\n\n}<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"27ac\">Additionally, we can retry the request with the same parameters to throw an error:<\/p>\n\n\n\n<pre id=\"12b7\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>2020-03-29 18:02:54.124  INFO &#91;customer,2f492a51cbdc93dd,2f492a51cbdc93dd,false] 21388 --- &#91;io-10000-exec-5] c.b.m.customer.config.LoggingAspect      : -&gt; CustomerController.saveCustomer invocation.  params: {\"customerDTO\":{\"customerId\":null,\"firstName\":\"Brian\",\"lastName\":\"Rook\",\"phoneNumber\":\"(303)555-1234\",\"email\":\"<a class=\"markup--anchor markup--pre-anchor\" href=\"mailto:testuser@test.com\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"mailto:testuser@test.com\">testuser@test.com<\/a>\"}}\n2020-03-29 18:02:54.124  INFO &#91;customer,2f492a51cbdc93dd,2f492a51cbdc93dd,false] 21388 --- &#91;io-10000-exec-5] c.b.m.customer.config.LoggingAspect      : -&gt; CustomerService.saveCustomer invocation.  params: {\"customer\":{\"customerId\":null,\"firstName\":\"Brian\",\"lastName\":\"Rook\",\"phoneNumber\":\"(303)555-1234\",\"email\":\"<a class=\"markup--anchor markup--pre-anchor\" href=\"mailto:testuser@test.com\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"mailto:testuser@test.com\">testuser@test.com<\/a>\"}}\n2020-03-29 18:02:54.124  INFO &#91;customer,2f492a51cbdc93dd,2f492a51cbdc93dd,false] 21388 --- &#91;io-10000-exec-5] c.b.m.customer.config.LoggingAspect      : -&gt; CrudRepository.save invocation.  params: {}\n2020-03-29 18:02:54.128  WARN &#91;customer,2f492a51cbdc93dd,2f492a51cbdc93dd,false] 21388 --- &#91;io-10000-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 23505, SQLState: 23505\n2020-03-29 18:02:54.129 ERROR &#91;customer,2f492a51cbdc93dd,2f492a51cbdc93dd,false] 21388 --- &#91;io-10000-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : Unique index or primary key violation: \"PUBLIC.UK_DWK6CX0AFU8BS9O4T536V1J5V_INDEX_5 ON PUBLIC.CUSTOMER(EMAIL) VALUES 1\"; SQL statement:\ninsert into customer (email, first_name, last_name, phone_number, customer_id) values (?, ?, ?, ?, ?) &#91;23505-200]\n2020-03-29 18:02:54.146 ERROR &#91;customer,2f492a51cbdc93dd,2f492a51cbdc93dd,false] 21388 --- &#91;io-10000-exec-5] o.s.c.s.i.web.ExceptionLoggingFilter     : Uncaught exception thrown<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"0ee9\">You can see that the span and trace id are different.<\/p>\n\n\n\n<p class=\"graf graf--p graf-after--p\" id=\"b42e\">You should also see the request in the access logs (and the trace\/spans line up):<\/p>\n\n\n\n<pre id=\"982e\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>0:0:0:0:0:0:0:1 - - &#91;29\/Mar\/2020:18:02:54 -0600] \\\"POST \/customer\/ HTTP\/1.1\\\" 500 21342 zipkin:&#91;2f492a51cbdc93dd, 2f492a51cbdc93dd, -]<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre graf--trailing\" id=\"959f\">However, our response in postman is fairly ugly. Lets see if we can clean that up now.<\/p>\n\n\n\n<h3 class=\"graf graf--h3 graf--leading wp-block-heading\" id=\"395e\">Error Handling<\/h3>\n\n\n\n<p class=\"graf graf--p graf-after--h3\" id=\"27e7\">For now we\u2019re just going to throw out some standard errors, but we could expand this simple implementation in the future.<\/p>\n\n\n\n<p class=\"graf graf--p graf-after--p\" id=\"dbe6\">In <code class=\"markup--code markup--p-code\">com.brianrook.medium.customer.controller<\/code> create this <code class=\"markup--code markup--p-code\">CustomerControllerAdvice <\/code>java class:<\/p>\n\n\n\n<pre id=\"6f32\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>package com.brianrook.medium.customer.controller;\n\nimport com.brianrook.medium.customer.exception.CustomerSystemException;\nimport org.springframework.http.HttpHeaders;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.context.request.WebRequest;\nimport org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;\n\n@ControllerAdvice\npublic class CustomerControllerAdvice extends ResponseEntityExceptionHandler {\n\n    @ExceptionHandler(value\n            = {IllegalArgumentException.class})\n    protected ResponseEntity&lt;Object&gt; handleConflict(\n            RuntimeException ex, WebRequest request) {\n        String bodyOfResponse = \"Unable to process request because of invalid input data\";\n        return handleExceptionInternal(ex, bodyOfResponse,\n                new HttpHeaders(), HttpStatus.<em class=\"markup--em markup--pre-em\">BAD_REQUEST<\/em>, request);\n    }\n\n    @ExceptionHandler(value\n            = {CustomerSystemException.class})\n    protected ResponseEntity&lt;Object&gt; handleSystemException(\n            RuntimeException ex, WebRequest request) {\n        String bodyOfResponse = String.<em class=\"markup--em markup--pre-em\">format<\/em>(\"Unable to process request because of system exception.\");\n        return handleExceptionInternal(ex, bodyOfResponse,\n                new HttpHeaders(), HttpStatus.<em class=\"markup--em markup--pre-em\">INTERNAL_SERVER_ERROR<\/em>, request);\n    }\n\n}<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"30c8\">We also need to make the exception <code class=\"markup--code markup--p-code\">com.brianrook.medium.customer.exception.CustomerSystemException<\/code><\/p>\n\n\n\n<pre id=\"33f6\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>package com.brianrook.medium.customer.exception;\n\npublic class CustomerSystemException extends RuntimeException{\n    public CustomerSystemException(String message){\n        super(message);\n    }\n    public CustomerSystemException(String message, Exception e){\n        super(message,e);\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"ecc9\">and update our service class to catch the database exception and throw a system exception instead:<\/p>\n\n\n\n<pre id=\"113e\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>public Customer persistCustomer(Customer customer) {\n    try {\n        CustomerEntity customerEntity = CustomerEntityMapper.<em class=\"markup--em markup--pre-em\">INSTANCE<\/em>.customerToCustomerEntity(customer);\n        CustomerEntity storedEntity = customerDAO.save(customerEntity);\n        Customer returnCustomer = CustomerEntityMapper.<em class=\"markup--em markup--pre-em\">INSTANCE<\/em>.customerEntityToCustomer(storedEntity);\n        return returnCustomer;\n    }catch (Exception e){\n        throw new CustomerSystemException(\"unable to persist customer data: \"+e.getMessage(), e);\n    }\n}<\/code><\/pre>\n\n\n\n<p class=\"graf graf--p graf-after--pre\" id=\"c88a\">Finally we need to exclude the advice class from the logging aspect<\/p>\n\n\n\n<pre id=\"1f35\" class=\"wp-block-code graf graf--pre graf-after--p\"><code>@Around(\"execution(* com.brianrook.medium.customer..*(..)) \" +\n        \"&amp;&amp; !within(com.brianrook.medium.customer.config. .*)\" +\n        \"&amp;&amp; !within(com.brianrook.medium.customer.controller.CustomerControllerAdvice)) \")<\/code><\/pre>\n\n\n\n<h3 class=\"graf graf--h3 graf-after--pre wp-block-heading\" id=\"f0bd\">Build and&nbsp;Commit<\/h3>\n\n\n\n<pre id=\"4568\" class=\"wp-block-code graf graf--pre graf-after--h3\"><code>git checkout -b logging\nmvn clean install\ngit add .\ngit commit -m \"logging and error handling\"\ngit push\ngit checkout master\ngit merge logging<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<div class=\"entry-summary\">\nNow that we\u2019ve got an almost fully functional webservice we\u2019re going to&hellip;\n<\/div>\n<div class=\"link-more\"><a href=\"https:\/\/bullyrooks.com\/index.php\/2020\/03\/30\/simple-spring-boot-service-to-kubernetes-application-step-5-a2d164711554\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &ldquo;Logging, Tracing and Error Handling&rdquo;<\/span>&hellip;<\/a><\/div>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[41],"tags":[63,62,67,66,29,61,50,42,43,65,64],"course":[40],"class_list":["post-831","post","type-post","status-publish","format-standard","hentry","category-software-development","tag-access-logs","tag-aspectj","tag-error-handling","tag-exception","tag-git","tag-logging","tag-maven","tag-spring","tag-spring-boot","tag-tracing","tag-zipkin","course-spring-with-kubernetes","entry"],"jetpack_featured_media_url":"","jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/831","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/comments?post=831"}],"version-history":[{"count":3,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/831\/revisions"}],"predecessor-version":[{"id":885,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/posts\/831\/revisions\/885"}],"wp:attachment":[{"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/media?parent=831"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/categories?post=831"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/tags?post=831"},{"taxonomy":"course","embeddable":true,"href":"https:\/\/bullyrooks.com\/index.php\/wp-json\/wp\/v2\/course?post=831"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}