Skip to content

Conversation

@gam6itko
Copy link

@gam6itko gam6itko commented Sep 25, 2025

Reason for This PR

Currently, the logs contain multiple duplicate "kgo_driver" keys. It's difficult to read or parse such logs.

image

Example of "hard to understand what's going on" log:

{
"level":"debug",
"logger": "kafka.kgo",
"msg":"read Fetch v15",
"kgo_driver":"broker",
"kgo_driver":"1",
"kgo_driver":"bytes_read",
"kgo_driver":21,
"kgo_driver":"read_wait",
"kgo_driver":0.000033985,
"kgo_driver":"time_to_read",
"kgo_driver":0.000234346,
"kgo_driver":"err",
"kgo_driver":null
}

Description of Changes

I simply add key-value splitter to Log function and some tests.

Now we will have pretty logs like:

{
"level": "debug",
"logger": "kafka.kgo",
"msg": "read Fetch v15",
"broker": "1",
"bytes_read": 21,
"read_wait": 0.000033985,
"time_to_read": 0.000234346,
"err": null
}

License Acceptance

By submitting this pull request, I confirm that my contribution is made under
the terms of the MIT license.

PR Checklist

[Author TODO: Meet these criteria.]
[Reviewer TODO: Verify that these criteria are met. Request changes if not]

  • All commits in this PR are signed (git commit -s).
  • The reason for this PR is clearly provided (issue no. or explanation).
  • The description of changes is clear and encompassing.
  • Any required documentation changes (code and docs) are included in this PR.
  • Any user-facing changes are mentioned in CHANGELOG.md.
  • All added/changed functionality is tested.

Summary by CodeRabbit

  • Bug Fixes

    • Ensures structured logs when key/value inputs are uneven or mixed by assigning sequential valN keys and returning no fields for empty input.
    • Produces clearer, consistent log fields instead of opaque wrappers while preserving log level behavior.
  • Refactor

    • Simplified and centralized log field construction for cleaner, more reliable output.
  • Tests

    • Added unit tests covering nil inputs, authentication messages, uneven pairs, and mixed types to validate log formatting.

@coderabbitai
Copy link

coderabbitai bot commented Sep 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds helper toKeyValuePair to convert variadic keyvals into []zapcore.Field with deterministic fallback naming; Log now uses that helper to build fields. Adds unit tests verifying behavior for nil, even string-key pairs, odd-length inputs, and mixed-type inputs.

Changes

Cohort / File(s) Summary
Logging logic update
kafkajobs/logger.go
Added toKeyValuePair(keyvals []any) []zapcore.Field which returns nil for empty input; when length is even and keys are strings it returns zap.Any(key, value) pairs, otherwise it returns fallback fields named val0, val1, ... using zap.Any. Replaced prior per-item wrapping in Log with a single call to this helper and pass resulting fields to the underlying zap logger. Added fmt import for fallback naming.
Unit tests for logger
kafkajobs/logger_test.go
New tests for Log using zaptest/observer: cases for keyvals nil, even string-key pairs (sasl auth fields), odd-length inputs, and mixed-type inputs; asserts emitted zapcore.Field slices with testify/assert.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant Logger
  participant Helper as toKeyValuePair
  participant Zap as zap.Logger

  Caller->>Logger: Log(level, msg, keyvals...)
  Logger->>Helper: toKeyValuePair(keyvals)
  alt keyvals empty
    Helper-->>Logger: nil
    Logger->>Zap: log(level, msg)
  else even-length && keys are strings
    Helper-->>Logger: fields (zap.Any key/value)
    Logger->>Zap: log(level, msg, fields)
  else fallback (odd-length or non-string keys)
    Helper-->>Logger: fields (val0, val1, ...)
    Logger->>Zap: log(level, msg, fields)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I hopped through keys, one by one,
