Understanding a system’s runtime state can be challenging. Monitoring endpoints and logs
often provide data that is difficult to interpret. When analyzing the internal behavior of
a system, dedicated diagnostic information can be invaluable, especially when visualized.
Generating on-demand snapshots of a system’s state can reveal what is happening under
the hood. Once canonical schemas that represent system state are in place, generating
diagnostics and reports in XML, JSON or PlantUML becomes
straightforward.
Diagnostic interfaces, beyond the well-known monitoring endpoints, should be treated as first-class citizens of critical deployment artifact. In production environments, you cannot simply place breakpoints or inspect variables with your IDE to understand a system’s internal state. Without dedicated diagnostic interfaces that expose runtime information, reproducing issues requires a debug-enabled environment with an attached debugger, which is rarely feasible in production. Moreover, some issues only become apparent and reproducible only when you have knowledge of the system’s internal state. A common but impractical workaround is to deploy versions filled with additional logging statements tracing variable values, which clutters both the logs and the codebase. A more effective approach is to provide a diagnostic interface that can generate reports and snapshots of the system’s internal state on demand.
The diagnostic interface may be a web endpoint that accepts query parameters to isolate
the data of interest and its representation or an MBean
accessible via JMX through tools such
as JConsole. Given the broad support of web endpoints
by the various motoring frameworks, web endpoints are considered a natural choice for a diagnostic interface.
JSON alike document for a serial data communication buffer at runtime […]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
CrcSegmentDecorator: {
ALIAS: "CrcSegmentDecorator",
CRC_ALGORITHM: "CRC-16/CCITT-FALSE",
CRC_BYTE_WIDTH: 2,
CRC_CHECKSUM: 22530,
CRC_CHECKSUM_CONCATENATION_MODE: "PREPEND",
CRC_CHECKSUM_HEX: { 0x02, 0x58 },
CRC_CHECKSUM_LITTLE_ENDIAN_BYTES: { 0x02, 0x58 },
CRC_ENDIANESS: "LITTLE",
DESCRIPTION: "A segment decorator enriching the encapsulated segment with a CRC checksum.",
HASH: 1344645519,
IDENTIFIER: "CrcSegmentDecorator@5025a98f",
LENGTH: 21,
TYPE: "org.refcodes.serial.CrcSegmentDecorator",
VALUE: { 0x02, 0x58, 0x01, 0x29, 0x14, 0x00, 0x00, 0x0c, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21 },
SegmentComposite: {
ALIAS: "SegmentComposite",
DESCRIPTION: "A body containing a composite segment as payload.",
HASH: 1414521932,
IDENTIFIER: "SegmentComposite@544fe44c",
LENGTH: 19,
TYPE: "org.refcodes.serial.SegmentComposite",
VALUE: { 0x01, 0x29, 0x14, 0x00, 0x00, 0x0c, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21 },
BooleanSegment: {
ALIAS: "booleanSegment",
DESCRIPTION: "A segment containing an boolean payload.",
HASH: 101478235,
IDENTIFIER: "BooleanSegment@60c6f5b",
LENGTH: 1,
TYPE: "org.refcodes.serial.BooleanSegment",
VALUE: { 0x01 },
VERBOSE: "true"
},
IntSegment: {
ALIAS: "intSegment",
DESCRIPTION: "A body containing an integer payload.",
ENDIANESS: "LITTLE",
HASH: 540585569,
IDENTIFIER: "IntSegment@2038ae61",
LENGTH: 4,
TYPE: "org.refcodes.serial.IntSegment",
VALUE: { 0x29, 0x14, 0x00, 0x00 },
VERBOSE: "5161"
},
AllocSectionDecoratorSegment: {
ALIAS: "AllocSectionDecoratorSegment",
ALLOC_LENGTH: 12,
ALLOC_LENGTH_WIDTH: 2,
DESCRIPTION: "An allocation decorator referencing a decoratee and prefixing the length of the decoratee in bytes.",
ENDIANESS: "LITTLE",
HASH: 1007653873,
IDENTIFIER: "AllocSectionDecoratorSegment@3c0f93f1",
LENGTH: 14,
TYPE: "org.refcodes.serial.AllocSectionDecoratorSegment",
VALUE: { 0x0c, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21 },
StringSection: {
ALIAS: "stringSection",
DESCRIPTION: "A section containing a string payload.",
HASH: 836514715,
IDENTIFIER: "StringSection@31dc339b",
LENGTH: 12,
TYPE: "org.refcodes.serial.StringSection",
VALUE: { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x21 },
VERBOSE: "Hello world!"
}
}
}
}
The diagnostics data may be served in an arbitrary notation which then is to be interpreted as required.
For example, the data may be provided as XML or JSON and then further processed or as PlantUML
for visualization and analysis.
Providing a diagnostic interface involves three key parts:
First, gather the runtime state in a canonical schema.
Second, generate reports from that schema in formats such as XML, JSON, or PlantUML.
Third, expose these generated reports through a diagnostic interface for easy access and analysis.
1. Gather the runtime state in a canonical schema
The canonical model captures the system’s runtime state. This diagnostic data is organized as nested instances of a Schema class. To remain flexible, the data is stored in a map of arbitrary key/value pairs. For convenience, common diagnostic properties such as identifier, alias, value, or description can be exposed through dedicated methods with the meaning of these properties depending on the specific diagnostic purpose.
Any data structure intended to provide diagnostic data implements the Schemable interface, which requires the implementation of a toSchema() method. This method returns a Schema instance that describes the diagnostic data of the structure. If any member fields within the object graph also implement Schemable, their toSchema() methods are invoked in turn, and their resulting Schema instances are added as children to the current one. This recursive process builds a nested Schema hierarchy that represents the complete diagnostic view of the data structure.
2. Generate XML, JSON, or PlantUML reports from the schema
The SchemaVisitor interface defines methods for traversing a Schema hierarchy according to the Visitor pattern. By invoking a Schema’s visit() method with a specific SchemaVisitor implementation, reports can be generated in various formats. The visitor traverses the entire Schema hierarchy and, depending on its implementation, produces output in JSON, XML, PlantUML, or other notations.
PlantUML source code of a serial data communication buffer at runtime […]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
@startuml
object "ComplexTypeSegment@37574691" {
ALIAS = "ComplexTypeSegment"
DESCRIPTION = "A body containing a composite segment as payload."
HASH = 928466577
IDENTIFIER = "ComplexTypeSegment@37574691"
LENGTH = 53
TYPE = org.refcodes.serial.ComplexTypeSegment
VALUE = [0x33, 0x00, 0xaf, 0x93, 0x01, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x41, 0xaf, 0x93, 0x01, 0x00, 0x5d, 0x56, 0x00, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x42, 0x5d, 0x56, 0x00, 0x00, 0xad, 0xc9, 0x04, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x43, 0xad, 0xc9, 0x04, 0x00]
}
object "AllocSectionDecoratorSegment@70beb599" {
ALIAS = "AllocSectionDecoratorSegment"
ALLOC_LENGTH = 51
ALLOC_LENGTH_WIDTH = 2
DESCRIPTION = "An allocation decorator referencing a decoratee and prefixing the length of the decoratee in bytes."
ENDIANESS = "LITTLE"
HASH = 1891546521
IDENTIFIER = "AllocSectionDecoratorSegment@70beb599"
LENGTH = 53
TYPE = org.refcodes.serial.AllocSectionDecoratorSegment
VALUE = [0x33, 0x00, 0xaf, 0x93, 0x01, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x41, 0xaf, 0x93, 0x01, 0x00, 0x5d, 0x56, 0x00, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x42, 0x5d, 0x56, 0x00, 0x00, 0xad, 0xc9, 0x04, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x43, 0xad, 0xc9, 0x04, 0x00]
}
object "SegmentArraySection@6a79c292" {
ALIAS = "/"
DESCRIPTION = "An array segment containing a fixed length elements array as payload."
HASH = 1786364562
IDENTIFIER = "SegmentArraySection@6a79c292"
LENGTH = 51
TYPE = org.refcodes.serial.SegmentArraySection
VALUE = [0xaf, 0x93, 0x01, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x41, 0xaf, 0x93, 0x01, 0x00, 0x5d, 0x56, 0x00, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x42, 0x5d, 0x56, 0x00, 0x00, 0xad, 0xc9, 0x04, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x43, 0xad, 0xc9, 0x04, 0x00]
}
object "SegmentComposite@11e21d0e" {
ALIAS = "SegmentComposite"
DESCRIPTION = "A body containing a composite segment as payload."
HASH = 300031246
IDENTIFIER = "SegmentComposite@11e21d0e"
LENGTH = 17
TYPE = org.refcodes.serial.SegmentComposite
VALUE = [0xaf, 0x93, 0x01, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x41, 0xaf, 0x93, 0x01, 0x00]
}
object "IntSegment@5e25a92e" {
ALIAS = "/payload"
DESCRIPTION = "A body containing an integer payload."
ENDIANESS = "LITTLE"
HASH = 1579526446
IDENTIFIER = "IntSegment@5e25a92e"
LENGTH = 4
TYPE = org.refcodes.serial.IntSegment
VALUE = [0xaf, 0x93, 0x01, 0x00]
VERBOSE = "103343"
}
object "AllocSectionDecoratorSegment@4df828d7" {
ALIAS = "AllocSectionDecoratorSegment"
ALLOC_LENGTH = 7
ALLOC_LENGTH_WIDTH = 2
DESCRIPTION = "An allocation decorator referencing a decoratee and prefixing the length of the decoratee in bytes."
ENDIANESS = "LITTLE"
HASH = 1308109015
IDENTIFIER = "AllocSectionDecoratorSegment@4df828d7"
LENGTH = 9
TYPE = org.refcodes.serial.AllocSectionDecoratorSegment
VALUE = [0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x41]
}
object "StringSection@b59d31" {
ALIAS = "/name"
DESCRIPTION = "A section containing a string payload."
HASH = 11902257
IDENTIFIER = "StringSection@b59d31"
LENGTH = 7
TYPE = org.refcodes.serial.StringSection
VALUE = [0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x41]
VERBOSE = "SensorA"
}
"AllocSectionDecoratorSegment@4df828d7" --> "StringSection@b59d31" : <has>
object "IntSegment@62fdb4a6" {
ALIAS = "/value"
DESCRIPTION = "A body containing an integer payload."
ENDIANESS = "LITTLE"
HASH = 1660794022
IDENTIFIER = "IntSegment@62fdb4a6"
LENGTH = 4
TYPE = org.refcodes.serial.IntSegment
VALUE = [0xaf, 0x93, 0x01, 0x00]
VERBOSE = "103343"
}
"SegmentComposite@11e21d0e" --> "IntSegment@5e25a92e" : <has>
"SegmentComposite@11e21d0e" --> "AllocSectionDecoratorSegment@4df828d7" : <has>
"SegmentComposite@11e21d0e" --> "IntSegment@62fdb4a6" : <has>
object "SegmentComposite@23bb8443" {
ALIAS = "SegmentComposite"
DESCRIPTION = "A body containing a composite segment as payload."
HASH = 599491651
IDENTIFIER = "SegmentComposite@23bb8443"
LENGTH = 17
TYPE = org.refcodes.serial.SegmentComposite
VALUE = [0x5d, 0x56, 0x00, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x42, 0x5d, 0x56, 0x00, 0x00]
}
object "IntSegment@1dd02175" {
ALIAS = "/payload"
DESCRIPTION = "A body containing an integer payload."
ENDIANESS = "LITTLE"
HASH = 500179317
IDENTIFIER = "IntSegment@1dd02175"
LENGTH = 4
TYPE = org.refcodes.serial.IntSegment
VALUE = [0x5d, 0x56, 0x00, 0x00]
VERBOSE = "22109"
}
object "AllocSectionDecoratorSegment@31206beb" {
ALIAS = "AllocSectionDecoratorSegment"
ALLOC_LENGTH = 7
ALLOC_LENGTH_WIDTH = 2
DESCRIPTION = "An allocation decorator referencing a decoratee and prefixing the length of the decoratee in bytes."
ENDIANESS = "LITTLE"
HASH = 824208363
IDENTIFIER = "AllocSectionDecoratorSegment@31206beb"
LENGTH = 9
TYPE = org.refcodes.serial.AllocSectionDecoratorSegment
VALUE = [0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x42]
}
object "StringSection@3e77a1ed" {
ALIAS = "/name"
DESCRIPTION = "A section containing a string payload."
HASH = 1048027629
IDENTIFIER = "StringSection@3e77a1ed"
LENGTH = 7
TYPE = org.refcodes.serial.StringSection
VALUE = [0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x42]
VERBOSE = "SensorB"
}
"AllocSectionDecoratorSegment@31206beb" --> "StringSection@3e77a1ed" : <has>
object "IntSegment@3ffcd140" {
ALIAS = "/value"
DESCRIPTION = "A body containing an integer payload."
ENDIANESS = "LITTLE"
HASH = 1073533248
IDENTIFIER = "IntSegment@3ffcd140"
LENGTH = 4
TYPE = org.refcodes.serial.IntSegment
VALUE = [0x5d, 0x56, 0x00, 0x00]
VERBOSE = "22109"
}
"SegmentComposite@23bb8443" --> "IntSegment@1dd02175" : <has>
"SegmentComposite@23bb8443" --> "AllocSectionDecoratorSegment@31206beb" : <has>
"SegmentComposite@23bb8443" --> "IntSegment@3ffcd140" : <has>
object "SegmentComposite@1372ed45" {
ALIAS = "SegmentComposite"
DESCRIPTION = "A body containing a composite segment as payload."
HASH = 326298949
IDENTIFIER = "SegmentComposite@1372ed45"
LENGTH = 17
TYPE = org.refcodes.serial.SegmentComposite
VALUE = [0xad, 0xc9, 0x04, 0x00, 0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x43, 0xad, 0xc9, 0x04, 0x00]
}
object "IntSegment@1176dcec" {
ALIAS = "/payload"
DESCRIPTION = "A body containing an integer payload."
ENDIANESS = "LITTLE"
HASH = 293002476
IDENTIFIER = "IntSegment@1176dcec"
LENGTH = 4
TYPE = org.refcodes.serial.IntSegment
VALUE = [0xad, 0xc9, 0x04, 0x00]
VERBOSE = "313773"
}
object "AllocSectionDecoratorSegment@120d6fe6" {
ALIAS = "AllocSectionDecoratorSegment"
ALLOC_LENGTH = 7
ALLOC_LENGTH_WIDTH = 2
DESCRIPTION = "An allocation decorator referencing a decoratee and prefixing the length of the decoratee in bytes."
ENDIANESS = "LITTLE"
HASH = 302870502
IDENTIFIER = "AllocSectionDecoratorSegment@120d6fe6"
LENGTH = 9
TYPE = org.refcodes.serial.AllocSectionDecoratorSegment
VALUE = [0x07, 0x00, 0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x43]
}
object "StringSection@4ba2ca36" {
ALIAS = "/name"
DESCRIPTION = "A section containing a string payload."
HASH = 1268959798
IDENTIFIER = "StringSection@4ba2ca36"
LENGTH = 7
TYPE = org.refcodes.serial.StringSection
VALUE = [0x53, 0x65, 0x6e, 0x73, 0x6f, 0x72, 0x43]
VERBOSE = "SensorC"
}
"AllocSectionDecoratorSegment@120d6fe6" --> "StringSection@4ba2ca36" : <has>
object "IntSegment@3444d69d" {
ALIAS = "/value"
DESCRIPTION = "A body containing an integer payload."
ENDIANESS = "LITTLE"
HASH = 876926621
IDENTIFIER = "IntSegment@3444d69d"
LENGTH = 4
TYPE = org.refcodes.serial.IntSegment
VALUE = [0xad, 0xc9, 0x04, 0x00]
VERBOSE = "313773"
}
"SegmentComposite@1372ed45" --> "IntSegment@1176dcec" : <has>
"SegmentComposite@1372ed45" --> "AllocSectionDecoratorSegment@120d6fe6" : <has>
"SegmentComposite@1372ed45" --> "IntSegment@3444d69d" : <has>
"SegmentArraySection@6a79c292" --> "SegmentComposite@11e21d0e" : <has>
"SegmentArraySection@6a79c292" --> "SegmentComposite@23bb8443" : <has>
"SegmentArraySection@6a79c292" --> "SegmentComposite@1372ed45" : <has>
"AllocSectionDecoratorSegment@70beb599" --> "SegmentArraySection@6a79c292" : <has>
"ComplexTypeSegment@37574691" --> "AllocSectionDecoratorSegment@70beb599" : <has>
@enduml
These reports can then be served through a diagnostic interface for easy access and analysis. In case of a PlantUmlVisitor implementation, the result will be a a text in PlantUML notation which then can be rendered as UML diagrams.
3. Expose the generated reports through a diagnostic interface
Using Spring Boot, the generated reports can be served through a WebEndpoint that implements the ReadOperation method. The example below shows how this can be done.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
@WebEndpoint(id = "diagnostics")
public class DiagnosticsEndpoint {
private Schemable data = ... // The data variable holds an instance implementing the Schemable interface
// ...
@ReadOperation
public WebEndpointResponse<String> getDiagnosticsAsPlantUml() {
Schema schema = data.toSchema();
String diagnostics = schema.visit(new PlantUmlVisitor());
return new WebEndpointResponse<>(diagnostics, 200, "text/x-plantuml");
}
// ...
}
Conclusion
Diagnostic interfaces bridge the gap between static monitoring and true runtime understanding. By exposing canonical diagnostic schemas through standardized interfaces, systems can provide meaningful insights without invasive debugging or excessive logging. The approach described here enables developers and operators to visualize complex runtime structures as UML diagrams, or to process the same data in machine-readable formats such as JSON or XML.
With a consistent schema model and visitor based report generation, runtime introspection becomes both systematic and extensible. Whether integrated into a Spring Boot endpoint or accessed via
JMX, such diagnostic interfaces turn opaque runtime behavior into accessible, analyzable knowledge, helping to maintain stability, trace issues faster, and understand what really happens under the hood.
