Skip to content

Commit 1f23a62

Browse files
committed
Add readable single-line log text
1 parent 559501e commit 1f23a62

5 files changed

Lines changed: 74 additions & 2 deletions

File tree

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,25 @@ GET /api/v1/yarn/applications/application_123_0001/logs?follow=true&logFiles=std
5656
Accept: text/event-stream
5757
```
5858

59-
Log event payloads use Base64 so offsets remain byte-accurate.
59+
Log events include both representations:
60+
61+
- `data`: Base64 containing the exact bytes used by `offset`.
62+
- `text`: a convenient UTF-8 view with newlines, tabs, backslashes, and control
63+
characters escaped so the value is printable on one line.
64+
65+
For example, a log chunk containing two lines has a payload like:
66+
67+
```json
68+
{
69+
"type": "LOG",
70+
"encoding": "BASE64",
71+
"data": "Zmlyc3QKc2Vjb25kCg==",
72+
"text": "first\\nsecond\\n"
73+
}
74+
```
75+
76+
Use `data` for exact reconstruction because a byte chunk can split a multi-byte
77+
UTF-8 character; use `text` for terminals, dashboards, and diagnostics.
6078

6179
## WebSocket
6280

core/src/main/kotlin/org/openprojectx/hadoop/yarn/log/api/YarnLogEvent.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ enum class YarnLogSource {
2121

2222
/**
2323
* Transport-neutral event used by both SSE and WebSocket endpoints.
24-
* [data] is Base64 when [encoding] is `BASE64`.
24+
* [data] is Base64 when [encoding] is `BASE64`. [text] is a lossy UTF-8 convenience
25+
* view with line breaks and control characters escaped onto one line.
2526
*/
2627
data class YarnLogEvent(
2728
val type: YarnLogEventType,
@@ -36,6 +37,7 @@ data class YarnLogEvent(
3637
val generation: Long? = null,
3738
val encoding: String? = null,
3839
val data: String? = null,
40+
val text: String? = null,
3941
val state: String? = null,
4042
val message: String? = null,
4143
)

core/src/main/kotlin/org/openprojectx/hadoop/yarn/log/api/engine/DefaultYarnLogStreamService.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class DefaultYarnLogStreamService(
198198
generation = cursor.generation,
199199
encoding = "BASE64",
200200
data = Base64.getEncoder().encodeToString(bytes),
201+
text = bytes.toSingleLineLogText(),
201202
),
202203
)
203204
}
@@ -247,6 +248,7 @@ class DefaultYarnLogStreamService(
247248
source = YarnLogSource.AGGREGATED,
248249
encoding = "BASE64",
249250
data = Base64.getEncoder().encodeToString(bytes),
251+
text = bytes.toSingleLineLogText(),
250252
)
251253
}
252254

@@ -287,6 +289,7 @@ class DefaultYarnLogStreamService(
287289
generation: Long? = null,
288290
encoding: String? = null,
289291
data: String? = null,
292+
text: String? = null,
290293
state: String? = null,
291294
message: String? = null,
292295
) = YarnLogEvent(
@@ -301,6 +304,7 @@ class DefaultYarnLogStreamService(
301304
generation = generation,
302305
encoding = encoding,
303306
data = data,
307+
text = text,
304308
state = state,
305309
message = message,
306310
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package org.openprojectx.hadoop.yarn.log.api.engine
2+
3+
internal fun ByteArray.toSingleLineLogText(): String = toString(Charsets.UTF_8).toSingleLineLogText()
4+
5+
private fun String.toSingleLineLogText(): String = buildString(length) {
6+
for (character in this@toSingleLineLogText) {
7+
when (character) {
8+
'\\' -> append("\\\\")
9+
'\n' -> append("\\n")
10+
'\r' -> append("\\r")
11+
'\t' -> append("\\t")
12+
'\b' -> append("\\b")
13+
'\u000c' -> append("\\f")
14+
'\u0085' -> append("\\u0085")
15+
'\u2028' -> append("\\u2028")
16+
'\u2029' -> append("\\u2029")
17+
else -> if (character.code < 0x20 || character.code == 0x7f) {
18+
append("\\u")
19+
append(character.code.toString(16).padStart(4, '0'))
20+
} else {
21+
append(character)
22+
}
23+
}
24+
}
25+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.openprojectx.hadoop.yarn.log.api.engine
2+
3+
import kotlin.test.Test
4+
import kotlin.test.assertEquals
5+
6+
class SingleLineLogTextTest {
7+
@Test
8+
fun `escapes line breaks tabs backslashes and controls`() {
9+
val bytes = "first\r\nsecond\t\\literal\u0001\u007f".toByteArray()
10+
11+
assertEquals("first\\r\\nsecond\\t\\\\literal\\u0001\\u007f", bytes.toSingleLineLogText())
12+
}
13+
14+
@Test
15+
fun `keeps ordinary unicode readable`() {
16+
assertEquals("任务完成 ✓", "任务完成 ✓".toByteArray().toSingleLineLogText())
17+
}
18+
19+
@Test
20+
fun `escapes unicode line separators`() {
21+
assertEquals("one\\u0085two\\u2028three\\u2029four", "one\u0085two\u2028three\u2029four".toByteArray().toSingleLineLogText())
22+
}
23+
}

0 commit comments

Comments
 (0)