Pairing names until the work was done.
When stray values wandered in the night,
I tagged them "val0" till morning light.
A little rabbit logged it right. 🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The current title attempts to describe the key‐value splitting feature but contains a grammatical error (“an value” instead of “and value”) and is not phrased in a concise, clear manner that immediately conveys the change. Please revise the title to be a clear, concise summary of the main change, for example “Split kgo_driver log keys and values” or “kgo_driver: split log keys and values.”
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The description properly follows the repository’s template by including the “Reason for This PR,” “Description of Changes,” “License Acceptance,” and “PR Checklist” sections with relevant details and examples.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2ede79 and e55440c.

📒 Files selected for processing (1)
  • kafkajobs/logger.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • kafkajobs/logger.go

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gam6itko gam6itko marked this pull request as draft September 25, 2025 10:52
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (7)
kafkajobs/logger.go (3)

37-37: Unify on zap.Field for clarity.

Declare fields as []zap.Field (zap’s alias) to avoid mixing zapcore.Field/zap.Field.

-	var zf []zapcore.Field
+	var zf []zap.Field

42-45: Minor loop readability nit.

Iterate values directly; avoids indexing boilerplate.

-		for i := range keyvals {
-			zf = append(zf, zap.Any("kgo_driver", keyvals[i]))
-		}
+		for _, v := range keyvals {
+			zf = append(zf, zap.Any("kgo_driver", v))
+		}

62-79: Return []zap.Field from helper to keep types consistent.

The function already builds []zap.Field; returning []zap.Field tightens the API and matches the logger call sites.

-func tryFetchKeyValPairs(keyvals []any) ([]zapcore.Field, bool) {
+func tryFetchKeyValPairs(keyvals []any) ([]zap.Field, bool) {

Optional: consider a more forgiving parse that:

  • parses the longest valid prefix of key/value pairs, and
  • returns any leftover values to be appended under "kgo_driver".
    This prevents losing structured context when the input is “mostly” pairs but ends odd.
kafkajobs/logger_test.go (4)

22-25: Fix typo in test name.

Spelling nit: “keycals” → “keyvals”.

-			name:     "keycals is nil",
+			name:     "keyvals is nil",

55-64: Isolate observer per subtest.

Create a fresh observer/core inside each t.Run to avoid cross-test coupling and make failures easier to diagnose. Also assert length after TakeAll.

-	core, o := observer.New(zapcore.DebugLevel)
-	for _, c := range cases {
-		t.Run(c.name, func(t *testing.T) {
-			newLogger(zap.New(core)).
-				Log(kgo.LogLevelDebug, c.name, c.keyvals...)
-			assert.Equal(t, 1, o.Len())
-			logs := o.TakeAll()
-			assert.Equal(t, c.expected, logs[0].Context)
-		})
-	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			core, o := observer.New(zapcore.DebugLevel)
+			newLogger(zap.New(core)).Log(kgo.LogLevelDebug, c.name, c.keyvals...)
+			logs := o.TakeAll()
+			require.Len(t, logs, 1)
+			assert.Equal(t, c.expected, logs[0].Context)
+		})
+	}

44-45: Clarify test name.

“no strings” → “no string keys” better reflects the scenario.

-			name:    "no strings",
+			name:    "no string keys",

67-111: Add a case for partial pairs (odd-length with valid prefix).

To lock in behavior when input is “mostly pairs” but ends with a trailing value, add a test e.g.: []any{"k1","v1","k2"} expecting fields for k1/v1 and a single kgo_driver for "k2" if you adopt the partial-parse approach suggested in code review.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92eda72 and 6e055e5.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • kafkajobs/logger.go (2 hunks)
  • kafkajobs/logger_test.go (1 hunks)
🔇 Additional comments (1)
kafkajobs/logger.go (1)

39-41: Good call: structured key/value parsing path.

Using a dedicated helper to parse key/value pairs makes logs parseable and avoids duplicate "kgo_driver" fields.

