├── .DS_Store ├── .gitignore ├── Dockerfile ├── README.md ├── docker-compose.yml ├── grafana └── grafana-dashboard.json ├── pom.xml ├── prometheus └── prometheus.yml └── src ├── main ├── java │ └── de │ │ └── codecentric │ │ └── springboot │ │ └── monitoring │ │ ├── Application.java │ │ ├── actuator │ │ ├── CustomEndpointController.java │ │ └── HealthCheckController.java │ │ ├── controller │ │ ├── CatController.java │ │ ├── EchoController.java │ │ └── RandomExceptionController.java │ │ └── domain │ │ └── EchoMessage.java └── resources │ ├── application.properties │ └── static │ └── index.html └── test ├── java └── de │ └── codecentric │ └── springboot │ └── monitoring │ ├── ApplicationTests.java │ └── CatControllerIntegrationTest.java └── resources └── expectedEchoMessage.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codecentric/spring-boot-monitoring-sample/f1583f96bfa7e4a2c32ab9a42c3953c6e2c56975/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /data/ 2 | 3 | # Created by https://www.gitignore.io/api/java,macos,windows,intellij+all 4 | 5 | ### Intellij+all ### 6 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 7 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 8 | 9 | # User-specific stuff: 10 | #.idea/**/workspace.xml 11 | .idea/**/tasks.xml 12 | .idea/dictionaries 13 | 14 | # Sensitive or high-churn files: 15 | .idea/**/dataSources/ 16 | .idea/**/dataSources.ids 17 | .idea/**/dataSources.xml 18 | .idea/**/dataSources.local.xml 19 | .idea/**/sqlDataSources.xml 20 | .idea/**/dynamic.xml 21 | .idea/**/uiDesigner.xml 22 | 23 | # Gradle: 24 | .idea/**/gradle.xml 25 | #.idea/**/libraries 26 | 27 | # CMake 28 | cmake-build-debug/ 29 | 30 | # Mongo Explorer plugin: 31 | .idea/**/mongoSettings.xml 32 | 33 | ## File-based project format: 34 | *.iws 35 | 36 | ## Plugin-specific files: 37 | 38 | # IntelliJ 39 | /out/ 40 | 41 | # mpeltonen/sbt-idea plugin 42 | .idea_modules/ 43 | 44 | # JIRA plugin 45 | atlassian-ide-plugin.xml 46 | 47 | # Cursive Clojure plugin 48 | .idea/replstate.xml 49 | 50 | # Crashlytics plugin (for Android Studio and IntelliJ) 51 | com_crashlytics_export_strings.xml 52 | crashlytics.properties 53 | crashlytics-build.properties 54 | fabric.properties 55 | 56 | ### Intellij+all Patch ### 57 | # Ignores the whole idea folder 58 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 59 | 60 | .idea/ 61 | 62 | # The out folder may also exist in sub directories 63 | # In GitHub's .gitignore, it is only excluded at the top level 64 | out/ 65 | 66 | ### Java ### 67 | # Compiled class file 68 | *.class 69 | 70 | # Log file 71 | *.log 72 | 73 | # BlueJ files 74 | *.ctxt 75 | 76 | # Mobile Tools for Java (J2ME) 77 | .mtj.tmp/ 78 | 79 | # Package Files # 80 | *.jar 81 | *.war 82 | *.ear 83 | *.zip 84 | *.tar.gz 85 | *.rar 86 | 87 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 88 | hs_err_pid* 89 | 90 | ### macOS ### 91 | *.DS_Store 92 | .AppleDouble 93 | .LSOverride 94 | 95 | # Icon must end with two \r 96 | Icon 97 | 98 | # Thumbnails 99 | ._* 100 | 101 | # Files that might appear in the root of a volume 102 | .DocumentRevisions-V100 103 | .fseventsd 104 | .Spotlight-V100 105 | .TemporaryItems 106 | .Trashes 107 | .VolumeIcon.icns 108 | .com.apple.timemachine.donotpresent 109 | 110 | # Directories potentially created on remote AFP share 111 | .AppleDB 112 | .AppleDesktop 113 | Network Trash Folder 114 | Temporary Items 115 | .apdisk 116 | 117 | ### Windows ### 118 | # Windows thumbnail cache files 119 | Thumbs.db 120 | ehthumbs.db 121 | ehthumbs_vista.db 122 | 123 | # Folder config file 124 | Desktop.ini 125 | 126 | # Recycle Bin used on file shares 127 | $RECYCLE.BIN/ 128 | 129 | # Windows Installer files 130 | *.cab 131 | *.msi 132 | *.msm 133 | *.msp 134 | 135 | # Windows shortcuts 136 | *.lnk 137 | 138 | # End of https://www.gitignore.io/api/java,macos,windows,intellij+all 139 | 140 | *.iml 141 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jdk-alpine 2 | MAINTAINER codecentric.de 3 | VOLUME /tmp 4 | EXPOSE 8080 5 | 6 | ENV USER_NAME monitoring 7 | ENV APP_HOME /home/$USER_NAME/app 8 | 9 | RUN adduser -D -u 1000 $USER_NAME 10 | RUN mkdir $APP_HOME 11 | 12 | ADD ["target/spring-boot-monitoring-0.0.1-SNAPSHOT.jar" ,"$APP_HOME/spring-boot-monitoring.jar"] 13 | RUN chown $USER_NAME $APP_HOME/spring-boot-monitoring.jar 14 | 15 | USER $USER_NAME 16 | WORKDIR $APP_HOME 17 | RUN touch spring-boot-monitoring.jar 18 | 19 | ENTRYPOINT ["java","-jar","spring-boot-monitoring.jar"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spring Boot - Monitoring 2 | 3 | This is a simple [spring](https://spring.io) [boot](http://projects.spring.io/spring-boot/) showcase project, which based on [https://start.spring.io](https://start.spring.io). 4 | 5 | ## Build application 6 | 7 | 8 | ```shell 9 | mvn clean package 10 | ``` 11 | 12 | ## Start infrastructure 13 | 14 | Start docker monitoring setup with grafana, prometheus, cadvisor and the application. 15 | 16 | ```shell 17 | docker-compose up 18 | docker-compose down 19 | ``` 20 | 21 | ## Smart configuration 22 | 23 | * Application: [http://localhost:8080/cat](http://localhost:8080/cat) 24 | * make some requests 25 | * Prometheus: [http://localhost:9090](http://localhost:9090) 26 | * Grafana: [http://localhost:3000](http://localhost:3000) 27 | * login with admin:admin 28 | * setup "prometheus" data source 29 | * import grafana-dashboard 30 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | services: 4 | prometheus: 5 | image: prom/prometheus:v1.8.2 6 | volumes: 7 | - ./prometheus/:/etc/prometheus/ 8 | - ./data/prometheus/:/prometheus 9 | command: 10 | - '-config.file=/etc/prometheus/prometheus.yml' 11 | - '-storage.local.path=/prometheus' 12 | expose: 13 | - 9090 14 | ports: 15 | - 9090:9090 16 | links: 17 | - cadvisor:cadvisor 18 | depends_on: 19 | - cadvisor 20 | - app 21 | 22 | cadvisor: 23 | image: google/cadvisor:v0.27.3 24 | volumes: 25 | - /:/rootfs:ro 26 | - /var/run:/var/run:rw 27 | - /sys:/sys:ro 28 | - /var/lib/docker/:/var/lib/docker:ro 29 | ports: 30 | - 8090:8080 31 | expose: 32 | - 8090 33 | 34 | grafana: 35 | image: grafana/grafana:4.6.3 36 | depends_on: 37 | - prometheus 38 | ports: 39 | - 3000:3000 40 | volumes: 41 | - ./data/grafana/:/var/lib/grafana 42 | 43 | app: 44 | build: . 45 | ports: 46 | - 8080:8080 47 | - 8081:8081 48 | volumes: 49 | - .:/code -------------------------------------------------------------------------------- /grafana/grafana-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__requires": [ 13 | { 14 | "type": "grafana", 15 | "id": "grafana", 16 | "name": "Grafana", 17 | "version": "4.6.3" 18 | }, 19 | { 20 | "type": "panel", 21 | "id": "graph", 22 | "name": "Graph", 23 | "version": "" 24 | }, 25 | { 26 | "type": "datasource", 27 | "id": "prometheus", 28 | "name": "Prometheus", 29 | "version": "1.0.0" 30 | } 31 | ], 32 | "annotations": { 33 | "list": [ 34 | { 35 | "builtIn": 1, 36 | "datasource": "-- Grafana --", 37 | "enable": true, 38 | "hide": true, 39 | "iconColor": "rgba(0, 211, 255, 1)", 40 | "name": "Annotations & Alerts", 41 | "type": "dashboard" 42 | } 43 | ] 44 | }, 45 | "description": "Docker Monitoring Template", 46 | "editable": true, 47 | "gnetId": 179, 48 | "graphTooltip": 1, 49 | "hideControls": false, 50 | "id": null, 51 | "links": [], 52 | "refresh": "30s", 53 | "rows": [ 54 | { 55 | "collapse": false, 56 | "height": "250px", 57 | "panels": [ 58 | { 59 | "aliasColors": {}, 60 | "bars": false, 61 | "dashLength": 10, 62 | "dashes": false, 63 | "datasource": "${DS_PROMETHEUS}", 64 | "fill": 0, 65 | "id": 10, 66 | "legend": { 67 | "alignAsTable": true, 68 | "avg": false, 69 | "current": false, 70 | "hideEmpty": true, 71 | "hideZero": false, 72 | "max": true, 73 | "min": false, 74 | "rightSide": true, 75 | "show": true, 76 | "total": false, 77 | "values": true 78 | }, 79 | "lines": true, 80 | "linewidth": 1, 81 | "links": [], 82 | "nullPointMode": "null as zero", 83 | "percentage": false, 84 | "pointradius": 5, 85 | "points": false, 86 | "renderer": "flot", 87 | "seriesOverrides": [], 88 | "spaceLength": 10, 89 | "span": 12, 90 | "stack": false, 91 | "steppedLine": false, 92 | "targets": [ 93 | { 94 | "expr": "delta(counter_status_200_cat[1m])", 95 | "format": "time_series", 96 | "instant": false, 97 | "interval": "", 98 | "intervalFactor": 1, 99 | "legendFormat": "/cat", 100 | "refId": "A", 101 | "step": 4 102 | }, 103 | { 104 | "expr": "idelta(counter_status_200_prometheus[1m])", 105 | "format": "time_series", 106 | "interval": "", 107 | "intervalFactor": 2, 108 | "legendFormat": "/prometheus", 109 | "refId": "B" 110 | } 111 | ], 112 | "thresholds": [], 113 | "timeFrom": null, 114 | "timeShift": null, 115 | "title": "Http request count", 116 | "tooltip": { 117 | "shared": true, 118 | "sort": 0, 119 | "value_type": "individual" 120 | }, 121 | "type": "graph", 122 | "xaxis": { 123 | "buckets": null, 124 | "mode": "time", 125 | "name": null, 126 | "show": true, 127 | "values": [] 128 | }, 129 | "yaxes": [ 130 | { 131 | "decimals": 0, 132 | "format": "short", 133 | "label": null, 134 | "logBase": 1, 135 | "max": null, 136 | "min": null, 137 | "show": true 138 | }, 139 | { 140 | "format": "short", 141 | "label": null, 142 | "logBase": 1, 143 | "max": null, 144 | "min": null, 145 | "show": false 146 | } 147 | ] 148 | } 149 | ], 150 | "repeat": null, 151 | "repeatIteration": null, 152 | "repeatRowId": null, 153 | "showTitle": false, 154 | "title": "Row", 155 | "titleSize": "h6" 156 | }, 157 | { 158 | "collapse": false, 159 | "height": "250px", 160 | "panels": [ 161 | { 162 | "aliasColors": {}, 163 | "bars": false, 164 | "dashLength": 10, 165 | "dashes": false, 166 | "datasource": "${DS_PROMETHEUS}", 167 | "decimals": 3, 168 | "editable": true, 169 | "error": false, 170 | "fill": 0, 171 | "grid": {}, 172 | "id": 3, 173 | "legend": { 174 | "alignAsTable": true, 175 | "avg": true, 176 | "current": true, 177 | "max": false, 178 | "min": false, 179 | "rightSide": true, 180 | "show": true, 181 | "sort": "current", 182 | "sortDesc": true, 183 | "total": false, 184 | "values": true 185 | }, 186 | "lines": true, 187 | "linewidth": 2, 188 | "links": [], 189 | "nullPointMode": "connected", 190 | "percentage": false, 191 | "pointradius": 5, 192 | "points": false, 193 | "renderer": "flot", 194 | "seriesOverrides": [], 195 | "spaceLength": 10, 196 | "span": 12, 197 | "stack": false, 198 | "steppedLine": false, 199 | "targets": [ 200 | { 201 | "expr": "sort_desc(sum(rate(container_cpu_user_seconds_total{image!=\"\"}[1m])) by (name))", 202 | "format": "time_series", 203 | "interval": "10s", 204 | "intervalFactor": 1, 205 | "legendFormat": "{{ name }}", 206 | "metric": "container_cpu_user_seconds_total", 207 | "refId": "A", 208 | "step": 10 209 | } 210 | ], 211 | "thresholds": [], 212 | "timeFrom": null, 213 | "timeShift": null, 214 | "title": "Container CPU usage", 215 | "tooltip": { 216 | "msResolution": true, 217 | "shared": true, 218 | "sort": 0, 219 | "value_type": "cumulative" 220 | }, 221 | "type": "graph", 222 | "xaxis": { 223 | "buckets": null, 224 | "mode": "time", 225 | "name": null, 226 | "show": true, 227 | "values": [] 228 | }, 229 | "yaxes": [ 230 | { 231 | "format": "percentunit", 232 | "label": null, 233 | "logBase": 1, 234 | "max": null, 235 | "min": null, 236 | "show": true 237 | }, 238 | { 239 | "format": "short", 240 | "label": null, 241 | "logBase": 1, 242 | "max": null, 243 | "min": null, 244 | "show": true 245 | } 246 | ] 247 | } 248 | ], 249 | "repeat": null, 250 | "repeatIteration": null, 251 | "repeatRowId": null, 252 | "showTitle": false, 253 | "title": "New row", 254 | "titleSize": "h6" 255 | }, 256 | { 257 | "collapse": false, 258 | "height": "250px", 259 | "panels": [ 260 | { 261 | "aliasColors": {}, 262 | "bars": false, 263 | "dashLength": 10, 264 | "dashes": false, 265 | "datasource": "${DS_PROMETHEUS}", 266 | "decimals": 2, 267 | "editable": true, 268 | "error": false, 269 | "fill": 0, 270 | "grid": {}, 271 | "id": 2, 272 | "legend": { 273 | "alignAsTable": true, 274 | "avg": true, 275 | "current": true, 276 | "max": false, 277 | "min": false, 278 | "rightSide": true, 279 | "show": true, 280 | "sideWidth": 200, 281 | "sort": "current", 282 | "sortDesc": true, 283 | "total": false, 284 | "values": true 285 | }, 286 | "lines": true, 287 | "linewidth": 2, 288 | "links": [], 289 | "nullPointMode": "connected", 290 | "percentage": false, 291 | "pointradius": 5, 292 | "points": false, 293 | "renderer": "flot", 294 | "seriesOverrides": [], 295 | "spaceLength": 10, 296 | "span": 12, 297 | "stack": false, 298 | "steppedLine": false, 299 | "targets": [ 300 | { 301 | "expr": "sort_desc(sum(container_memory_usage_bytes{image!=\"\"}) by (name))", 302 | "interval": "10s", 303 | "intervalFactor": 1, 304 | "legendFormat": "{{ name }}", 305 | "metric": "container_memory_usage:sort_desc", 306 | "refId": "A", 307 | "step": 10 308 | } 309 | ], 310 | "thresholds": [], 311 | "timeFrom": null, 312 | "timeShift": null, 313 | "title": "Container Memory Usage", 314 | "tooltip": { 315 | "msResolution": false, 316 | "shared": true, 317 | "sort": 0, 318 | "value_type": "cumulative" 319 | }, 320 | "type": "graph", 321 | "xaxis": { 322 | "buckets": null, 323 | "mode": "time", 324 | "name": null, 325 | "show": true, 326 | "values": [] 327 | }, 328 | "yaxes": [ 329 | { 330 | "format": "bytes", 331 | "label": null, 332 | "logBase": 1, 333 | "max": null, 334 | "min": null, 335 | "show": true 336 | }, 337 | { 338 | "format": "short", 339 | "label": null, 340 | "logBase": 1, 341 | "max": null, 342 | "min": null, 343 | "show": true 344 | } 345 | ] 346 | }, 347 | { 348 | "aliasColors": {}, 349 | "bars": false, 350 | "dashLength": 10, 351 | "dashes": false, 352 | "datasource": "${DS_PROMETHEUS}", 353 | "decimals": 2, 354 | "editable": true, 355 | "error": false, 356 | "fill": 0, 357 | "grid": {}, 358 | "id": 8, 359 | "legend": { 360 | "alignAsTable": true, 361 | "avg": true, 362 | "current": true, 363 | "max": false, 364 | "min": false, 365 | "rightSide": true, 366 | "show": true, 367 | "sideWidth": 200, 368 | "sort": "current", 369 | "sortDesc": true, 370 | "total": false, 371 | "values": true 372 | }, 373 | "lines": true, 374 | "linewidth": 2, 375 | "links": [], 376 | "nullPointMode": "connected", 377 | "percentage": false, 378 | "pointradius": 5, 379 | "points": false, 380 | "renderer": "flot", 381 | "seriesOverrides": [], 382 | "spaceLength": 10, 383 | "span": 12, 384 | "stack": false, 385 | "steppedLine": false, 386 | "targets": [ 387 | { 388 | "expr": "sort_desc(sum by (name) (rate(container_network_receive_bytes_total{image!=\"\"}[1m] ) ))", 389 | "interval": "10s", 390 | "intervalFactor": 1, 391 | "legendFormat": "{{ name }}", 392 | "metric": "container_network_receive_bytes_total", 393 | "refId": "A", 394 | "step": 10 395 | } 396 | ], 397 | "thresholds": [], 398 | "timeFrom": null, 399 | "timeShift": null, 400 | "title": "Container Network Input", 401 | "tooltip": { 402 | "msResolution": false, 403 | "shared": true, 404 | "sort": 0, 405 | "value_type": "cumulative" 406 | }, 407 | "type": "graph", 408 | "xaxis": { 409 | "buckets": null, 410 | "mode": "time", 411 | "name": null, 412 | "show": true, 413 | "values": [] 414 | }, 415 | "yaxes": [ 416 | { 417 | "format": "bytes", 418 | "label": null, 419 | "logBase": 1, 420 | "max": null, 421 | "min": null, 422 | "show": true 423 | }, 424 | { 425 | "format": "short", 426 | "label": null, 427 | "logBase": 1, 428 | "max": null, 429 | "min": null, 430 | "show": true 431 | } 432 | ] 433 | }, 434 | { 435 | "aliasColors": {}, 436 | "bars": false, 437 | "dashLength": 10, 438 | "dashes": false, 439 | "datasource": "${DS_PROMETHEUS}", 440 | "decimals": 2, 441 | "editable": true, 442 | "error": false, 443 | "fill": 0, 444 | "grid": {}, 445 | "id": 9, 446 | "legend": { 447 | "alignAsTable": true, 448 | "avg": true, 449 | "current": true, 450 | "max": false, 451 | "min": false, 452 | "rightSide": true, 453 | "show": true, 454 | "sideWidth": 200, 455 | "sort": "current", 456 | "sortDesc": true, 457 | "total": false, 458 | "values": true 459 | }, 460 | "lines": true, 461 | "linewidth": 2, 462 | "links": [], 463 | "nullPointMode": "connected", 464 | "percentage": false, 465 | "pointradius": 5, 466 | "points": false, 467 | "renderer": "flot", 468 | "seriesOverrides": [], 469 | "spaceLength": 10, 470 | "span": 12, 471 | "stack": false, 472 | "steppedLine": false, 473 | "targets": [ 474 | { 475 | "expr": "sort_desc(sum by (name) (rate(container_network_transmit_bytes_total{image!=\"\"}[1m] ) ))", 476 | "intervalFactor": 2, 477 | "legendFormat": "{{ name }}", 478 | "metric": "container_network_transmit_bytes_total", 479 | "refId": "B", 480 | "step": 4 481 | } 482 | ], 483 | "thresholds": [], 484 | "timeFrom": null, 485 | "timeShift": null, 486 | "title": "Container Network Output", 487 | "tooltip": { 488 | "msResolution": false, 489 | "shared": true, 490 | "sort": 0, 491 | "value_type": "cumulative" 492 | }, 493 | "type": "graph", 494 | "xaxis": { 495 | "buckets": null, 496 | "mode": "time", 497 | "name": null, 498 | "show": true, 499 | "values": [] 500 | }, 501 | "yaxes": [ 502 | { 503 | "format": "bytes", 504 | "label": null, 505 | "logBase": 1, 506 | "max": null, 507 | "min": null, 508 | "show": true 509 | }, 510 | { 511 | "format": "short", 512 | "label": null, 513 | "logBase": 1, 514 | "max": null, 515 | "min": null, 516 | "show": false 517 | } 518 | ] 519 | } 520 | ], 521 | "repeat": null, 522 | "repeatIteration": null, 523 | "repeatRowId": null, 524 | "showTitle": false, 525 | "title": "New row", 526 | "titleSize": "h6" 527 | } 528 | ], 529 | "schemaVersion": 14, 530 | "style": "dark", 531 | "tags": [ 532 | "docker" 533 | ], 534 | "templating": { 535 | "list": [] 536 | }, 537 | "time": { 538 | "from": "now-30m", 539 | "to": "now" 540 | }, 541 | "timepicker": { 542 | "refresh_intervals": [ 543 | "5s", 544 | "10s", 545 | "30s", 546 | "1m", 547 | "5m", 548 | "15m", 549 | "30m", 550 | "1h", 551 | "2h", 552 | "1d" 553 | ], 554 | "time_options": [ 555 | "5m", 556 | "15m", 557 | "1h", 558 | "6h", 559 | "12h", 560 | "24h", 561 | "2d", 562 | "7d", 563 | "30d" 564 | ] 565 | }, 566 | "timezone": "browser", 567 | "title": "Spring Boot Monitoring Sample Dashboard", 568 | "version": 1 569 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | de.codecentric 7 | spring-boot-monitoring 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | spring-boot-monitoring 12 | Spring Boot Sample Project - Monitoring 13 | 14 | 15 | UTF-8 16 | UTF-8 17 | 1.8 18 | 1.18.0 19 | 0.4.0 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-parent 25 | 1.5.14.RELEASE 26 | 27 | 28 | 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-web 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-actuator 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-actuator-docs 44 | 45 | 46 | 47 | io.prometheus 48 | simpleclient_spring_boot 49 | ${prometheus.verison} 50 | 51 | 52 | 53 | org.projectlombok 54 | lombok 55 | ${lombok.version} 56 | provided 57 | 58 | 59 | 60 | 61 | org.springframework.boot 62 | spring-boot-starter-test 63 | test 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.springframework.boot 71 | spring-boot-maven-plugin 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-jar-plugin 76 | 3.1.0 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | # my global config 2 | global: 3 | scrape_interval: 15s # By default, scrape targets every 15 seconds. 4 | evaluation_interval: 15s # By default, scrape targets every 15 seconds. 5 | # scrape_timeout is set to the global default (10s). 6 | 7 | # Attach these labels to any time series or alerts when communicating with 8 | # external systems (federation, remote storage, Alertmanager). 9 | external_labels: 10 | monitor: 'my-project' 11 | 12 | # Load and evaluate rules in this file every 'evaluation_interval' seconds. 13 | rule_files: 14 | - "alert.rules" 15 | # - "first.rules" 16 | # - "second.rules" 17 | 18 | # A scrape configuration containing exactly one endpoint to scrape: 19 | scrape_configs: 20 | - job_name: 'app' 21 | 22 | scrape_interval: 5s 23 | 24 | metrics_path: '/prometheus' 25 | 26 | static_configs: 27 | - targets: ['app:8080'] 28 | 29 | - job_name: 'cadvisor' 30 | 31 | scrape_interval: 5s 32 | 33 | metrics_path: '/metrics' 34 | 35 | static_configs: 36 | - targets: ['cadvisor:8080'] -------------------------------------------------------------------------------- /src/main/java/de/codecentric/springboot/monitoring/Application.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring; 2 | 3 | import io.prometheus.client.spring.boot.EnablePrometheusEndpoint; 4 | import io.prometheus.client.spring.boot.EnableSpringBootMetricsCollector; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | 8 | @SpringBootApplication 9 | @EnablePrometheusEndpoint 10 | @EnableSpringBootMetricsCollector 11 | public class Application { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(Application.class, args); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/de/codecentric/springboot/monitoring/actuator/CustomEndpointController.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring.actuator; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import org.springframework.boot.actuate.endpoint.Endpoint; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class CustomEndpointController implements Endpoint> { 10 | 11 | @Override 12 | public String getId() { 13 | return "customEndpoint"; 14 | } 15 | 16 | @Override 17 | public boolean isEnabled() { 18 | return true; 19 | } 20 | 21 | @Override 22 | public boolean isSensitive() { 23 | return true; 24 | } 25 | 26 | @Override 27 | public List invoke() { 28 | // your custom logic 29 | return Arrays.asList("Message 1", "Message 2"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/de/codecentric/springboot/monitoring/actuator/HealthCheckController.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring.actuator; 2 | 3 | import org.springframework.boot.actuate.health.Health; 4 | import org.springframework.boot.actuate.health.HealthIndicator; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class HealthCheckController implements HealthIndicator { 9 | 10 | @Override 11 | public Health health() { 12 | int errorCode = specificHealthCheck(); 13 | if (errorCode != 0) { 14 | return Health.down() 15 | .withDetail("Error Code", errorCode).build(); 16 | } 17 | return Health.up().build(); 18 | } 19 | 20 | public int specificHealthCheck() { 21 | // your logic to check health 22 | return 0; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/de/codecentric/springboot/monitoring/controller/CatController.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring.controller; 2 | 3 | import org.springframework.web.bind.annotation.RequestMapping; 4 | import org.springframework.web.bind.annotation.RestController; 5 | 6 | @RestController 7 | public class CatController { 8 | 9 | @RequestMapping("/cat") 10 | public String cat() { 11 | return "Miau"; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/de/codecentric/springboot/monitoring/controller/EchoController.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring.controller; 2 | 3 | import de.codecentric.springboot.monitoring.domain.EchoMessage; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RequestMethod; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | public class EchoController { 12 | 13 | private final AtomicLong atomicCounter = new AtomicLong(); 14 | 15 | @RequestMapping(value = "/echo") 16 | public String echo(@RequestParam(value = "text") final String text) { 17 | return getMessage(text).getMessage(); 18 | } 19 | 20 | @RequestMapping(value = "/echo/json", method = RequestMethod.GET, produces = "application/json") 21 | public EchoMessage echoJson(@RequestParam(value = "text", defaultValue = "Hello World") final String text) { 22 | return getMessage(text); 23 | } 24 | 25 | private EchoMessage getMessage(final String text) { 26 | return new EchoMessage( 27 | String.format("%d: %s", atomicCounter.incrementAndGet(), text)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/de/codecentric/springboot/monitoring/controller/RandomExceptionController.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring.controller; 2 | 3 | import java.util.Random; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.actuate.metrics.CounterService; 7 | import org.springframework.boot.actuate.metrics.GaugeService; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @Slf4j 12 | @RestController 13 | public class RandomExceptionController { 14 | 15 | private final GaugeService gaugeService; 16 | private final CounterService counterService; 17 | 18 | @Autowired 19 | public RandomExceptionController(final GaugeService gaugeService, final CounterService counterService) { 20 | this.gaugeService = gaugeService; 21 | this.counterService = counterService; 22 | } 23 | 24 | @RequestMapping(value = "/random/exception") 25 | public String randomException() { 26 | final double randomValue = new Random().nextDouble(); 27 | this.gaugeService.submit("myRequestRandomValue", randomValue); 28 | if (randomValue > 0.5) { 29 | this.counterService.increment("myExceptionCounter"); 30 | log.error("random exception was triggered"); 31 | throw new IllegalStateException("random exception"); 32 | } 33 | return "work well"; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/de/codecentric/springboot/monitoring/domain/EchoMessage.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring.domain; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | @Data 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | public class EchoMessage { 11 | 12 | private String message; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html 2 | 3 | server.port=8080 4 | # ---------------------------------------- 5 | # ACTUATOR PROPERTIES 6 | # ---------------------------------------- 7 | endpoints.sensitive=false 8 | management.security.enabled=false 9 | management.port=8080 10 | #management.address=127.0.0.1 11 | #security.user.name=admin 12 | #security.user.password=DONT_DO_THIS -------------------------------------------------------------------------------- /src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Spring Boot - Monitoring 6 | 7 | 8 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /src/test/java/de/codecentric/springboot/monitoring/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class ApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/de/codecentric/springboot/monitoring/CatControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package de.codecentric.springboot.monitoring; 2 | 3 | import static org.assertj.core.api.BDDAssertions.then; 4 | 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.context.embedded.LocalServerPort; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; 11 | import org.springframework.boot.test.web.client.TestRestTemplate; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | @RunWith(SpringRunner.class) 17 | @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) 18 | public class CatControllerIntegrationTest { 19 | 20 | @LocalServerPort 21 | private int serverPort; 22 | 23 | @Autowired 24 | private TestRestTemplate testRestTemplate; 25 | 26 | @Test 27 | public void shouldReturn200WhenSendingRequestToController() { 28 | then(getResponseEntity().getStatusCode()).isEqualTo(HttpStatus.OK); 29 | } 30 | 31 | @Test 32 | public void shouldReturnContentWhenSendingRequestToController() { 33 | then(getResponseEntity().getBody()).isEqualTo("Miau"); 34 | } 35 | 36 | private ResponseEntity getResponseEntity() { 37 | return this.testRestTemplate.getForEntity( 38 | "http://localhost:" + this.serverPort + "/cat", String.class); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/test/resources/expectedEchoMessage.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Hello" 3 | } --------------------------------------------------------------------------------