Skip to content

fix(loki-stream): reconnect when websocket is unexpectedly closed#1287

Merged
pirosiki197 merged 2 commits into
mainfrom
fix/loki-stream
Jun 24, 2026
Merged

fix(loki-stream): reconnect when websocket is unexpectedly closed#1287
pirosiki197 merged 2 commits into
mainfrom
fix/loki-stream

Conversation

@pirosiki197

@pirosiki197 pirosiki197 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

なぜやるか

loki側のtail_max_durationを過ぎても自動で再接続するようにしたい

やったこと

再接続するようにした
ついでにwebsocketのライブラリもメンテナンスされているものに変えた

やらなかったこと

資料

Summary by CodeRabbit

Release Notes

  • Chores(保守)

    • Websocket ライブラリの依存関係を更新しました
  • Refactor(リファクター)

    • Loki のログストリーミング取得ロジックを見直し、受信の継続性やタイムスタンプ更新を改善しました
    • キャンセル時の停止と、逐次エラー発生時の復帰動作をより安定させました

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pirosiki197, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 51 minutes and 30 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 789f2d98-291f-4997-847e-c7f2975a79c7

📥 Commits

Reviewing files that changed from the base of the PR and between 4d122ff and ed89b38.

📒 Files selected for processing (1)
  • pkg/infrastructure/log/loki/streamer.go
📝 Walkthrough

Walkthrough

github.com/shiguredo/websocketgithub.com/coder/websocket v1.8.14 に置き換え、Loki ログストリーマーの websocket 読取ロジックを新設の readStream イテレータ関数へ分離し、Stream メソッドを当該イテレータを反復処理するループ構造へ書き換えた。新実装は lastSeenTime 基準の動的再接続、conn.Read による Text/Binary 分岐処理、context.Done() 連動のブロッキング送信制御を導入している。

Changes

WebSocket ライブラリ移行と Stream イテレータ再実装

Layer / File(s) Summary
依存関係と import の置換
go.mod, pkg/infrastructure/log/loki/streamer.go
go.modgithub.com/shiguredo/websocket を削除し github.com/coder/websocket v1.8.14 を追加。streamer.go の import を新ライブラリと iter パッケージに切り替え。
readStream イテレータ関数の実装
pkg/infrastructure/log/loki/streamer.go
iter.Seq2 を返す新関数 readStream を導入。websocket 接続時にクエリへ start 時刻を含める。conn.Read(ctx) で Text メッセージを json.Unmarshal でデコードして yield で逐次返し、Binary は無視、読取エラーは yield(nil, err) で終了。
Stream メソッドのイテレータ連動書き換え
pkg/infrastructure/log/loki/streamer.go
readStream を反復処理するループへ変更。lastSeenTime より後のログを判定してフィルタ。チャネル送信を ctx.Done() 連動のブロッキング送信へ置き換え。context.Canceled で即 return、その他エラーはバックオフ後に再接続。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed PR タイトルは主な変更内容である websocket の予期しない閉鎖時の自動再接続機能を明確に説明しており、変更内容と一致しています。
Description check ✅ Passed PR の説明はテンプレートの主要セクション(なぜやるか、やったこと、やらなかったこと、資料)をすべて含み、Loki の tail_max_duration 超過時の自動再接続とライブラリの更新について説明しています。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loki-stream

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.

@github-actions

Copy link
Copy Markdown
Contributor

Preview (prod backend + PR dashboard) → https://1287.ns-preview.trapti.tech/

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/infrastructure/log/loki/streamer.go`:
- Around line 134-137: The backoffCount variable is incremented on read errors
(around line 156) but never reset, causing the streaming loop to exit
permanently after 5 disconnections even if those are normal reconnections. To
fix this, reset backoffCount to 0 whenever a message is successfully received
and processed (after handling MessageText), so that only consecutive errors
count toward the maximum backoff limit. Additionally, add a sleep duration
between retry attempts before the next iteration of the for loop checking
backoffCount, implementing actual exponential backoff behavior rather than
immediate retries that would hammer the server.
- Around line 173-181: The unconditional blocking send `ch <- l` in the loop
will cause a goroutine leak when context is canceled. When the channel buffer
(capacity 100) fills up and ctx.Done() is triggered, the send blocks forever and
prevents `defer close(ch)` from executing. Replace the blocking send with a
select statement that includes both the channel send case and a `<-ctx.Done()`
case, allowing the goroutine to exit gracefully and close the channel when the
context is canceled.
- Line 151: The conn.Read call in the streamer.go file uses the default 32KiB
read limit from coder/websocket, which can cause connection failures when Loki
tail batches exceed this size. Before calling conn.Read(ctx), increase the read
limit using conn.SetReadLimit(...) with an appropriate higher value to
accommodate larger Loki batch messages and prevent frequent reconnections due to
read errors.
- Around line 144-158: The WebSocket connection created by websocket.Dial in the
streamer.go file is not being explicitly closed, causing resource leaks. The
conn object needs to be closed in all code paths: when context.Canceled causes
an early return on line 153, and before breaking from the loop on line 157 to
avoid accumulating multiple unclosed connections during reconnect attempts. Use
a defer statement immediately after successfully dialing the WebSocket endpoint,
or ensure conn.Close() or conn.CloseNow() is called before each return and
before the break statement that triggers reconnection attempts. The
coder/websocket v1.8.x library requires explicit closure, so every connection
established by websocket.Dial must be properly cleaned up.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 907543c7-85f0-4e9e-aa31-d50e34a5e0aa

📥 Commits

Reviewing files that changed from the base of the PR and between ef66d80 and f16f29c.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • go.mod
  • pkg/infrastructure/log/loki/streamer.go

Comment thread pkg/infrastructure/log/loki/streamer.go Outdated
Comment thread pkg/infrastructure/log/loki/streamer.go Outdated
Comment thread pkg/infrastructure/log/loki/streamer.go Outdated
continue // fail-safe

for {
typ, b, err := conn.Read(ctx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

What is the default read message size limit in github.com/coder/websocket Conn, and how is it changed?

💡 Result:

The default read message size limit in the github.com/coder/websocket library is 32,768 bytes (32 KB) [1][2][3]. You can change or disable this limit using the SetReadLimit(n int64) method on the connection object [1][3]. Setting this value to -1 will completely disable the read message size limit [1][2][3]. When a message exceeds the configured limit, the connection will be closed with a StatusMessageTooBig error [1][3]. Starting from recent versions, you can also detect this condition by checking if the returned error wraps ErrMessageTooBig [4][1].

Citations:


🏁 Script executed:

find . -name "streamer.go" -type f | head -20

Repository: traPtitech/NeoShowcase

Length of output: 252


🏁 Script executed:

cat -n ./pkg/infrastructure/log/loki/streamer.go | head -160 | tail -30

Repository: traPtitech/NeoShowcase

Length of output: 1119


🏁 Script executed:

cat -n ./pkg/infrastructure/log/loki/streamer.go | sed -n '144,200p'

Repository: traPtitech/NeoShowcase

Length of output: 1641


🏁 Script executed:

grep -n "SetReadLimit\|websocket.Dial" ./pkg/infrastructure/log/loki/streamer.go

Repository: traPtitech/NeoShowcase

Length of output: 243


🏁 Script executed:

head -30 ./pkg/infrastructure/log/loki/streamer.go

Repository: traPtitech/NeoShowcase

Length of output: 742


conn.Read のデフォルト読み取り上限(32768 バイト)に注意してください。

coder/websocketConn はデフォルトで 32KiB の読み取り上限を持ち、これを超えるメッセージは読み取りエラーになりコネクションが切断されます。Loki の tail バッチが大きい場合に頻繁な再接続を誘発する恐れがあります。必要に応じて conn.SetReadLimit(...) で上限を引き上げてください。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/infrastructure/log/loki/streamer.go` at line 151, The conn.Read call in
the streamer.go file uses the default 32KiB read limit from coder/websocket,
which can cause connection failures when Loki tail batches exceed this size.
Before calling conn.Read(ctx), increase the read limit using
conn.SetReadLimit(...) with an appropriate higher value to accommodate larger
Loki batch messages and prevent frequent reconnections due to read errors.

