go - 记录未知长度参数的大小控制
问题描述
问题:
现在,我正在记录我的 SQL 查询和与该查询相关的 args,但是如果我的 args 很重会发生什么?说100MB?
解决方案:
我想遍历 args,一旦它们超过 0.5MB,我想将 args 带到这一点并且只记录它们(当然我将使用实际 SQL 查询中设置的整个 args)。
卡在哪里:
- 我发现很难在磁盘上找到
interface{}
. - 我怎样才能打印它?(有比这更好的方法
%v
吗?)
关注点主要集中在第一部分,如何找到大小,我需要知道类型,如果是数组、堆栈、堆等。
如果代码有帮助,这是我的代码结构(所有内容都位于 dal pkg 的 util 文件中):
package dal
import (
"fmt"
)
const limitedLogArgsSizeB = 100000 // ~ 0.1MB
func parsedArgs(args ...interface{}) string {
currentSize := 0
var res string
for i := 0; i < len(args); i++ {
currentEleSize := getSizeOfElement(args[i])
if !(currentSize+currentEleSize =< limitedLogArgsSizeB) {
break
}
currentSize += currentEleSize
res = fmt.Sprintf("%s, %v", res, args[i])
}
return "[" + res + "]"
}
func getSizeOfElement(interface{}) (sizeInBytes int) {
}
如您所见,我希望从 parsedArgs() 中返回一个字符串,如下所示:
“[4378233, 33, 真]”
为了完整起见,附带的查询:
INSERT INTO Person (id,age,is_healthy) VALUES ($0,$1,$2)
所以为了证明这一切的意义:
假设前两个 args 完全等于我要记录的大小限制的阈值,我只会从 parsedArgs() 中返回前两个 args 作为字符串,如下所示:
“[4378233, 33]”
我可以根据要求提供更多详细信息,谢谢:)
解决方案
在 Go 中获取任意值(任意数据结构)的内存大小并非不可能,而是“困难”。有关详细信息,请参阅如何在 Go 中获取变量的内存大小?
最简单的解决方案可能是生成要记录在内存中的数据,您可以在记录之前简单地截断它(例如,如果它是一个string
或一个字节切片,只需对其进行切片)。然而,这不是最温和的解决方案(更慢并且需要更多内存)。
相反,我会以不同的方式实现你想要的。我会尝试组装要记录的数据,但我会使用一个特殊io.Writer
的目标(可能针对您的磁盘或内存缓冲区),它跟踪写入它的字节,并且一旦限制达到,它可能会丢弃更多数据(或报告错误,无论适合您)。
你可以在这里看到一个计数io.Writer
实现:Size in bits of object encoding to JSON?
type CounterWr struct {
io.Writer
Count int
}
func (cw *CounterWr) Write(p []byte) (n int, err error) {
n, err = cw.Writer.Write(p)
cw.Count += n
return
}
我们可以轻松地将其更改为功能受限的编写器:
type LimitWriter struct {
io.Writer
Remaining int
}
func (lw *LimitWriter) Write(p []byte) (n int, err error) {
if lw.Remaining == 0 {
return 0, io.EOF
}
if lw.Remaining < len(p) {
p = p[:lw.Remaining]
}
n, err = lw.Writer.Write(p)
lw.Remaining -= n
return
}
您可以使用fmt.FprintXXX()
函数写入 this 的值LimitWriter
。
写入内存缓冲区的示例:
buf := &bytes.Buffer{}
lw := &LimitWriter{
Writer: buf,
Remaining: 20,
}
args := []interface{}{1, 2, "Looooooooooooong"}
fmt.Fprint(lw, args)
fmt.Printf("%d %q", buf.Len(), buf)
这将输出(在Go Playground上尝试):
20 "[1 2 Looooooooooooon"
如您所见,我们LimitWriter
只允许写入 20 个字节(LimitWriter.Remaining
),其余的被丢弃。
请注意,在此示例中,我将数据组装在内存缓冲区中,但在您的日志记录系统中,您可以直接写入日志流,只需将其包装进去LimitWriter
(这样您就可以完全省略内存缓冲区)。
优化提示:如果您将参数作为切片,您可以使用循环优化截断的渲染,并在达到限制后停止打印参数。
这样做的一个例子:
buf := &bytes.Buffer{}
lw := &LimitWriter{
Writer: buf,
Remaining: 20,
}
args := []interface{}{1, 2, "Loooooooooooooooong", 3, 4, 5}
io.WriteString(lw, "[")
for i, v := range args {
if _, err := fmt.Fprint(lw, v, " "); err != nil {
fmt.Printf("Breaking at argument %d, err: %v\n", i, err)
break
}
}
io.WriteString(lw, "]")
fmt.Printf("%d %q", buf.Len(), buf)
输出(在Go Playground上试试):
Breaking at argument 3, err: EOF
20 "[1 2 Loooooooooooooo"
这样做的好处是,一旦达到限制,我们就不必生成无论如何都会被丢弃的剩余参数的字符串表示,从而节省了一些 CPU(和内存)资源。
推荐阅读
- c++ - 我对将值分配给 std::array 的方式有一些疑问
- python - 在 gensim 中创建一个新的向量模型
- ios - 使用 Chameleon 的结果设置 UIView.backgroundColor 时出现意外 nil
- vue.js - 如何将ui组件(物化步进器)添加到vue项目中?
- javascript - 如何只实现一次 firebase 并在其他脚本中使用它,而不是在我需要在 javscript 中的每个脚本中进行初始化?
- java - 使用 TableColumnModelListener 时如何知道哪些列已调整大小
- amazon-web-services - 如何从 Sagemaker 读取 AWS S3 图像进行处理
- react-native - 当我不使用异步存储时收到“已提取异步存储”警告
- excel - 当我更改引用的单元格时,自动记录整行单元格(引用其他单元格)
- slick - 在 Slick 中更新时隐式更新某些字段?