Comment on lines 39 to 45
for i := range keyvals {
zf = append(zf, zap.Any("kgo_driver", keyvals[i]))
if zfTmp, ok := tryFetchKeyValPairs(keyvals); ok {
zf = zfTmp
} else {
zf = make([]zap.Field, 0, len(keyvals))
for i := range keyvals {
zf = append(zf, zap.Any("kgo_driver", keyvals[i]))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that you don't need to do anything here, but just remove kgo_driver part completely to have a clean logs:

	for i := range keyvals {
		zf = append(zf, zap.Any("kgo_driver", keyvals[i]))
	}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we can be completely sure that keyvals always have pairs?
Like {"key1", val1, "key2": val2, ...}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may double-check that, but I think that you should always have an even number of fields. If not, you may always add some placeholder at the beginning/end of the sequence and apply the same algorithm for an even number of vals.
You may also look at the kgo/logger.go for the BasicLogger and implement a writer interface for our logger and pass it to the kgo BasicLogger.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Yeah, it should be in the key: value format. So you can always expect an even number of keys (but with a double check to avoid panicking on out-of-bound access).

@gam6itko
Copy link
Author

@rustatian PR in draft because I going to build RR with velox and my PR to test it.

@rustatian rustatian requested a review from Copilot September 25, 2025 14:34
@rustatian rustatian added the enhancement New feature or request label Sep 25, 2025
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes duplicate "kgo_driver" keys in logging output from the Kafka Franz-go library. The implementation adds key-value pair parsing to split structured log entries instead of using a single "kgo_driver" key for all values.

  • Introduces tryFetchKeyValPairs function to parse alternating key-value pairs from log arguments
  • Updates the Log method to attempt structured parsing first, falling back to the original behavior
  • Adds comprehensive unit tests for both the logger and the new parsing functionality

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
kafkajobs/logger_test.go New test file with comprehensive test cases for logger functionality and key-value parsing
kafkajobs/logger.go Updates Log method to parse key-value pairs and adds tryFetchKeyValPairs helper function
go.work.sum Updates to dependency checksums (not directly related to the core change)

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

@rustatian rustatian changed the title kgo_driver log split key an value feature: kgo_driver log split key an value Sep 25, 2025
@gam6itko
Copy link
Author

OK. I've build RR with velox and my branch LGTM

image

@gam6itko gam6itko requested a review from rustatian September 26, 2025 09:41
@gam6itko gam6itko marked this pull request as ready for review September 26, 2025 09:49
@rustatian rustatian requested a review from Copilot September 26, 2025 09:50
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
kafkajobs/logger.go (1)

84-89: Clarify the fallback comment to reflect reality.

This branch intentionally handles odd-length or non-string-key inputs (and the new tests cover it), so labeling it “should never happen” is misleading. Please update the comment to describe the fallback behavior.

-	// This should never happen.
+	// Fallback when keyvals are not well-formed key/value pairs.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e055e5 and a2ede79.

📒 Files selected for processing (2)
  • kafkajobs/logger.go (3 hunks)
  • kafkajobs/logger_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • kafkajobs/logger_test.go
🔇 Additional comments (1)
kafkajobs/logger.go (1)

61-81: Good defensive handling of key/value slices.

Nice to see the even-length gate and string-key validation before constructing the zap fields; that keeps us aligned with franz-go’s expectations while preserving the original key names.

Comment on lines +56 to +59
inLen := len(keyvals)
if inLen == 0 {
return nil
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not call checks for the function to be executed inside the callee function. It is better to check that condition before entering the function.

Comment on lines +64 to +81
keys := make([]string, 0, inLen/2)
for i := 0; i < inLen; i += 2 {
if key, ok := keyvals[i].(string); ok {
keys = append(keys, key)
} else {
isKVP = false
break
}
}

// positive scenario
if isKVP {
result := make([]zap.Field, inLen/2)
for i, key := range keys {
result[i] = zap.Any(key, keyvals[i*2+1])
}
return result
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the hot place, don't iterate twice here (keys check + keys add to the result).
Remove isKVP, because if by some reason the number is odd, as I suggested, just add some placeholder at the end of the sequence with some predefined text.
Preallocate result slice and while iterating - add key-value pairs to the result.
Do not cast values to string type, it does not matter since you're using zap.Any.

@rustatian rustatian marked this pull request as draft October 9, 2025 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants