@@ -5,7 +5,30 @@ import ch.qos.logback.classic.spi.ThrowableProxy
5
5
import kotlinx.serialization.SerializationStrategy
6
6
import net.logstash.logback.marker.SingleFieldAppendingMarker
7
7
8
- /* * Class used in the logging methods on [Logger] to add markers/cause exception to logs. */
8
+ /* *
9
+ * Class used in the logging methods on [Logger], allowing you to set a [cause] exception and
10
+ * [add structured key-value data][addField] to a log.
11
+ *
12
+ * ### Example
13
+ *
14
+ * ```
15
+ * private val log = Logger {}
16
+ *
17
+ * fun example(user: User) {
18
+ * try {
19
+ * storeUser(user)
20
+ * } catch (e: Exception) {
21
+ * // The lambda argument passed to this logger method has a LogBuilder as its receiver, which
22
+ * // means that you can set `LogBuilder.cause` and call `LogBuilder.addField` in this scope.
23
+ * log.error {
24
+ * cause = e
25
+ * addField("user", user)
26
+ * "Failed to store user in database"
27
+ * }
28
+ * }
29
+ * }
30
+ * ```
31
+ */
9
32
@JvmInline // Inline value class, since we just wrap a Logback logging event
10
33
value class LogBuilder
11
34
internal constructor (
@@ -20,14 +43,14 @@ internal constructor(
20
43
get() = (logEvent.throwableProxy as ? ThrowableProxy )?.throwable
21
44
22
45
/* *
23
- * Adds a [log marker][LogMarker] with the given key and value to the log.
46
+ * Adds a [log field][LogField] (structured key- value data) to the log.
24
47
*
25
48
* The value is serialized using `kotlinx.serialization`, so if you pass an object here, you
26
49
* should make sure it is annotated with [@Serializable][kotlinx.serialization.Serializable].
27
50
* Alternatively, you can pass your own serializer for the value. If serialization fails, we fall
28
51
* back to calling `toString()` on the value.
29
52
*
30
- * If you have a value that is already serialized, you should use [addRawJsonMarker ] instead.
53
+ * If you have a value that is already serialized, you should use [addRawJsonField ] instead.
31
54
*
32
55
* ### Example
33
56
*
@@ -41,15 +64,15 @@ internal constructor(
41
64
* val user = User(id = 1, name = "John Doe")
42
65
*
43
66
* log.info {
44
- * addMarker ("user", user)
67
+ * addField ("user", user)
45
68
* "Registered new user"
46
69
* }
47
70
* }
48
71
*
49
72
* @Serializable data class User(val id: Long, val name: String)
50
73
* ```
51
74
*
52
- * This gives the following output using `logstash-logback-encoder`:
75
+ * This gives the following output ( using `logstash-logback-encoder`) :
53
76
* ```json
54
77
* {
55
78
* "message": "Registered new user",
@@ -61,18 +84,19 @@ internal constructor(
61
84
* }
62
85
* ```
63
86
*/
64
- inline fun <reified ValueT > addMarker (
87
+ inline fun <reified ValueT > addField (
65
88
key : String ,
66
89
value : ValueT ,
67
90
serializer : SerializationStrategy <ValueT >? = null,
68
91
) {
69
- if (! markerKeyAdded (key)) {
70
- logEvent.addMarker(createLogstashMarker (key, value, serializer))
92
+ if (! keyAdded (key)) {
93
+ logEvent.addMarker(createLogstashField (key, value, serializer))
71
94
}
72
95
}
73
96
74
97
/* *
75
- * Adds a [log marker][LogMarker] with the given key and pre-serialized JSON value to the log.
98
+ * Adds a [log field][LogField] (structured key-value data) to the log, with the given
99
+ * pre-serialized JSON value.
76
100
*
77
101
* By default, this function checks that the given JSON string is actually valid JSON. The reason
78
102
* for this is that giving raw JSON to our log encoder when it is not in fact valid JSON can break
@@ -90,59 +114,58 @@ internal constructor(
90
114
* val userJson = """{"id":1,"name":"John Doe"}"""
91
115
*
92
116
* log.info {
93
- * addRawJsonMarker ("user", userJson)
117
+ * addRawJsonField ("user", userJson)
94
118
* "Registered new user"
95
119
* }
96
120
* }
97
121
* ```
98
122
*
99
- * This gives the following output using `logstash-logback-encoder`:
123
+ * This gives the following output ( using `logstash-logback-encoder`) :
100
124
* ```json
101
125
* {"message":"Registered new user","user":{"id":1,"name":"John Doe"},/* ...timestamp etc. */}
102
126
* ```
103
127
*/
104
- fun addRawJsonMarker (key : String , json : String , validJson : Boolean = false) {
105
- if (! markerKeyAdded (key)) {
106
- logEvent.addMarker(createRawJsonLogstashMarker (key, json, validJson))
128
+ fun addRawJsonField (key : String , json : String , validJson : Boolean = false) {
129
+ if (! keyAdded (key)) {
130
+ logEvent.addMarker(createRawJsonLogstashField (key, json, validJson))
107
131
}
108
132
}
109
133
110
134
/* *
111
- * Adds the given [log marker][LogMarker ] to the log. This is useful when you have a previously
112
- * constructed log marker, from the [marker]/[rawJsonMarker ] functions.
113
- * - If you want to create a new marker and add it to the log, you should instead call [addMarker ]
114
- * - If you want to add the marker to all logs within a scope, you should instead use
135
+ * Adds the given [log field][LogField ] to the log. This is useful when you have a previously
136
+ * constructed field from the [field][dev.hermannm.devlog.field]/[rawJsonField ] functions.
137
+ * - If you want to create a new field and add it to the log, you should instead call [addField ]
138
+ * - If you want to add the field to all logs within a scope, you should instead use
115
139
* [withLoggingContext]
116
140
*/
117
- fun addExistingMarker ( marker : LogMarker ) {
118
- if (! markerKeyAdded(marker.logstashMarker.fieldName )) {
119
- logEvent.addMarker(marker.logstashMarker )
141
+ fun addPreconstructedField ( field : LogField ) {
142
+ if (! keyAdded(field.key )) {
143
+ logEvent.addMarker(field.logstashField )
120
144
}
121
145
}
122
146
123
- /* * Adds log markers from [withLoggingContext]. */
124
- internal fun addMarkersFromContext () {
147
+ /* * Adds log fields from [withLoggingContext]. */
148
+ internal fun addFieldsFromContext () {
125
149
// loggingContext will be null if withLoggingContext has not been called in this thread
126
- val contextMarkers = loggingContext.get() ? : return
150
+ val contextFields = loggingContext.get() ? : return
127
151
128
- // Add context markers in reverse, so newest marker shows first
129
- contextMarkers .forEachReversed { logstashMarker ->
130
- // Don't add marker keys that have already been added
131
- if (! markerKeyAdded(logstashMarker .fieldName)) {
132
- logEvent.addMarker(logstashMarker )
152
+ // Add context fields in reverse, so newest field shows first
153
+ contextFields .forEachReversed { logstashField ->
154
+ // Don't add fields with keys that have already been added
155
+ if (! keyAdded(logstashField .fieldName)) {
156
+ logEvent.addMarker(logstashField )
133
157
}
134
158
}
135
159
}
136
160
137
161
/* *
138
162
* Checks if the log [cause] exception (or any of its own cause exceptions) implements the
139
- * [WithLogMarkers ] interface, and if so, adds those markers .
163
+ * [WithLogFields ] interface, and if so, adds those fields .
140
164
*/
141
- internal fun addMarkersFromCauseException () {
165
+ internal fun addFieldsFromCauseException () {
142
166
// The `cause` here is the log event cause exception. But this exception may itself have a
143
167
// `cause` exception, and that may have another one, and so on. We want to go through all these
144
- // exceptions to look for log markers, so we re-assign this local variable as we iterate
145
- // through.
168
+ // exceptions to look for log field, so we re-assign this local variable as we iterate through.
146
169
var exception = cause
147
170
// Limit the depth of cause exceptions, so we don't expose ourselves to infinite loops.
148
171
// This can happen if:
@@ -152,11 +175,11 @@ internal constructor(
152
175
// We set max depth to 10, which should be high enough to not affect real users.
153
176
var depth = 0
154
177
while (exception != null && depth < 10 ) {
155
- if (exception is WithLogMarkers ) {
156
- exception.logMarkers .forEach { marker ->
157
- // Don't add marker keys that have already been added
158
- if (! markerKeyAdded(marker.logstashMarker.fieldName )) {
159
- logEvent.addMarker(marker.logstashMarker )
178
+ if (exception is WithLogFields ) {
179
+ exception.logFields .forEach { field ->
180
+ // Don't add fields with keys that have already been added
181
+ if (! keyAdded(field.key )) {
182
+ logEvent.addMarker(field.logstashField )
160
183
}
161
184
}
162
185
}
@@ -167,12 +190,12 @@ internal constructor(
167
190
}
168
191
169
192
@PublishedApi
170
- internal fun markerKeyAdded (key : String ): Boolean {
171
- /* * [LogbackEvent.markerList] can be null if no markers have been added yet. */
172
- val markers = logEvent.markerList ? : return false
193
+ internal fun keyAdded (key : String ): Boolean {
194
+ /* * [LogbackEvent.markerList] can be null if no fields have been added yet. */
195
+ val addedFields = logEvent.markerList ? : return false
173
196
174
- return markers .any { existingMarker ->
175
- existingMarker is SingleFieldAppendingMarker && existingMarker .fieldName == key
197
+ return addedFields .any { logstashField ->
198
+ logstashField is SingleFieldAppendingMarker && logstashField .fieldName == key
176
199
}
177
200
}
178
201
}
0 commit comments