【问题标题】:Size control on logging an unknown length of parameters记录未知长度参数的大小控制
【发布时间】:2019-12-12 22:19:43
【问题描述】:

问题:

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

解决方案:

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

卡在哪里:

  1. 我发现很难在磁盘上找到interface{} 的大小。
  2. 如何打印? (有比%v更好的方法吗?)

关注点主要集中在第一部分,如何找到大小,我需要知道类型,如果是数组,堆栈,堆等。

如果代码有帮助,这是我的代码结构(所有内容都位于 util 文件中的 dal pkg 中):

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]”

我可以根据要求提供更多详细信息,谢谢:)

【问题讨论】:

  • 如果我这样做,我会使用 len(fmt.Sprintf(", %v", e)) 作为 size ,但在日志中使用 ` ...` 表示 args 列表不完整
  • 好点,我会记住的谢谢 :)

标签: go


【解决方案1】:

在 Go 中获取任意值(任意数据结构)的内存大小并非不可能,而是“困难”。详情见How to get memory size of variable in Go?

最简单的解决方案可能是生成要记录在内存中的数据,您可以在记录之前简单地截断它(例如,如果它是 string 或字节切片,只需切片它)。然而,这不是最温和的解决方案(速度较慢且需要更多内存)。

相反,我会以不同的方式实现您想要的。我会尝试组合要记录的数据,但我会使用一个特殊的io.Writer 作为目标(可能针对您的磁盘或内存缓冲区),它会跟踪写入它的字节,并且一旦达到限制,它可能会丢弃更多数据(或报告错误,任何适合您的)。

您可以在此处查看计数io.Writer 实现:Size in bits of object encoded 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() 函数写入此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(和内存)资源。

【讨论】:

  • 真的很酷的答案,你解决了我的问题,但现在我需要更改问题的标题,因为它不再适合这个线程:) 已标记并赞成。
猜你喜欢
  • 1970-01-01
  • 2012-12-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-27
  • 1970-01-01
相关资源
最近更新 更多