@@ -3,6 +3,8 @@ package proxy
33import (
44 "fmt"
55 "log/slog"
6+ "reflect"
7+ "strings"
68 "sync"
79 "time"
810
@@ -143,20 +145,106 @@ func cmdNameFromArgs(args [][]byte) string {
143145// truncateValue formats a value for logging/Sentry, truncating to avoid data leakage and oversized events.
144146// Handles common types by slicing before formatting to avoid allocating the full string representation.
145147func truncateValue (v any ) string {
146- var s string
147148 switch tv := v .(type ) {
148149 case string :
149- s = tv
150+ return truncateString ( tv )
150151 case []byte :
152+ // Avoid converting an arbitrarily large byte slice into a full string.
151153 if len (tv ) > maxSentryValueLen {
152154 return string (tv [:maxSentryValueLen ]) + "...(truncated)"
153155 }
154156 return string (tv )
157+ case fmt.Stringer :
158+ // Respect custom String implementations but still apply length limits.
159+ return truncateString (tv .String ())
155160 default :
156- s = fmt .Sprintf ("%v" , v )
161+ rv := reflect .ValueOf (v )
162+ switch rv .Kind () {
163+ case reflect .Slice , reflect .Array :
164+ return formatSliceValue (rv , maxSentryValueLen )
165+ case reflect .Map :
166+ return formatMapValue (rv , maxSentryValueLen )
167+ default :
168+ // For non-container types, fall back to fmt and then truncate.
169+ return truncateString (fmt .Sprintf ("%v" , v ))
170+ }
157171 }
172+ }
173+
174+ // truncateString enforces maxSentryValueLen on an already-built string.
175+ func truncateString (s string ) string {
158176 if len (s ) > maxSentryValueLen {
159177 return s [:maxSentryValueLen ] + "...(truncated)"
160178 }
161179 return s
162180}
181+
182+ // formatSliceValue formats a slice/array value without allocating an unbounded string.
183+ // It stops once approximately maxLen bytes have been written and appends a truncation marker.
184+ func formatSliceValue (rv reflect.Value , maxLen int ) string {
185+ var b strings.Builder
186+ b .WriteByte ('[' )
187+ for i := 0 ; i < rv .Len (); i ++ {
188+ if b .Len () >= maxLen {
189+ b .WriteString ("...(truncated)]" )
190+ return b .String ()
191+ }
192+ if i > 0 {
193+ b .WriteString (", " )
194+ }
195+ elemStr := truncateValue (rv .Index (i ).Interface ())
196+ if b .Len ()+ len (elemStr ) > maxLen {
197+ // Write as much as fits, then mark as truncated.
198+ remaining := maxLen - b .Len ()
199+ if remaining > 0 {
200+ if remaining < len (elemStr ) {
201+ b .WriteString (elemStr [:remaining ])
202+ } else {
203+ b .WriteString (elemStr )
204+ }
205+ }
206+ b .WriteString ("...(truncated)]" )
207+ return b .String ()
208+ }
209+ b .WriteString (elemStr )
210+ }
211+ b .WriteByte (']' )
212+ return truncateString (b .String ())
213+ }
214+
215+ // formatMapValue formats a map value without allocating an unbounded string.
216+ // It stops once approximately maxLen bytes have been written and appends a truncation marker.
217+ func formatMapValue (rv reflect.Value , maxLen int ) string {
218+ var b strings.Builder
219+ b .WriteByte ('{' )
220+ iter := rv .MapRange ()
221+ first := true
222+ for iter .Next () {
223+ if b .Len () >= maxLen {
224+ b .WriteString ("...(truncated)}" )
225+ return b .String ()
226+ }
227+ if ! first {
228+ b .WriteString (", " )
229+ }
230+ first = false
231+ keyStr := truncateValue (iter .Key ().Interface ())
232+ valStr := truncateValue (iter .Value ().Interface ())
233+ entry := fmt .Sprintf ("%s: %s" , keyStr , valStr )
234+ if b .Len ()+ len (entry ) > maxLen {
235+ remaining := maxLen - b .Len ()
236+ if remaining > 0 {
237+ if remaining < len (entry ) {
238+ b .WriteString (entry [:remaining ])
239+ } else {
240+ b .WriteString (entry )
241+ }
242+ }
243+ b .WriteString ("...(truncated)}" )
244+ return b .String ()
245+ }
246+ b .WriteString (entry )
247+ }
248+ b .WriteByte ('}' )
249+ return truncateString (b .String ())
250+ }
0 commit comments