Repository: ObolNetwork/charon

Issue:
Remove the `pretty` key from JSON logging
# 🎯 Problem to be solved

If you turn `log-format` to JSON, you get the full normal log line as a key in the JSON, along with the JSON-ified value. This doubles the length of the log for little gain

# 🛠️ Proposed solution

Drop the `pretty` key from JSON logging

Example:

```json
{"level":"warn","ts":"2025-08-22T06:52:31.762796658Z","caller":"peerinfo/peerinfo.go:255","msg":"Mismatching peer builder API status","peer":"selfish-box","peer_builder_api_enabled":true,"builder_api_enabled":false,"topic":"peerinfo","pretty":"WARN peerinfo   Mismatching peer builder API status      {\"peer\": \"selfish-box\", \"peer_builder_api_enabled\": true, \"builder_api_enabled\": false}\n"}
```

Hints from issue comments:
@KaloyanTanev: I have to double check how the logs are ingested in our Loki first, not to break anything with the queries there.@jakubgs: It's not that it just doubles the size of the log, it also makes the JSON not parseable by `jq`:
```
 > echo '{"level":"warn","ts":"2025-08-22T06:52:31.762796658Z","caller":"peerinfo/peerinfo.go:255","msg":"Mismatching peer builder API status","peer":"selfish-box","peer_builder_api_enabled":true,"builder_api_enabled":false,"topic":"peerinfo","pretty":"WARN peerinfo   Mismatching peer builder API status      {\"peer\": \"selfish-box\", \"peer_builder_api_enabled\": true, \"builder_api_enabled\": false}\n"}' | jq .
jq: parse error: Invalid string: control characters from U+0000 through U+001F must be escaped at line 2, column 1
```
Works if I remove the `pretty` key:
```
 > echo '{"level":"warn","ts":"2025-08-22T06:52:31.762796658Z","caller":"peerinfo/peerinfo.go:255","msg":"Mismatching peer builder API status","peer":"selfish-box","peer_builder_api_enabled":true,"builder_api_enabled":false,"topic":"peerinfo"}' | jq .
{
  "level": "warn",
  "ts": "2025-08-22T06:52:31.762796658Z",
  "caller": "peerinfo/peerinfo.go:255",
  "msg": "Mismatching peer builder API status",
  "peer": "selfish-box",
  "peer_builder_api_enabled": true,
  "builder_api_enabled": false,
  "topic": "peerinfo"
}
```

Solution patch:
diff --git a/app/log/config.go b/app/log/config.go
index ef8b8f08..97d39c06 100644
--- a/app/log/config.go
+++ b/app/log/config.go
@@ -319,13 +319,18 @@ func newStructuredLogger(format string, level zapcore.Level, color bool, ws zapc
 		opt(&encConfig)
 	}
 
-	var encoder zapcore.Encoder
+	var (
+		encoder zapcore.Encoder
+		pretty  bool
+	)
 
 	switch format {
 	case "logfmt":
 		encoder = zaplogfmt.NewEncoder(encConfig)
+		pretty = true
 	case "json":
 		encoder = zapcore.NewJSONEncoder(encConfig)
+		pretty = false
 	default:
 		return nil, errors.New("invalid logger format; not console, logfmt or json", z.Str("format", format))
 	}
