【问题标题】:What's the simplest way to log a single field from a set of structs?从一组结构中记录单个字段的最简单方法是什么?
【发布时间】:2021-04-15 16:42:34
【问题描述】:

假设我有一个接受 Person 切片的函数:

type Person struct {
    ID              uuid.UUID   
    FirstName       string
    LastName        string
    ... plus 20 more fields
}

在记录时,我可能只想记录 ID。有没有一种简单的方法可以在不创建另一种类型的情况下做到这一点?我正在使用Logrus。如果我在 JS 中,我只会使用 map 函数。示例记录器行:

logger.Debugf("personProcessor starting for people: %v", persons)

但这会导致我的日志中有很多不必要的输出。 ID 足以找到我们正在处理的人。

【问题讨论】:

  • 我不认为有一个内置的,所以你必须自己写。您可以使用反射,因此它适用于所有结构类型。
  • 很多时候“Go 中最简单的方法是什么”问题实际上是在问“有没有比 for 循环更短的解决方案”,答案通常是“不,或者将 for 循环放在你不想看到它的功能”。我不确定这里是不是这样,但作为一个有经验的程序员,在 for 循环中实现它可能不会有太多麻烦。
  • 对,我的意思是我确信我能做到,但我不想为了让调试日志看起来更好而添加 3 行以上的代码。我不喜欢这种权衡。

标签: go


【解决方案1】:

使用反射包编写一个用于提取字段的“通用”函数:

func extractField(s interface{}, name string) []interface{} {
    var result []interface{}
    v := reflect.ValueOf(s)
    for i := 0; i < v.Len(); i++ {
        result = append(result, reflect.Indirect(v.Index(i)).FieldByName(name).Interface())
    }
    return result
}

像这样使用它:

logger.Debugf("personProcessor starting for people: %v", extractField(persons, "ID"))

Run an example on the playground

该功能可以扩展为包括格式化花里胡哨:

// sprintFields prints the field name in slice of struct s 
// using the specified format.
func sprintFields(s interface{}, name string, sep string, format string) string {
    var fields []string
    v := reflect.ValueOf(s)
    for i := 0; i < v.Len(); i++ {
        fields = append(fields, 
            fmt.Sprintf(format,
                reflect.Indirect(v.Index(i)).FieldByName(name).Interface()))
    }
    return strings.Join(fields, sep)
}

Try it on the playground!

【讨论】:

    【解决方案2】:

    我不确定 Logrus,但这适用于标准库:

    package main
    
    import (
       "fmt"
       "strconv"
    )
    
    type Person struct {
       ID int
       FirstName, LastName string
    }
    
    func (p Person) String() string {
       return strconv.Itoa(p.ID)
    }
    
    func main() {
       p := Person{9, "First", "Last"}
       fmt.Println(p) // 9
    }
    

    【讨论】:

    • 有效,但应该注意的是,您无法选择在不同位置打印不同字段,因为该类型只能有一个 String() 方法。
    【解决方案3】:

    查看这个使用反射和字符串构建的函数:

    func printPersonField(persons []Person, field string) {
        buf := bytes.NewBuffer(nil)
        fmt.Fprintf(buf, "Persons (%s): [", field)
        for i := range persons {
            if i > 0 {
                fmt.Fprint(buf, ", ")
            }
            fmt.Fprint(buf, reflect.ValueOf(persons[i]).FieldByName(field).Interface())
        }
        fmt.Fprint(buf, "]")
        log.Println(buf.String())
    }
    

    这样使用:

    func main() {
        persons := []Person{
            {1, "first1", "last1"},
            {2, "first2", "last2"},
            {3, "first3", "last3"},
        }
        
        printPersonField(persons, "ID")
        printPersonField(persons, "FirstName")
        printPersonField(persons, "LastName")
        
    }
    

    输出:

    2009/11/10 23:00:00 Persons (ID): [1, 2, 3]
    2009/11/10 23:00:00 Persons (FirstName): [first1, first2, first3]
    2009/11/10 23:00:00 Persons (LastName): [last1, last2, last3]
    

    Playground

    请注意,这不是“通用”,因为它不适用于任何类型。它严格接受[]Person 输入。如果您想让它适用于其他结构的其他类型的切片,请参阅 I Love Reflection 的答案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 2010-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多