Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/observability #33

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added modules/ROOT/images/show_deployed_services.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions modules/ROOT/images/structure_application.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 41 additions & 1 deletion modules/ROOT/pages/cross_cutting/exceptions.adoc
Original file line number Diff line number Diff line change
@@ -1,11 +1,51 @@
:imagesdir: ../images
= Exceptions

[NOTE]
====

This article needs further clarification. Help the project by contributing.

https://github.capgemini.com/CG-Europe-ABL/devonfw/issues/16

====

== Business exceptions

=== Use a common base class

* All business exceptions should inherit a common abstract base class so they can be distinguished from technical exceptions.
** Should be inheriting `RuntimeException`
** Should store the cause `Exception`
** Should contain a reason enum. The enum is project individual and contains values like `Conflict`, `NotFound` and `IncorrectInput`. This enum can later be used to decide how to inform the user about the problem e.g. via HTTP status codes when building REST apis.
** Should contain a reason enum. The enum is project individual and contains values like `Conflict`, `NotFound` and `IncorrectInput`. This enum can later be used to decide how to inform the user about the problem e.g. via HTTP status codes when building REST apis.


== Exception handler
* the central entry point for exceptions in the application
* is annotated with `@ExceptionHandler` at each method which handles the exceptions
* each method has a return value of `ResponseEntity<ErrorResponse>`
* within the method, the values for HTTPStatus, ErrorCode, Message and UUID are set
* the class is globally annotated with `@ControllerAdvice`
* for each type of Exception there should be a handler method to managet the exception