@@ -333,6 +338,7 @@ func newStructuredLogger(format string, level zapcore.Level, color bool, ws zapc
 	structured := structuredEncoder{
 		Encoder:        encoder,
 		consoleEncoder: newConsoleEncoder(false, color, false),
+		pretty:         pretty,
 	}
 
 	return zap.New(
@@ -396,15 +402,19 @@ type structuredEncoder struct {
 	zapcore.Encoder
 
 	consoleEncoder zapcore.Encoder
+
+	pretty bool
 }
 
 func (e structuredEncoder) EncodeEntry(ent zapcore.Entry, fields []zap.Field) (*buffer.Buffer, error) {
-	pretty, err := e.consoleEncoder.EncodeEntry(ent, append([]zap.Field(nil), fields...))
-	if err != nil {
-		return nil, err
-	}
+	if e.pretty {
+		pretty, err := e.consoleEncoder.EncodeEntry(ent, append([]zap.Field(nil), fields...))
+		if err != nil {
+			return nil, err
+		}
 
-	fields = append(fields, zap.String("pretty", pretty.String()))
+		fields = append(fields, zap.String("pretty", pretty.String()))
+	}
 
 	for i, f := range fields {
 		if f.Key == keyStack {
diff --git a/go.mod b/go.mod
index 6229bf8f..a7bb792d 100644
--- a/go.mod
+++ b/go.mod
@@ -3,8 +3,8 @@ module github.com/obolnetwork/charon
 go 1.25
 
 require (
-	github.com/attestantio/go-builder-client v0.7.1
-	github.com/attestantio/go-eth2-client v0.27.0
+	github.com/attestantio/go-builder-client v0.7.2
+	github.com/attestantio/go-eth2-client v0.27.1
 	github.com/coinbase/kryptology v1.5.6-0.20220316191335-269410e1b06b
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0
 	github.com/ethereum/go-ethereum v1.16.4
diff --git a/go.sum b/go.sum
index fb434447..e62b8769 100644
--- a/go.sum
+++ b/go.sum
@@ -52,8 +52,8 @@ github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYW
 github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
 github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
 github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
-github.com/attestantio/go-builder-client v0.7.1 h1:wKzmfuizxBE6q00Gguk71fHkcudwOcxn/W4uvll36Fo=
-github.com/attestantio/go-builder-client v0.7.1/go.mod h1:wGZ0U3QX8/F4lWwieJpqCPgXIl8gbfBxm8iViznrTFQ=
+github.com/attestantio/go-builder-client v0.7.2 h1:bOrtysEIZd9bEM+mAeT6OtAo6LSAft/qylBLwFoFwZ0=
+github.com/attestantio/go-builder-client v0.7.2/go.mod h1:+NADxbaknI5yxl+0mCkMa/VciVsesxRMGNP/poDfV08=
 github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o=
 github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=



Test patch:
diff --git a/app/log/testdata/TestCopyFields_json.golden b/app/log/testdata/TestCopyFields_json.golden
index 1717225a..fd22b75d 100644
--- a/app/log/testdata/TestCopyFields_json.golden
+++ b/app/log/testdata/TestCopyFields_json.golden
@@ -1,2 +1,2 @@
-{"level":"info","ts":"00:00","caller":"log/log_test.go:83","msg":"see source","source":"source","pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m see source                               {\"source\": \"source\"}\n"}
-{"level":"info","ts":"00:00","caller":"log/log_test.go:84","msg":"also source","source":"source","pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m also source                              {\"source\": \"source\"}\n"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:83","msg":"see source","source":"source"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:84","msg":"also source","source":"source"}
diff --git a/app/log/testdata/TestErrorWrapOther_json.golden b/app/log/testdata/TestErrorWrapOther_json.golden
index f6fef645..e0bb856a 100644
--- a/app/log/testdata/TestErrorWrapOther_json.golden
+++ b/app/log/testdata/TestErrorWrapOther_json.golden
@@ -1,2 +1,2 @@
-{"level":"error","ts":"00:00","caller":"log/log_test.go:66","msg":"err1: EOF","stacktrace":"\tapp/log/log_test.go:66 .func1\n\tapp/log/log_test.go:138 .func1","pretty":"\u001b[31mERRO\u001b[0m \u001b[32m          \u001b[0m err1: EOF                               \n"}
-{"level":"error","ts":"00:00","caller":"log/log_test.go:67","msg":"err2: wrap: EOF","stacktrace":"\tapp/log/log_test.go:63 .func1\n\tapp/log/log_test.go:138 .func1","pretty":"\u001b[31mERRO\u001b[0m \u001b[32m          \u001b[0m err2: wrap: EOF                         \n"}
+{"level":"error","ts":"00:00","caller":"log/log_test.go:66","msg":"err1: EOF","stacktrace":"\tapp/log/log_test.go:66 .func1\n\tapp/log/log_test.go:138 .func1"}
+{"level":"error","ts":"00:00","caller":"log/log_test.go:67","msg":"err2: wrap: EOF","stacktrace":"\tapp/log/log_test.go:63 .func1\n\tapp/log/log_test.go:138 .func1"}
diff --git a/app/log/testdata/TestErrorWrap_json.golden b/app/log/testdata/TestErrorWrap_json.golden
index 2bf25dc9..32b45f4e 100644
--- a/app/log/testdata/TestErrorWrap_json.golden
+++ b/app/log/testdata/TestErrorWrap_json.golden
@@ -1,3 +1,3 @@
-{"level":"warn","ts":"00:00","caller":"log/log_test.go:54","msg":"err1: first","stacktrace":"\tapp/log/log_test.go:49 .func1\n\tapp/log/log_test.go:138 .func1","1":1,"pretty":"\u001b[33mWARN\u001b[0m \u001b[32m          \u001b[0m err1: first                              {\"1\": 1}\n"}
-{"level":"error","ts":"00:00","caller":"log/log_test.go:55","msg":"err2: second: first","stacktrace":"\tapp/log/log_test.go:49 .func1\n\tapp/log/log_test.go:138 .func1","2":2,"1":1,"pretty":"\u001b[31mERRO\u001b[0m \u001b[32m          \u001b[0m err2: second: first                      {\"2\": 2, \"1\": 1}\n"}
-{"level":"error","ts":"00:00","caller":"log/log_test.go:56","msg":"err3: third: second: first","stacktrace":"\tapp/log/log_test.go:49 .func1\n\tapp/log/log_test.go:138 .func1","3":3,"2":2,"1":1,"pretty":"\u001b[31mERRO\u001b[0m \u001b[32m          \u001b[0m err3: third: second: first               {\"3\": 3, \"2\": 2, \"1\": 1}\n"}
+{"level":"warn","ts":"00:00","caller":"log/log_test.go:54","msg":"err1: first","stacktrace":"\tapp/log/log_test.go:49 .func1\n\tapp/log/log_test.go:138 .func1","1":1}
+{"level":"error","ts":"00:00","caller":"log/log_test.go:55","msg":"err2: second: first","stacktrace":"\tapp/log/log_test.go:49 .func1\n\tapp/log/log_test.go:138 .func1","2":2,"1":1}
+{"level":"error","ts":"00:00","caller":"log/log_test.go:56","msg":"err3: third: second: first","stacktrace":"\tapp/log/log_test.go:49 .func1\n\tapp/log/log_test.go:138 .func1","3":3,"2":2,"1":1}
diff --git a/app/log/testdata/TestFilterDefault_json.golden b/app/log/testdata/TestFilterDefault_json.golden
index 61055925..8395d957 100644
--- a/app/log/testdata/TestFilterDefault_json.golden
+++ b/app/log/testdata/TestFilterDefault_json.golden
@@ -1 +1 @@
-{"level":"info","ts":"00:00","caller":"log/log_test.go:93","msg":"expect","pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m expect                                  \n"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:93","msg":"expect"}
diff --git a/app/log/testdata/TestFilterNone_json.golden b/app/log/testdata/TestFilterNone_json.golden
index b6530134..e202a478 100644
--- a/app/log/testdata/TestFilterNone_json.golden
+++ b/app/log/testdata/TestFilterNone_json.golden
@@ -1,4 +1,4 @@
-{"level":"info","ts":"00:00","caller":"log/log_test.go:104","msg":"expect1","pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m expect1                                 \n"}
-{"level":"info","ts":"00:00","caller":"log/log_test.go:105","msg":"expect2","pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m expect2                                 \n"}
-{"level":"info","ts":"00:00","caller":"log/log_test.go:106","msg":"expect3","pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m expect3                                 \n"}
-{"level":"info","ts":"00:00","caller":"log/log_test.go:107","msg":"expect4","pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m expect4                                 \n"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:104","msg":"expect1"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:105","msg":"expect2"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:106","msg":"expect3"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:107","msg":"expect4"}
diff --git a/app/log/testdata/TestSentinelStack_json.golden b/app/log/testdata/TestSentinelStack_json.golden
index 383a1481..1729aef4 100644
--- a/app/log/testdata/TestSentinelStack_json.golden
+++ b/app/log/testdata/TestSentinelStack_json.golden
@@ -1 +1 @@
-{"level":"error","ts":"00:00","caller":"log/log_test.go:117","msg":"test: wrap sentinel: test","stacktrace":"\tapp/log/log_test.go:117 .func1\n\tapp/log/log_test.go:138 .func1","pretty":"\u001b[31mERRO\u001b[0m \u001b[32m          \u001b[0m test: wrap sentinel: test               \n"}
+{"level":"error","ts":"00:00","caller":"log/log_test.go:117","msg":"test: wrap sentinel: test","stacktrace":"\tapp/log/log_test.go:117 .func1\n\tapp/log/log_test.go:138 .func1"}
diff --git a/app/log/testdata/TestWithContext_json.golden b/app/log/testdata/TestWithContext_json.golden
index b23df1be..7938fa22 100644
--- a/app/log/testdata/TestWithContext_json.golden
+++ b/app/log/testdata/TestWithContext_json.golden
@@ -1,4 +1,4 @@
-{"level":"debug","ts":"00:00","caller":"log/log_test.go:40","msg":"msg1","ctx1":1,"pretty":"\u001b[35mDEBG\u001b[0m \u001b[32m          \u001b[0m msg1                                     {\"ctx1\": 1}\n"}
-{"level":"info","ts":"00:00","caller":"log/log_test.go:41","msg":"msg2","ctx2":2,"wrap2":2,"pretty":"\u001b[34mINFO\u001b[0m \u001b[32m          \u001b[0m msg2                                     {\"ctx2\": 2, \"wrap2\": 2}\n"}
-{"level":"warn","ts":"00:00","caller":"log/log_test.go:42","msg":"msg3a","wrap3":"a","wrap2":2,"pretty":"\u001b[33mWARN\u001b[0m \u001b[32m          \u001b[0m msg3a                                    {\"wrap3\": \"a\", \"wrap2\": 2}\n"}
-{"level":"warn","ts":"00:00","caller":"log/log_test.go:43","msg":"msg3b","wrap3":"b","wrap2":2,"pretty":"\u001b[33mWARN\u001b[0m \u001b[32m          \u001b[0m msg3b                                    {\"wrap3\": \"b\", \"wrap2\": 2}\n"}
+{"level":"debug","ts":"00:00","caller":"log/log_test.go:40","msg":"msg1","ctx1":1}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:41","msg":"msg2","ctx2":2,"wrap2":2}
+{"level":"warn","ts":"00:00","caller":"log/log_test.go:42","msg":"msg3a","wrap3":"a","wrap2":2}
+{"level":"warn","ts":"00:00","caller":"log/log_test.go:43","msg":"msg3b","wrap3":"b","wrap2":2}
diff --git a/app/log/testdata/TestWithTopic_json.golden b/app/log/testdata/TestWithTopic_json.golden
index 28c4f524..67ae928f 100644
--- a/app/log/testdata/TestWithTopic_json.golden
+++ b/app/log/testdata/TestWithTopic_json.golden
@@ -1,2 +1,2 @@
-{"level":"debug","ts":"00:00","caller":"log/log_test.go:28","msg":"msg1","ctx1":1,"topic":"topic","pretty":"\u001b[35mDEBG\u001b[0m \u001b[32mtopic     \u001b[0m msg1                                     {\"ctx1\": 1}\n"}
-{"level":"info","ts":"00:00","caller":"log/log_test.go:29","msg":"msg2","ctx2":2,"topic":"topic","pretty":"\u001b[34mINFO\u001b[0m \u001b[32mtopic     \u001b[0m msg2                                     {\"ctx2\": 2}\n"}
+{"level":"debug","ts":"00:00","caller":"log/log_test.go:28","msg":"msg1","ctx1":1,"topic":"topic"}
+{"level":"info","ts":"00:00","caller":"log/log_test.go:29","msg":"msg2","ctx2":2,"topic":"topic"}