Comment thread pkg/infrastructure/log/loki/streamer.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
pkg/infrastructure/log/loki/streamer.go (1)

142-158: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

レシーバ l をループ変数 l でシャドウしており可読性・保守性を損ねます。

for l, err := range logSeq のループ変数 llokiStreamer レシーバ l を隠蔽しています。現状はループ内でレシーバのメソッドを呼ばないため動作しますが、将来 l.xxx() を追加した際に意図せぬ参照ミスを招きます。ログ要素は log 等へ改名することを推奨します。

♻️ 改名例
-			for l, err := range logSeq {
+			for log, err := range logSeq {
 				if errors.Is(err, context.Canceled) {
 					return
 				} else if err != nil {
 					slog.WarnContext(ctx, "unexpected error occurred while reading log stream", "error", err)
 					break // retry
 				}
 
-				if l.Time.After(lastSeenTime) {
-					lastSeenTime = l.Time
-				}
+				if log.Time.After(lastSeenTime) {
+					lastSeenTime = log.Time
+				}
 				select {
-				case ch <- l:
+				case ch <- log:
 				case <-ctx.Done():
 					return
 				}
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/infrastructure/log/loki/streamer.go` around lines 142 - 158, The loop
variable `l` in the `for l, err := range logSeq` statement shadows the receiver
`l` of the method, creating variable shadowing that reduces code maintainability
and could cause reference errors if receiver methods are added in the future.
Rename the loop variable from `l` to a more descriptive name such as `log` and
update all references to this variable within the loop block (including `l.Time`
which should become `log.Time` and the send operation `ch <- l` which should
become `ch <- log`).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/infrastructure/log/loki/streamer.go`:
- Around line 150-158: In the log streaming logic within the `readStream`
method, logs at or before the `lastSeenTime` are being re-fetched from Loki on
reconnection because the current implementation lacks a filter to skip
already-sent logs. The code currently only updates `lastSeenTime` when a log's
timestamp is after `lastSeenTime`, but does not skip logs with timestamps before
or equal to `lastSeenTime`. Add a condition that checks if
`l.Time.Before(lastSeenTime)` or if the times are equal, and skip sending those
logs (use `continue` to move to the next iteration) before the `select`
statement that sends logs to the channel. This ensures that previously delivered
logs are not duplicated during reconnections triggered by `tail_max_duration`.

---

Nitpick comments:
In `@pkg/infrastructure/log/loki/streamer.go`:
- Around line 142-158: The loop variable `l` in the `for l, err := range logSeq`
statement shadows the receiver `l` of the method, creating variable shadowing
that reduces code maintainability and could cause reference errors if receiver
methods are added in the future. Rename the loop variable from `l` to a more
descriptive name such as `log` and update all references to this variable within
the loop block (including `l.Time` which should become `log.Time` and the send
operation `ch <- l` which should become `ch <- log`).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b35bc372-fa38-4b1d-b135-94c873012c23

📥 Commits

Reviewing files that changed from the base of the PR and between f16f29c and 4d122ff.

📒 Files selected for processing (1)
  • pkg/infrastructure/log/loki/streamer.go

Comment thread pkg/infrastructure/log/loki/streamer.go Outdated
@pirosiki197 pirosiki197 merged commit cf851a1 into main Jun 24, 2026
15 checks passed
@pirosiki197 pirosiki197 deleted the fix/loki-stream branch June 24, 2026 01:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant