This sample application shows how cf-java-logging-support can be used in an application. It features a small Spring Boot application to showcase various features provided by the library. The application provides a REST API to trigger several actions. See section Features for details. You can deploy the sample application just with the library implementation using the default manifest.yml. Alternatively, you can choose to add instrumentation with the OpenTelemetry Java Agent and the Java Agent Extension provided by this library. See the section Adding the OpenTelemetry Java Agent for details.
This sample application is contained in the Maven module sample-spring-boot.
It can be built with a simple mvn install
.
A minimal CF application manifest is provided in manifest.yml.
This allows to deploy the newly built app with cf push
, provided a login to a CF instance.
This sample application contains several credentials to secure the access. The credentials should be changed, before the application is built and deployed.
Every Rest endpoint is secured with Basic Authentication.
The corresponding username and password are contained in the application.yml.
Please change the values of username
and password
in the following section of that file.
auth:
basic:
username: user
password: secret
This sample application uses a JKS keystore.
It is created by Maven, when it cannot be found in target/generated-resources/keystore/token_keystore.jks
The credentials to that keystore as well as the dname for the generated key can be configured as Maven properties in the pom.xml:
<properties>
<!-- other properties -->
<keystore.token.store_password>0bzhBRNUXBR5</keystore.token.store_password>
<keystore.token.key_password>0bzhBRNUXBR5</keystore.token.key_password>
<keystore.token.key_alias>jwt-token</keystore.token.key_alias>
<keystore.token.dname>CN=cf-java-logging-support, OU=None, O=SAP, L=Unknown, ST=Unknown, C=Unknown</keystore.token.dname>
</properties>
Note: Creating the keystore with Maven means, that it will be deleted by mvn clean
and a new key pair will be created with mvn install
.
This sample application can use both supported logging backends. By default it will use logback, which is configured by logback.xml.
Log4j2 can be chosen as backend by using the corresponding Maven profile:
mvn install -Plog4j2
The configuration file is log4j2.xml in that case.
This sample application offers a Rest API with several endpoints to show different features of cf-java-logging-support. A summary of the endpoints is given in the following table. Detailed descriptions are given in later sections.
Feature | Endpoint | Request Parameter |
---|---|---|
Generate Log Message | POST /log/{logger}/{logLevel}
|
|
Create JWT for dynamic log level | GET /token/{logLevel}
|
|
Get public key for JWT verification | GET /publickey |
This sample application allows the generation of log messages triggered by an HTTP request. Users can send a POST request containing the logger, the required log level and optionally the message to log. This allows testing the logging configuration with respect to packages and logging levels.
Example:
$ curl -X POST -u user:secret localhost:8080/log/test/info
Generated info log with message: "This is the default log message!".
{
"written_at":"2021-02-13T10:25:18.673Z",
"written_ts":1613211918673355000,
"tenant_id":"-",
"component_id":"-",
"component_name":"-",
"organization_name":"-",
"component_type":"application",
"space_name":"-",
"component_instance":"0",
"organization_id":"-",
"correlation_id":"81c759fd-4433-4d06-bddf-c5c30199c49b",
"space_id":"-",
"container_id":"-",
"tenant_subdomain":"-",
"type":"log",
"logger":"test",
"thread":"http-nio-8080-exec-2",
"level":"INFO",
"categories":[],
"msg":"This is the default log message!"
}
{
"written_at":"2021-02-13T10:25:18.676Z",
"written_ts":1613211918677807000,
"tenant_id":"-",
"component_id":"-",
"component_name":"-",
"organization_name":"-",
"component_type":"application",
"space_name":"-",
"component_instance":"0",
"organization_id":"-",
"correlation_id":"81c759fd-4433-4d06-bddf-c5c30199c49b",
"space_id":"-",
"container_id":"-",
"tenant_subdomain":"-",
"type":"request",
"request":"/log/test/info",
"referer":"-",
"response_sent_at":"2021-02-13T10:25:18.676Z",
"response_status":200,
"method":"POST",
"response_size_b":68,
"request_size_b":-1,
"remote_port":"redacted",
"layer":"[SERVLET]",
"remote_host":"redacted",
"x_forwarded_for":"-",
"remote_user":"redacted",
"protocol":"HTTP/1.1",
"remote_ip":"redacted",
"response_content_type":"text/plain;charset=ISO-8859-1",
"request_received_at":"2021-02-13T10:25:18.665Z",
"response_time_ms":10.78147,
"direction":"IN"
}
Note, that the request generates the default log message, that is also contained in the HTTP response and additionally a request log. Since this example was executed locally, the CF metadata is empty.
A custom message can be given as well:
$ curl -X POST -u user:secret 'localhost:8080/log/test/info?m=Hello+cf-java-logging-support!'
Generated info log with message: "Hello cf-java-logging-support!".
Note the quotes for bash and the HTTP encoding of the whitespace in the message.
This sample-application supports dynamic log levels with package support.
This feature is documented in detail in the project wiki.
It allows to change the log level by sending the HTTP header SAP_LOG_LEVEL
with an appropriate JWT.
Since it is not straight forward to generate such a token, there is an endpoint to create such a JWT.
The generated token needs to contain three things:
- the log level to apply
- the package names or prefixes to use
- an expiration timestamp
The log level is taken as path variable.
Since package names and expiration timestamp are optional, they can be specified as request parameters.
JWTs to be used with this sample application require a package name, but an empty string is allowed.
If no expiration timestamp is provided the application will take the current timestamp and add the default period as configure in defaults.token.expiration: P2D
in application.yml (two days by default).
You can change the JWT issuer with property defaults.token.issuer: your-issuer
in application.yml.
Example:
$ curl -u user:secret 'localhost:8080/token/DEBUG?p=com,org'
eyJraWQiOiJqd3QtdG9rZW4iLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsZXZlbCI6IkRFQlVHIiwiaXNzIjoic2FtcGxlLWFwcC1zcHJpbmctYm9vdCIsImV4cCI6MTYxMzM4Njg2MCwicGFja2FnZXMiOiJjb20sb3JnIiwiaWF0IjoxNjEzMjE0MDYwfQ.YtTk8VnMGN2i3SRLK8GRCDfgQcnAQL04IqojZdVYwEJFCezJsot20MYN-WpAWizVVV3midunnBrNzR3_dByK2gRzTQGGE9rXh4KpLw_4UM6XUJTgpMU8yzqt4pCBT-wpMbJ8UOKbT2RCdqOU1oWJL6rggxi5hGBItTvu0PZzjSG3Zv1eIvDKZcNF9pq4F1r8H2og1Mun28o1r-J5SCURjmolunKDNp4e6fsGSeUttbT5EulIcfHcM9nD4Byyywc2Khs0H13YPqAjdxxFcu_5fYp8JFgbns2Lo5PYjMbY8nxnuZFJmILwXHHRtAoxrcSbpSzFRtZfQsI4earGBGSyog
The response payload decodes to:
{
"level": "DEBUG",
"iss": "sample-app-spring-boot",
"exp": 1613386860,
"packages": "com,org",
"iat": 1613214060
}
Note: cf-java-logging-support requires the log level to be one of ERROR
, WARN
, INFO
, DEBUG
, TRACE
in all-caps.
The tokens created with the token endpoint can be used as HTTP headers on all endpoints of the application.
We use the token created in the previous section to post a new log message.
Note, that the package name prefixes org
and com
will trigger debug logs from many classes, especially Spring (org.springframework.*) and this libray (com.sap.*).
$ curl -X POST -u user:secret 'localhost:8080/log/test/info?m=Hello+cf-java-logging-support!' -H 'SAP-LOG-LEVEL: eyJraWQiOiJqd3QtdG9rZW4iLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsZXZlbCI6IkRFQlVHIiwiaXNzIjoic2FtcGxlLWFwcC1zcHJpbmctYm9vdCIsImV4cCI6MTYxMzM4Njg2MCwicGFja2FnZXMiOiJjb20sb3JnIiwiaWF0IjoxNjEzMjE0MDYwfQ.YtTk8VnMGN2i3SRLK8GRCDfgQcnAQL04IqojZdVYwEJFCezJsot20MYN-WpAWizVVV3midunnBrNzR3_dByK2gRzTQGGE9rXh4KpLw_4UM6XUJTgpMU8yzqt4pCBT-wpMbJ8UOKbT2RCdqOU1oWJL6rggxi5hGBItTvu0PZzjSG3Zv1eIvDKZcNF9pq4F1r8H2og1Mun28o1r-J5SCURjmolunKDNp4e6fsGSeUttbT5EulIcfHcM9nD4Byyywc2Khs0H13YPqAjdxxFcu_5fYp8JFgbns2Lo5PYjMbY8nxnuZFJmILwXHHRtAoxrcSbpSzFRtZfQsI4earGBGSyog'
Generated info log with message: "Hello cf-java-logging-support!".
{ "written_at":"2021-02-13T11:01:25.914Z","written_ts":1613214085914635000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"org.springframework.web.servlet.DispatcherServlet","thread":"http-nio-8080-exec-6","level":"DEBUG","categories":[],"msg":"POST \"/log/test/info?m=Hello+cf-java-logging-support!\", parameters={masked}" }
{ "written_at":"2021-02-13T11:01:25.915Z","written_ts":1613214085915196000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping","thread":"http-nio-8080-exec-6","level":"DEBUG","categories":[],"msg":"Mapped to com.sap.hcp.cf.logging.sample.springboot.controller.LogController#generateLog(String, String, String)" }
{ "written_at":"2021-02-13T11:01:25.915Z","written_ts":1613214085915814000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"test","thread":"http-nio-8080-exec-6","level":"INFO","categories":[],"msg":"Hello cf-java-logging-support!" }
{ "written_at":"2021-02-13T11:01:25.916Z","written_ts":1613214085916279000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor","thread":"http-nio-8080-exec-6","level":"DEBUG","categories":[],"msg":"Using 'text/plain', given [*/*] and supported [text/plain, */*, application/json, application/*+json]" }
{ "written_at":"2021-02-13T11:01:25.916Z","written_ts":1613214085916702000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor","thread":"http-nio-8080-exec-6","level":"DEBUG","categories":[],"msg":"Writing [\"Generated info log with message: \"Hello cf-java-logging-support!\".\"]" }
{ "written_at":"2021-02-13T11:01:25.917Z","written_ts":1613214085917217000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"org.springframework.security.web.header.writers.HstsHeaderWriter","thread":"http-nio-8080-exec-6","level":"DEBUG","categories":[],"msg":"Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@279e2b53" }
{ "written_at":"2021-02-13T11:01:25.917Z","written_ts":1613214085917528000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"org.springframework.security.web.context.HttpSessionSecurityContextRepository","thread":"http-nio-8080-exec-6","level":"DEBUG","categories":[],"msg":"SecurityContext 'org.springframework.security.core.context.SecurityContextImpl@442b5a9f: Authentication: org.springframework.security.authentication.UsernamePasswordAuthenticationToken@442b5a9f: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@b364: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: null; Granted Authorities: ROLE_USER' stored to HttpSession: 'org.apache.catalina.session.StandardSessionFacade@488b143c" }
{ "written_at":"2021-02-13T11:01:25.918Z","written_ts":1613214085918181000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"log","logger":"org.springframework.web.servlet.DispatcherServlet","thread":"http-nio-8080-exec-6","level":"DEBUG","categories":[],"msg":"Completed 200 OK" }
{ "written_at":"2021-02-13T11:01:25.918Z","written_ts":1613214085918645000,"tenant_id":"-","component_id":"-","component_name":"-","organization_name":"-","component_type":"application","space_name":"-","component_instance":"0","organization_id":"-","dynamic_log_level_prefixes":"com,org","correlation_id":"c7a92c5e-1e69-4ef9-98ad-8cca27accab9","space_id":"-","container_id":"-","dynamic_log_level":"DEBUG","tenant_subdomain":"-","type":"request","request":"/log/test/info?m=Hello+cf-java-logging-support!","referer":"-","response_sent_at":"2021-02-13T11:01:25.918Z","response_status":200,"method":"POST","response_size_b":66,"request_size_b":-1,"remote_port":"redacted","layer":"[SERVLET]","remote_host":"redacted","x_forwarded_for":"-","remote_user":"redacted","protocol":"HTTP/1.1","remote_ip":"redacted","response_content_type":"text/plain;charset=ISO-8859-1","request_received_at":"2021-02-13T11:01:25.914Z","response_time_ms":3.90267,"direction":"IN"}
As you can see there are now several debug messages from Spring, that are usually suppressed by the logging configuration but are now written due to the JWT configuration.
This sample application can be used as a JWT generator for applications in production. For the configuration of those application cf-java-logging-support needs the public key to validate the JWT signature. This public key can be obtained
$ curl -u user:secret 'localhost:8080/publickey'
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk/a5ZKvQ0voLi29klo5GCtN40JpscGud5diz2y5mNVCMvU6rdE+yNMs5zfgjy2PhR0TLXWfZrwHeX75dhC49hJup39pClv83FJWVbu6ScWNQUGYgdUY5zGkFcBayZlt1yXybQaCUtC8ksHe+QOAUW9Y43nPa8/vznH+zbROlF/kSHIjegcr0GF6ZOLMBAj+9p6Xp+kZxsFUgnqgIZWUp4YI3+j2xDuBgNptZbjUg7WsqEU/u+CA5uCyjGVriq++qSW1fj1A0C29uj1+n3IqrMlL2MdMQayS/5ppyrjApsSYDG56wQEAOrOKaSeBsZexIvIdhQ12+5SkqwPlQGCUHpQIDAQAB
Note: If this application is used this way you may want to fix the keystore and not accidentally delete it with mvn clean
.
You can add your keystore as src/main/resoures/token_keystore.jks
, which will override the automatically created keystore.
The sample application comes bundled with the OpenTelemetry Java Agent and the agent extension provided by this library. The Java agent allows additional auto-instrumentation for traces and metrics. It can also tap into logback or log4j to forward the logs via OpenTelemetry. The example manifest-otel-javaagent.yml shows, how to deploy this sample application with OpenTelemetry support enabled. Note, that the extension scans the application bindings for SAP Cloud Logging as the OpenTelemetry sink. The application needs to be bound to such a service instance.