Example code exception handler:
[source,java]
----
@ControllerAdvice
public class CustomControllerAdvice {

@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(Exception ex) {
...

----

== ErrorResponse class
* `ErrorResponse`: The ErrorResponse class returns the error object to the client.
* mainly consists of constructors with a different number of parameters that provides the information about the class variables (message, HTTPStatus code etc.).

== More information

https://www.tutorialspoint.com/spring_boot/spring_boot_exception_handling.htm[Spring Boot - Exception Handling (Tutorial),window=_blank]

https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc[Official Documentation: Spring Boot Exception Handling, window=_blank]
139 changes: 137 additions & 2 deletions modules/ROOT/pages/cross_cutting/tracing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,142 @@

== Correlation ID

Most of the time it is handy to find all the log messages created as a result of a user action, service request or message. To achieve this it is best practice to a a correlation ID to every message. A correlation ID is a number or string that is the same for all log messages of a user action, service request or message but different for different user actions, service requests or messages. With that it is possible to filter the log messages. This uniqueness can be achieved by using a `UUID` as correlation ID.
Most of the time it is handy to find all the log messages created as a result of a user action, service request or message. To achieve this it is best practice to add a correlation ID to every message. A correlation ID is a number or string that is the same for all log messages of a user action, service request or message but different for different user actions, service requests or messages. With that it is possible to filter the log messages. This uniqueness can be achieved by using a `UUID` as correlation ID.

The correlation ID is generated when a new user action, service request or message occurs. In distributed systems it is important to pass the correlation ID to called service to be able to find the log messages for the complete system.
For HTTP calls this is usually done in the header field `X-Correlation-Id`. For messaging systems like queues or service busses there is usually a special file in the message header to store the correlation ID.
For HTTP calls this is usually done in the header field `X-Correlation-Id`. For messaging systems like queues or service busses there is usually a special file in the message header to store the correlation ID.

== Observability

=== How to set up an observability stack with Grafana, Prometheus, Tempo and Loki

This short guide shows how to build an observability stack using Grafana, Prometheus, Tempo and Loki.

*The prerequisite* is a spring boot app that is previously containerized with docker.

You also need Kubernetes installed, which comes with Rancher Desktop.

Helm should also be installed.

==== Overview
The following graphic shows how a Grafana stack is usually built up. Several services are used for this.


image:grafana_stack_overview smaller.jpg["Overview Grafana-stack",scalewidth="80%",align="center"]

Build up the following folder-structure in your application-project:

[subs=+macros]
----
├──/project-folder
| ├──/kubernetes
| ├─────/loki
| | └──/loki.yaml
| ├─────/prometheus-grafana
| | └──/chart.yaml
| | └──/values.yaml
| ├─────/promtrail
| | └──/promtrail.yaml
| ├─────/springboot-app
| | └──/springboot-app.yaml
| ├─────/tempo
| | └──/tempo.yaml
| └──/springboot-app
| ├──/source
| | └──/main
| | └──/test
| └──...

----

We add the charts we need for our services and update the Helm repository:

[source,shell]
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update

After we have done this, we will create a namespace for the observability tools. To do so, go to the shell and type in:
[source,shell]
kubectl create ns observability

==== Install Promtail and Loki

To install Promtail, run:
[source,shell]
cd promtail
helm upgrade --install promtail grafana/promtail -n observability -f promtail.yaml

Deploy Loki inside the cluster:
[source,shell]
helm upgrade --install loki grafana/loki-distributed -n observability

We are interested in the `loki-loki-distributed-gateway` service by Loki in order to get the logs send. `Grafana` also needs that as a datasource.

==== Installing Tempo

To install Tempo as a prerequisite we need to install minio. This is a tool that helps us to simulate an AWS Object Storage s3 service.

[source,shell]
cd ../tempo
kubectl apply -f minio.yaml

To deploy Tempo, run:
[source,shell]
helm upgrade --install tempo grafana/tempo-distributed -n observability -f tempo.yaml

==== Install Prometheus and Grafana

[source,shell]
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

after that, run:
[source,shell]
cd ../prometheus-grafana
helm dependency update
helm upgrade --install kube-prometheus-stack -n observability .

==== checking the deployments
The following command

[source,shell]
helm ls -n observability

will show us this

image::show_deployed_services.png["list with deployed services kubernetes",scaledwidth="80%",align="center"]

[source,shell]
kubectl get svc -n observability

will give us a list with all deployed services on our created namespace which we called `observability`.

image::kubectl_get-svc-namespace.gif["list all services in kubernetes namespace",scalewidth="85%",align="center"]

After done this, we have to deploy our Springboot-Application on Kubernetes. For that, go to the springboot-app/ directory and run the kubectl command:
[source,shell]
cd ../springboot-app
kubectl apply -f springboot-app.yaml

NOTE: You can name the yaml-file anything you like, e.g.: deployment.yaml

After successul deployment on Kubernetes, point out the external ip-address, on which the springboot-app run with:
[source,shell]
kubectl get deploy,svc,cm -l app=springboot-app

and obtain out the port for Grafana running:
[source,shell]
kubectl get svc -n observability

==== Testing the endpoints

The Springboot-Application can be tested via the URL:
http://EXTERNAL-IP:8080/ENDPOINT

Grafana should be accessed with:
http://EXTERNAL-IP:PORT-FOR-GRAFANA


== References
* link:https://opentelemetry.io/docs/instrumentation/java/[OpenTelemetry with Java]
* link:https://grafana.com/docs/[Grafana Docs]
67 changes: 67 additions & 0 deletions modules/ROOT/pages/integration/rest_exception_handling.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
= RESTful Exception Handling
// https://github.capgemini.com/CG-Europe-ABL/devonfw/issues/16

[NOTE]
====

This article needs further clarification. Help the project by contribution.

https://github.capgemini.com/CG-Europe-ABL/devonfw/issues/16

====

This article gives advice on a proper exception handling in the service layer.
It is important to catch all exceptions and map them to a meaningful result and not pass implementation details to the outside.
Expand Down Expand Up @@ -118,5 +128,62 @@ E.g.: adding a property that is false on default and when set to true the detail

|===

== Concrete Steps

=== Library

* Use Jakarta EE ExceptionMapper
* Add a provider for spring boot? Or also for quarkus?

=== Identify exceptions of groups

* If exceptionmapper can handle mutliple specific exceptions,
** then add one exception handler per group that has a specific handling
* ELSE
** use a single exceptionmapper to catch everything.
** Create group specific handler that contain a handling method and a list of exception classes
** The exceptionmapper needs to identify (inject) all possible handlers and check for their ability to handle certain classes (performance?)
** It then delegates the exceptions dynamically

=== Create the response

* Create response factory that can create responses based on multiple input values.

== Examples

=== The structure of exceptions

*The structure of exceptions can be described as follows:*

First of all, this following picture shows a general structure of an application with exceptions.

[#application-structure]
image::structure_application.svg["structure-application",scaledwidth="80%",align="center"]


* specific exception classes are derived from the ApplicationException class - such as BusinessException, ValidationException or SecurityException.

* `ErrorCode` itself is defined as an enum and contains a set of internal constants that are mapped to HttpStatus codes (like 400, 404, or 500 etc.).

==== Business Exception
* HTTP status code depends on the reason of the exception. Default: 400 (BAD REQUEST)
* Response Body consists of: a message, ErrorCode (a specific code) and UUID
* is thrown when a business rule within our application is violated (e.g. error, a compliance failure or if a requested data was not found - 404)

==== Technical Exception
* HTTP status code is 500 (Internal Server Error)
* Response Body consists of: a message, ErrorCode (a specific code) and UUID
* is thrown when something goes wrong
* technical exception is usually derived from Java’s RuntimeException
* Example: Java’s built-in `IllegalArgumentException`

=== Example application

[NOTE]
====
In this article, we need to add a link to the example-application that clarifies the workflow of exception-handling

====
// TODO: add the link to the example-application in github

// TODO: Provide an example using all four exception types and an exception mapper