Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Unverified
Commit
12880281
authored
May 25, 2026
by
Seefs
Committed by
GitHub
May 25, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: truncate oversized upstream error logs (#5083)
parent
2a528d46
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
124 additions
and
7 deletions
+124
-7
common/str.go
+11
-0
controller/relay.go
+2
-2
model/log.go
+1
-1
service/channel.go
+1
-1
service/error.go
+5
-3
service/error_test.go
+104
-0
No files found.
common/str.go
View file @
12880281
...
@@ -3,6 +3,7 @@ package common
...
@@ -3,6 +3,7 @@ package common
import
(
import
(
"encoding/base64"
"encoding/base64"
"encoding/json"
"encoding/json"
"fmt"
"net/url"
"net/url"
"regexp"
"regexp"
"strconv"
"strconv"
...
@@ -20,6 +21,16 @@ var (
...
@@ -20,6 +21,16 @@ var (
maskApiKeyPattern
=
regexp
.
MustCompile
(
`(['"]?)api_key:([^\s'"]+)(['"]?)`
)
maskApiKeyPattern
=
regexp
.
MustCompile
(
`(['"]?)api_key:([^\s'"]+)(['"]?)`
)
)
)
const
LocalLogContentLimit
=
2048
// LocalLogPreview limits log-only content unless debug logging is enabled.
func
LocalLogPreview
(
content
string
)
string
{
if
DebugEnabled
||
len
(
content
)
<=
LocalLogContentLimit
{
return
content
}
return
fmt
.
Sprintf
(
"%s... [truncated, original_length=%d, limit=%d]"
,
content
[
:
LocalLogContentLimit
],
len
(
content
),
LocalLogContentLimit
)
}
func
GetStringIfEmpty
(
str
string
,
defaultValue
string
)
string
{
func
GetStringIfEmpty
(
str
string
,
defaultValue
string
)
string
{
if
str
==
""
{
if
str
==
""
{
return
defaultValue
return
defaultValue
...
...
controller/relay.go
View file @
12880281
...
@@ -88,7 +88,7 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
...
@@ -88,7 +88,7 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
defer
func
()
{
defer
func
()
{
if
newAPIError
!=
nil
{
if
newAPIError
!=
nil
{
logger
.
LogError
(
c
,
fmt
.
Sprintf
(
"relay error: %s"
,
newAPIError
.
Error
(
)))
logger
.
LogError
(
c
,
fmt
.
Sprintf
(
"relay error: %s"
,
common
.
LocalLogPreview
(
newAPIError
.
Error
()
)))
newAPIError
.
SetMessage
(
common
.
MessageWithRequestId
(
newAPIError
.
Error
(),
requestId
))
newAPIError
.
SetMessage
(
common
.
MessageWithRequestId
(
newAPIError
.
Error
(),
requestId
))
switch
relayFormat
{
switch
relayFormat
{
case
types
.
RelayFormatOpenAIRealtime
:
case
types
.
RelayFormatOpenAIRealtime
:
...
@@ -354,7 +354,7 @@ func shouldRetry(c *gin.Context, openaiErr *types.NewAPIError, retryTimes int) b
...
@@ -354,7 +354,7 @@ func shouldRetry(c *gin.Context, openaiErr *types.NewAPIError, retryTimes int) b
}
}
func
processChannelError
(
c
*
gin
.
Context
,
channelError
types
.
ChannelError
,
err
*
types
.
NewAPIError
)
{
func
processChannelError
(
c
*
gin
.
Context
,
channelError
types
.
ChannelError
,
err
*
types
.
NewAPIError
)
{
logger
.
LogError
(
c
,
fmt
.
Sprintf
(
"channel error (channel #%d, status code: %d): %s"
,
channelError
.
ChannelId
,
err
.
StatusCode
,
err
.
Error
(
)))
logger
.
LogError
(
c
,
fmt
.
Sprintf
(
"channel error (channel #%d, status code: %d): %s"
,
channelError
.
ChannelId
,
err
.
StatusCode
,
common
.
LocalLogPreview
(
err
.
Error
()
)))
// 不要使用context获取渠道信息,异步处理时可能会出现渠道信息不一致的情况
// 不要使用context获取渠道信息,异步处理时可能会出现渠道信息不一致的情况
// do not use context to get channel info, there may be inconsistent channel info when processing asynchronously
// do not use context to get channel info, there may be inconsistent channel info when processing asynchronously
if
service
.
ShouldDisableChannel
(
err
)
&&
channelError
.
AutoBan
{
if
service
.
ShouldDisableChannel
(
err
)
&&
channelError
.
AutoBan
{
...
...
model/log.go
View file @
12880281
...
@@ -145,7 +145,7 @@ func RecordTopupLog(userId int, content string, callerIp string, paymentMethod s
...
@@ -145,7 +145,7 @@ func RecordTopupLog(userId int, content string, callerIp string, paymentMethod s
func
RecordErrorLog
(
c
*
gin
.
Context
,
userId
int
,
channelId
int
,
modelName
string
,
tokenName
string
,
content
string
,
tokenId
int
,
useTimeSeconds
int
,
func
RecordErrorLog
(
c
*
gin
.
Context
,
userId
int
,
channelId
int
,
modelName
string
,
tokenName
string
,
content
string
,
tokenId
int
,
useTimeSeconds
int
,
isStream
bool
,
group
string
,
other
map
[
string
]
interface
{})
{
isStream
bool
,
group
string
,
other
map
[
string
]
interface
{})
{
logger
.
LogInfo
(
c
,
fmt
.
Sprintf
(
"record error log: userId=%d, channelId=%d, modelName=%s, tokenName=%s, content=%s"
,
userId
,
channelId
,
modelName
,
tokenName
,
co
ntent
))
logger
.
LogInfo
(
c
,
fmt
.
Sprintf
(
"record error log: userId=%d, channelId=%d, modelName=%s, tokenName=%s, content=%s"
,
userId
,
channelId
,
modelName
,
tokenName
,
co
mmon
.
LocalLogPreview
(
content
)
))
username
:=
c
.
GetString
(
"username"
)
username
:=
c
.
GetString
(
"username"
)
requestId
:=
c
.
GetString
(
common
.
RequestIdKey
)
requestId
:=
c
.
GetString
(
common
.
RequestIdKey
)
upstreamRequestId
:=
c
.
GetString
(
common
.
UpstreamRequestIdKey
)
upstreamRequestId
:=
c
.
GetString
(
common
.
UpstreamRequestIdKey
)
...
...
service/channel.go
View file @
12880281
...
@@ -17,7 +17,7 @@ func formatNotifyType(channelId int, status int) string {
...
@@ -17,7 +17,7 @@ func formatNotifyType(channelId int, status int) string {
// disable & notify
// disable & notify
func
DisableChannel
(
channelError
types
.
ChannelError
,
reason
string
)
{
func
DisableChannel
(
channelError
types
.
ChannelError
,
reason
string
)
{
common
.
SysLog
(
fmt
.
Sprintf
(
"通道「%s」(#%d)发生错误,准备禁用,原因:%s"
,
channelError
.
ChannelName
,
channelError
.
ChannelId
,
reason
))
common
.
SysLog
(
fmt
.
Sprintf
(
"通道「%s」(#%d)发生错误,准备禁用,原因:%s"
,
channelError
.
ChannelName
,
channelError
.
ChannelId
,
common
.
LocalLogPreview
(
reason
)
))
// 检查是否启用自动禁用功能
// 检查是否启用自动禁用功能
if
!
channelError
.
AutoBan
{
if
!
channelError
.
AutoBan
{
...
...
service/error.go
View file @
12880281
...
@@ -92,11 +92,13 @@ func RelayErrorHandler(ctx context.Context, resp *http.Response, showBodyWhenFai
...
@@ -92,11 +92,13 @@ func RelayErrorHandler(ctx context.Context, resp *http.Response, showBodyWhenFai
}
}
CloseResponseBodyGracefully
(
resp
)
CloseResponseBodyGracefully
(
resp
)
var
errResponse
dto
.
GeneralErrorResponse
var
errResponse
dto
.
GeneralErrorResponse
responseBodyText
:=
string
(
responseBody
)
responseBodyPreview
:=
common
.
LocalLogPreview
(
responseBodyText
)
buildErrWithBody
:=
func
(
message
string
)
error
{
buildErrWithBody
:=
func
(
message
string
)
error
{
if
message
==
""
{
if
message
==
""
{
return
fmt
.
Errorf
(
"bad response status code %d, body: %s"
,
resp
.
StatusCode
,
string
(
responseBody
)
)
return
fmt
.
Errorf
(
"bad response status code %d, body: %s"
,
resp
.
StatusCode
,
responseBodyText
)
}
}
return
fmt
.
Errorf
(
"bad response status code %d, message: %s, body: %s"
,
resp
.
StatusCode
,
message
,
string
(
responseBody
)
)
return
fmt
.
Errorf
(
"bad response status code %d, message: %s, body: %s"
,
resp
.
StatusCode
,
message
,
responseBodyText
)
}
}
err
=
common
.
Unmarshal
(
responseBody
,
&
errResponse
)
err
=
common
.
Unmarshal
(
responseBody
,
&
errResponse
)
...
@@ -104,7 +106,7 @@ func RelayErrorHandler(ctx context.Context, resp *http.Response, showBodyWhenFai
...
@@ -104,7 +106,7 @@ func RelayErrorHandler(ctx context.Context, resp *http.Response, showBodyWhenFai
if
showBodyWhenFail
{
if
showBodyWhenFail
{
newApiErr
.
Err
=
buildErrWithBody
(
""
)
newApiErr
.
Err
=
buildErrWithBody
(
""
)
}
else
{
}
else
{
logger
.
LogError
(
ctx
,
fmt
.
Sprintf
(
"bad response status code %d, body: %s"
,
resp
.
StatusCode
,
string
(
responseBody
)
))
logger
.
LogError
(
ctx
,
fmt
.
Sprintf
(
"bad response status code %d, body: %s"
,
resp
.
StatusCode
,
responseBodyPreview
))
newApiErr
.
Err
=
fmt
.
Errorf
(
"bad response status code %d"
,
resp
.
StatusCode
)
newApiErr
.
Err
=
fmt
.
Errorf
(
"bad response status code %d"
,
resp
.
StatusCode
)
}
}
return
return
...
...
service/error_test.go
View file @
12880281
package
service
package
service
import
(
import
(
"bytes"
"context"
"fmt"
"io"
"net/http"
"strings"
"testing"
"testing"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/types"
"github.com/QuantumNous/new-api/types"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
)
)
...
@@ -55,3 +63,99 @@ func TestResetStatusCode(t *testing.T) {
...
@@ -55,3 +63,99 @@ func TestResetStatusCode(t *testing.T) {
})
})
}
}
}
}
func
TestRelayErrorHandlerTruncatesInvalidJSONBodyInLog
(
t
*
testing
.
T
)
{
withDebugEnabled
(
t
,
false
)
body
:=
strings
.
Repeat
(
"b"
,
common
.
LocalLogContentLimit
+
256
)
var
logBuffer
bytes
.
Buffer
common
.
LogWriterMu
.
Lock
()
oldWriter
:=
gin
.
DefaultErrorWriter
gin
.
DefaultErrorWriter
=
&
logBuffer
common
.
LogWriterMu
.
Unlock
()
t
.
Cleanup
(
func
()
{
common
.
LogWriterMu
.
Lock
()
gin
.
DefaultErrorWriter
=
oldWriter
common
.
LogWriterMu
.
Unlock
()
})
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusInternalServerError
,
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
body
)),
}
newAPIError
:=
RelayErrorHandler
(
context
.
Background
(),
resp
,
false
)
require
.
NotNil
(
t
,
newAPIError
)
require
.
Equal
(
t
,
"bad response status code 500"
,
newAPIError
.
Error
())
require
.
Contains
(
t
,
logBuffer
.
String
(),
"[truncated"
)
require
.
Contains
(
t
,
logBuffer
.
String
(),
fmt
.
Sprintf
(
"original_length=%d"
,
len
(
body
)))
require
.
NotContains
(
t
,
logBuffer
.
String
(),
strings
.
Repeat
(
"b"
,
common
.
LocalLogContentLimit
+
1
))
}
func
TestRelayErrorHandlerKeepsStructuredErrorMessage
(
t
*
testing
.
T
)
{
message
:=
strings
.
Repeat
(
"c"
,
common
.
LocalLogContentLimit
+
256
)
body
:=
`{"message":"`
+
message
+
`"}`
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusInternalServerError
,
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
body
)),
}
newAPIError
:=
RelayErrorHandler
(
context
.
Background
(),
resp
,
false
)
require
.
NotNil
(
t
,
newAPIError
)
require
.
Equal
(
t
,
message
,
newAPIError
.
Error
())
}
func
TestRelayErrorHandlerKeepsOpenAIErrorMessage
(
t
*
testing
.
T
)
{
message
:=
strings
.
Repeat
(
"d"
,
common
.
LocalLogContentLimit
+
256
)
body
:=
`{"error":{"message":"`
+
message
+
`","type":"server_error","code":"server_error"}}`
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusInternalServerError
,
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
body
)),
}
newAPIError
:=
RelayErrorHandler
(
context
.
Background
(),
resp
,
false
)
require
.
NotNil
(
t
,
newAPIError
)
require
.
Equal
(
t
,
message
,
newAPIError
.
Error
())
}
func
TestRelayErrorHandlerKeepsInvalidJSONBodyInDebugLog
(
t
*
testing
.
T
)
{
withDebugEnabled
(
t
,
true
)
body
:=
strings
.
Repeat
(
"e"
,
common
.
LocalLogContentLimit
+
256
)
var
logBuffer
bytes
.
Buffer
common
.
LogWriterMu
.
Lock
()
oldWriter
:=
gin
.
DefaultErrorWriter
gin
.
DefaultErrorWriter
=
&
logBuffer
common
.
LogWriterMu
.
Unlock
()
t
.
Cleanup
(
func
()
{
common
.
LogWriterMu
.
Lock
()
gin
.
DefaultErrorWriter
=
oldWriter
common
.
LogWriterMu
.
Unlock
()
})
resp
:=
&
http
.
Response
{
StatusCode
:
http
.
StatusInternalServerError
,
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
body
)),
}
newAPIError
:=
RelayErrorHandler
(
context
.
Background
(),
resp
,
false
)
require
.
NotNil
(
t
,
newAPIError
)
require
.
NotContains
(
t
,
logBuffer
.
String
(),
"[truncated"
)
require
.
Contains
(
t
,
logBuffer
.
String
(),
body
)
}
func
withDebugEnabled
(
t
*
testing
.
T
,
enabled
bool
)
{
t
.
Helper
()
oldDebug
:=
common
.
DebugEnabled
common
.
DebugEnabled
=
enabled
t
.
Cleanup
(
func
()
{
common
.
DebugEnabled
=
oldDebug
})
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment