├── .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 | }
--------------------------------------------------------------------------------