首页 > 解决方案 > 记录未知长度参数的大小控制

问题描述

问题:

现在,我正在记录我的 SQL 查询和与该查询相关的 args,但是如果我的 args 很重会发生什么?说100MB?

解决方案:

我想遍历 args,一旦它们超过 0.5MB,我想将 args 带到这一点并且只记录它们(当然我将使用实际 SQL 查询中设置的整个 args)。

卡在哪里:

  1. 我发现很难在磁盘上找到interface{}.
  2. 我怎样才能打印它?(有比这更好的方法%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 中获取任意值(任意数据结构)的内存大小并非不可能,而是“困难”。有关详细信息,请参阅如何在 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(和内存)资源。


推荐阅读