jcollum
jcollum

Reputation: 46609

What's the simplest way to log a single field from a set of structs?

Let's say I have a func that takes in a slice of Person:

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

When logging I might want to log just the ID. Is there a simple way to do this without creating another type? I'm using Logrus. If I was in JS I'd just use the map function. Example logger line:

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

But this will result in a lot of unnecessary output in my logs. The ID is enough to find the person we are processing.

Upvotes: 1

Views: 1093

Answers (3)

user12258482
user12258482

Reputation:

Use the reflect package to write a "generic" function for extracting the fields:

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
}

Use it like this:

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

Run an example on the playground

The function can be extended to included formatting bells and whistles:

// 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!

Upvotes: 2

Hymns For Disco
Hymns For Disco

Reputation: 8405

See this function that uses reflect and string building:

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())
}

Usage like so:

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

Output:

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

Note that this isn't "generic", as in it won't work for any type. It strictly takes a []Person input. If you want to make it work for other types of slices of other structs, then see I Love Reflection's answer.

Upvotes: 0

Zombo
Zombo

Reputation: 1

I am not sure about Logrus, but this works with the standard library:

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
}

Upvotes: 0

Related Questions