|
| 1 | +# Spring Boot Sample Application |
| 2 | + |
| 3 | +This sample application shows how cf-java-logging-support can be used in an application. |
| 4 | +It features a small Spring Boot application to showcase various features provided by the library. |
| 5 | +The application provides a Rest API to trigger several actions. |
| 6 | +See section [Features](#features) for details. |
| 7 | + |
| 8 | +## Getting Started |
| 9 | + |
| 10 | +This sample application is contained in the Maven module sample-spring-boot. |
| 11 | +It can be built with a simple `mvn install`. |
| 12 | +A minimal CF application manifest is provided in [manifest.yml.](manifest.yml) |
| 13 | +This allows to deploy the newly built app with `cf push`, provided a login to a CF instance. |
| 14 | + |
| 15 | +### Changing the Credentials |
| 16 | + |
| 17 | +This sample application contains several credentials to secure the access. |
| 18 | +The credentials should be changed, before the application is built and deployed. |
| 19 | + |
| 20 | +#### Basic Authentication |
| 21 | + |
| 22 | +Every Rest endpoint is secured with Basic Authentication. |
| 23 | +The corresponding username and password are contained in the [application.yml](src/main/resources/application.yml). |
| 24 | +Please change the values of `username` and `password` in the following section of that file. |
| 25 | + |
| 26 | +```yaml |
| 27 | +auth: |
| 28 | + basic: |
| 29 | + username: user |
| 30 | + password: secret |
| 31 | +``` |
| 32 | +
|
| 33 | +#### Keystore Configuration |
| 34 | +
|
| 35 | +This sample application uses a JKS keystore. |
| 36 | +It is created by Maven, when it cannot be found in `target/generated-resources/keystore/token_keystore.jks` |
| 37 | +The credentials to that keystore as well as the dname for the generated key can be configured as Maven properties in the [pom.xml](pom.xml): |
| 38 | + |
| 39 | +```xml |
| 40 | +<properties> |
| 41 | + <!-- other properties --> |
| 42 | + <keystore.token.store_password>0bzhBRNUXBR5</keystore.token.store_password> |
| 43 | + <keystore.token.key_password>0bzhBRNUXBR5</keystore.token.key_password> |
| 44 | + <keystore.token.key_alias>jwt-token</keystore.token.key_alias> |
| 45 | + <keystore.token.dname>CN=cf-java-logging-support, OU=None, O=SAP, L=Unknown, ST=Unknown, C=Unknown</keystore.token.dname> |
| 46 | +</properties> |
| 47 | +``` |
| 48 | + |
| 49 | +**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`. |
| 50 | + |
| 51 | +### Changing the Logging Backend |
| 52 | + |
| 53 | +This sample application can use both supported logging backends. |
| 54 | +By default it will use logback, which is configured by [logback.xml](src/main/resources/logback.xml). |
| 55 | + |
| 56 | +Log4j2 can be chosen as backend by using the corresponding Maven profile: |
| 57 | + |
| 58 | +```bash |
| 59 | +mvn install -Plog4j2 |
| 60 | +``` |
| 61 | + |
| 62 | +The configuration file is [log4j2.xml](src/main/resources/log4j2.xml) in that case. |
| 63 | + |
| 64 | +## Features |
| 65 | + |
| 66 | +This sample application offers a Rest API with several endpoints to show different features of cf-java-logging-support. |
| 67 | +A summary of the endpoints is given in the following table. |
| 68 | +Detailed descriptions are given in later sections. |
| 69 | + |
| 70 | +| Feature | Endpoint | Request Parameter | |
| 71 | +|---------|----------|-------------------| |
| 72 | +| [Generate Log Message](#generating-log-messages) | `POST /log/{logger}/{logLevel}` <br /><dl><dt>`{logger}`</dt><dd>the logger name, usually a class name</dd><dt>`{logLevel}`</dt><dd>the log level, one of `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`</dd></dl>| <dl><dt>`m=message`_(optional)_</dt><dd>the message to log</dd></dl> | |
| 73 | +| [Create JWT for dynamic log level](#creating-tokens) | `GET /token/{logLevel}` <br /><dl><dt>`{logLevel}`</dt><dd>the dynamic log level, one of `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`</dd></dl>| <dl><dt>`p=packagenames` _(optional)_</dt><dd>a comma-separated list of package names</dd><dt>`exp=unixEpoch` _(optional)_</dt><dd>the expiry time in milliseconds since Unix epoch</dd></dl>| |
| 74 | +| [Get public key for JWT verification](#get-public-key) | `GET /publickey` || |
| 75 | + |
| 76 | +### Generating Log Messages |
| 77 | + |
| 78 | +This sample application allows the generation of log messages triggered by an HTTP request. |
| 79 | +Users can send a POST request containing the logger, the required log level and optionally the message to log. |
| 80 | +This allows testing the logging configuration with respect to packages and logging levels. |
| 81 | + |
| 82 | +**Example:** |
| 83 | + |
| 84 | +```bash |
| 85 | +$ curl -X POST -u user:secret localhost:8080/log/test/info |
| 86 | +Generated info log with message: "This is the default log message!". |
| 87 | +``` |
| 88 | + |
| 89 | +```json |
| 90 | +{ |
| 91 | + "written_at":"2021-02-13T10:25:18.673Z", |
| 92 | + "written_ts":1613211918673355000, |
| 93 | + "tenant_id":"-", |
| 94 | + "component_id":"-", |
| 95 | + "component_name":"-", |
| 96 | + "organization_name":"-", |
| 97 | + "component_type":"application", |
| 98 | + "space_name":"-", |
| 99 | + "component_instance":"0", |
| 100 | + "organization_id":"-", |
| 101 | + "correlation_id":"81c759fd-4433-4d06-bddf-c5c30199c49b", |
| 102 | + "space_id":"-", |
| 103 | + "container_id":"-", |
| 104 | + "tenant_subdomain":"-", |
| 105 | + "type":"log", |
| 106 | + "logger":"test", |
| 107 | + "thread":"http-nio-8080-exec-2", |
| 108 | + "level":"INFO", |
| 109 | + "categories":[], |
| 110 | + "msg":"This is the default log message!" |
| 111 | +} |
| 112 | +{ |
| 113 | + "written_at":"2021-02-13T10:25:18.676Z", |
| 114 | + "written_ts":1613211918677807000, |
| 115 | + "tenant_id":"-", |
| 116 | + "component_id":"-", |
| 117 | + "component_name":"-", |
| 118 | + "organization_name":"-", |
| 119 | + "component_type":"application", |
| 120 | + "space_name":"-", |
| 121 | + "component_instance":"0", |
| 122 | + "organization_id":"-", |
| 123 | + "correlation_id":"81c759fd-4433-4d06-bddf-c5c30199c49b", |
| 124 | + "space_id":"-", |
| 125 | + "container_id":"-", |
| 126 | + "tenant_subdomain":"-", |
| 127 | + "type":"request", |
| 128 | + "request":"/log/test/info", |
| 129 | + "referer":"-", |
| 130 | + "response_sent_at":"2021-02-13T10:25:18.676Z", |
| 131 | + "response_status":200, |
| 132 | + "method":"POST", |
| 133 | + "response_size_b":68, |
| 134 | + "request_size_b":-1, |
| 135 | + "remote_port":"redacted", |
| 136 | + "layer":"[SERVLET]", |
| 137 | + "remote_host":"redacted", |
| 138 | + "x_forwarded_for":"-", |
| 139 | + "remote_user":"redacted", |
| 140 | + "protocol":"HTTP/1.1", |
| 141 | + "remote_ip":"redacted", |
| 142 | + "response_content_type":"text/plain;charset=ISO-8859-1", |
| 143 | + "request_received_at":"2021-02-13T10:25:18.665Z", |
| 144 | + "response_time_ms":10.78147, |
| 145 | + "direction":"IN" |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +Note, that the request generates the default log message, that is also contained in the HTTP response and additionally a request log. |
| 150 | +Since this example was executed locally, the CF metadata is empty. |
| 151 | + |
| 152 | +A custom message can be given as well: |
| 153 | + |
| 154 | +```bash |
| 155 | +$ curl -X POST -u user:secret 'localhost:8080/log/test/info?m=Hello+cf-java-logging-support!' |
| 156 | +Generated info log with message: "Hello cf-java-logging-support!". |
| 157 | +``` |
| 158 | + |
| 159 | +Note the quotes for bash and the HTTP encoding of the whitespace in the message. |
| 160 | + |
| 161 | +### Dynamic Log Levels |
| 162 | + |
| 163 | +This sample-application supports dynamic log levels with package support. |
| 164 | +This feature is documented in detail in the [project wiki.](https://github.com/SAP/cf-java-logging-support/wiki/Dynamic-Log-Levels) |
| 165 | +It allows to change the log level by sending the HTTP header `SAP_LOG_LEVEL` with an appropriate JWT. |
| 166 | +Since it is not straight forward to generate such a token, there is an endpoint to create such a JWT. |
| 167 | + |
| 168 | +#### Creating Tokens |
| 169 | + |
| 170 | +The generated token needs to contain three things: |
| 171 | + |
| 172 | +* the log level to apply |
| 173 | +* the package names or prefixes to use |
| 174 | +* an expiration timestamp |
| 175 | + |
| 176 | +The log level is taken as path variable. |
| 177 | +Since package names and expiration timestamp are optional, they can be specified as request parameters. |
| 178 | +JWTs to be used with this sample application require a package name, but an empty string is allowed. |
| 179 | +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](src/main/resources/application.yml) (two days by default). |
| 180 | +You can change the JWT issuer with property `defaults.token.issuer: your-issuer` in [application.yml](src/main/resources/application.yml). |
| 181 | + |
| 182 | +**Example:** |
| 183 | + |
| 184 | +```bash |
| 185 | +$ curl -u user:secret 'localhost:8080/token/DEBUG?p=com,org' |
| 186 | +eyJraWQiOiJqd3QtdG9rZW4iLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJsZXZlbCI6IkRFQlVHIiwiaXNzIjoic2FtcGxlLWFwcC1zcHJpbmctYm9vdCIsImV4cCI6MTYxMzM4Njg2MCwicGFja2FnZXMiOiJjb20sb3JnIiwiaWF0IjoxNjEzMjE0MDYwfQ.YtTk8VnMGN2i3SRLK8GRCDfgQcnAQL04IqojZdVYwEJFCezJsot20MYN-WpAWizVVV3midunnBrNzR3_dByK2gRzTQGGE9rXh4KpLw_4UM6XUJTgpMU8yzqt4pCBT-wpMbJ8UOKbT2RCdqOU1oWJL6rggxi5hGBItTvu0PZzjSG3Zv1eIvDKZcNF9pq4F1r8H2og1Mun28o1r-J5SCURjmolunKDNp4e6fsGSeUttbT5EulIcfHcM9nD4Byyywc2Khs0H13YPqAjdxxFcu_5fYp8JFgbns2Lo5PYjMbY8nxnuZFJmILwXHHRtAoxrcSbpSzFRtZfQsI4earGBGSyog |
| 187 | +``` |
| 188 | + |
| 189 | +The response payload decodes to: |
| 190 | + |
| 191 | +```json |
| 192 | +{ |
| 193 | + "level": "DEBUG", |
| 194 | + "iss": "sample-app-spring-boot", |
| 195 | + "exp": 1613386860, |
| 196 | + "packages": "com,org", |
| 197 | + "iat": 1613214060 |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +**Note:** cf-java-logging-support requires the log level to be one of `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE` in all-caps. |
| 202 | + |
| 203 | + |
| 204 | +#### Using Tokens |
| 205 | + |
| 206 | +The tokens created with the token endpoint can be used as HTTP headers on all endpoints of the application. |
| 207 | +We use the token created in the previous section to post a new log message. |
| 208 | +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.\*). |
| 209 | + |
| 210 | +```bash |
| 211 | +$ 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' |
| 212 | +Generated info log with message: "Hello cf-java-logging-support!". |
| 213 | +``` |
| 214 | + |
| 215 | +```json |
| 216 | +{ "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}" } |
| 217 | +{ "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)" } |
| 218 | +{ "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!" } |
| 219 | +{ "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]" } |
| 220 | +{ "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!\".\"]" } |
| 221 | +{ "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" } |
| 222 | +{ "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" } |
| 223 | +{ "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" } |
| 224 | +{ "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"} |
| 225 | +``` |
| 226 | + |
| 227 | +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. |
| 228 | + |
| 229 | +#### Get Public Key |
| 230 | + |
| 231 | +This sample application can be used as a JWT generator for applications in production. |
| 232 | +For the configuration of those application cf-java-logging-support needs the public key to validate the JWT signature. |
| 233 | +This public key can be obtained |
| 234 | + |
| 235 | +```bash |
| 236 | +$ curl -u user:secret 'localhost:8080/publickey' |
| 237 | +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk/a5ZKvQ0voLi29klo5GCtN40JpscGud5diz2y5mNVCMvU6rdE+yNMs5zfgjy2PhR0TLXWfZrwHeX75dhC49hJup39pClv83FJWVbu6ScWNQUGYgdUY5zGkFcBayZlt1yXybQaCUtC8ksHe+QOAUW9Y43nPa8/vznH+zbROlF/kSHIjegcr0GF6ZOLMBAj+9p6Xp+kZxsFUgnqgIZWUp4YI3+j2xDuBgNptZbjUg7WsqEU/u+CA5uCyjGVriq++qSW1fj1A0C29uj1+n3IqrMlL2MdMQayS/5ppyrjApsSYDG56wQEAOrOKaSeBsZexIvIdhQ12+5SkqwPlQGCUHpQIDAQAB |
| 238 | +``` |
| 239 | + |
| 240 | +**Note:** If this application is used this way you may want to fix the keystore and not accidentally delete it with `mvn clean`. |
| 241 | +You can add your keystore as `src/main/resoures/token_keystore.jks`, which will override the automatically created keystore. |
0 commit comments