Set nested fields in Golang with the Logrus? - go

I'm using github.com/sirupsen/logrus to make a logger. This simplifies how fields are being initiated in the logger (straightforward from docs):
package main
import "github.com/sirupsen/logrus"
func main() {
logFields := logrus.Fields{
"details": logrus.Fields{
"app_name": "Some name",
"app_version": "Some version",
},
}
logger := logrus.New()
logger.WithFields(logFields)
}
The library seems to allow to add fields with method WithFields only at top level (as in the last line).
But because my API has many steps, I need to add nested fields under details with every step (to accumulate log data), then output it all together in the end.
How can I add these fields?

logrus.Fields is just map[string]interface{}, so you can nest all data in every steps into a struct like
type Details struct {
Step1 string `json:"step1"`
Step2 string `json:"step2"`
Step3 string `json:"step3"`
}
and finally logger.WithFields(logFields).Debugln("some debug info")
Another method, you can create multiple variables to save data from every step, for example
var step1data = "step1"
var step2data = "step2"
and finally
logger.WithFields(logrus.Fields{
"details": logrus.Fields{
"step1": step1data,
"step2": step2data,
},
}).Debugln("some debug info")`

Related

UUID field within Go Pact consumer test

I'm currently looking at adding Pact testing into my Go code, and i'm getting stuck on how to deal with field types of UUID.
I have the following struct, which I use to deserialise a response from an API to
import (
"github.com/google/uuid"
)
type Foo struct {
ID uuid.UUID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
}
Now, when I try and write my consumer test, it looks something like this
pact.
AddInteraction().
Given("A result exists").
UponReceiving("A request to get all results").
WithRequest(dsl.Request{
Method: "get",
Path: dsl.String("/v1"),
}).
WillRespondWith(dsl.Response{
Status: 200,
Headers: dsl.MapMatcher{"Content-Type": dsl.String("application/json")},
Body: dsl.Match(&Foo{}),
})
The problem now, is the mocked response comes through as below, where it tries to put an array of bytes in the "id" field, I'm assuming since behind the scenes that is what the google/uuid library stores it as.
{
"id": [
1
],
"name": "string",
"description": "string"
}
Has anyone encountered this? And what would be the best way forward - the only solution I can see is changing my model to be a string, and then manually convert to a UUID within my code.
You currently can't use the Match function this way, as it recurses non primitive structures, albeit it should be possible to override this behaviour with struct tags. Could you please raise a feature request?
The simplest approach is to not use the Match method, and just manually express the contract details in the usual way.
The internal representation of uuid.UUID is type UUID [16]byte but json representation of UUID is string
var u uuid.UUID
u, _ = uuid.Parse("f47ac10b-58cc-0372-8567-0e02b2c3d479")
foo1 := Foo{u, "n", "d"}
res, _ := json.Marshal(foo1)
fmt.Println(string(res))
{
"id": "f47ac10b-58cc-0372-8567-0e02b2c3d479",
"name": "n",
"description": "d"
}
and then load marshaled []byte
var foo Foo
json.Unmarshal(res, &foo)

How to make a 2-level depth type definition in a struct?

Currently I have the following definition for my structs:
type WholeJson struct {
Features []Temp
}
type Temp struct {
Properties Human
}
type Human struct {
Name string
Age uint
}
Which is working when unmarshaling a JSON string into a variable of type WholeJson, which would have the following structure:
{
"features":[
{
"properties": {
"name": "John Doe",
"age": 50
}
}
]
}
Go Playground sample here: https://play.golang.org/p/3WTLxR0EZWP
But I don't know how to write it in a simpler way. It is obvious that not both WholeJson and Temp are necessary as far as using the information they will hold. The only reason I have Temp is because I simply don't know how to avoid defining it (and still have the program work).
Presumably the Features property of WholeJson would have an array to some interface{}, but I can't nail the syntax. And I'm assuming the code for reading the unmarshalled data (from the playground sample) will stay the same.
How would I "squash" those those two structs into one (the Human struct i'm assuming is okay if it stays on its own), and still have useful data in the end, where I could loop through the features key to go through all the data?
It's OK to define a type for each level of the hierarchy and preferred when constructing values from Go code. The types do not need to be exported.
Use anonymous types to eliminate the defined types:
var sourceData struct {
Features []struct {
Properties Human
}
}
var jsonString string = `{
"features":[
{
"properties": {
"name": "John Doe",
"age": 50
}
}
]
}`
json.Unmarshal([]byte(jsonString), &sourceData)
fmt.Println(sourceData.Features[0].Properties.Name)

Passing nested JSON as variable in Machinebox GraphQL mutation using golang

Hi there Golang experts,
I am using the Machinebox "github.com/machinebox/graphql" library in golang as client for my GraphQL server.
Mutations with single layer JSON variables work just fine
I am, however, at a loss as to how to pass a nested JSON as a variable
With a single layer JSON I simply create a map[string]string type and pass into the Var method. This in turn populates my graphql $data variable
The machinebox (graphql.Request).Var method takes an empty interface{} as value so the map[string]string works fine. But embedded json simply throws an error.
code:
func Mutate(data map[string]string, mutation string) interface{} {
client := GQLClient()
graphqlRequest := graphql.NewRequest(mutation)
graphqlRequest.Var("data", data)
var graphqlResponse interface{}
if err := client.Run(context.Background(), graphqlRequest, &graphqlResponse); err != nil {
panic(err)
}
return graphqlResponse
}
Mutation:
mutation createWfLog($data: WfLogCreateInput)
{
createWfLog (data: $data){
taskGUID {
id
status
notes
}
event
log
createdBy
}
}
data variable shape:
{
"data": {
"event": "Task Create",
"taskGUID": {
"connect": {"id": "606f46cdbbe767001a3b4707"}
},
"log": "my log and information",
"createdBy": "calvin cheng"
}
}
As mentioned, the embedded json (value of taskGUID) presents the problem. If value was simple string type, it's not an issue.
Have tried using a struct to define every nesting, passed in struct.
Have tried unmarshaling a struct to json. Same error.
Any help appreciated
Calvin
I have figured it out... and it is a case of my noobness with Golang.
I didn't need to do all this conversion of data or any such crazy type conversions. For some reason I got in my head everything HAD to be a map for the machinebox Var(key, value) to work
thanks to xarantolus's referenced site I was able to construct a proper strut. I populated the strut with my variable data (which was a nested json) and the mutation ran perfectly!
thanks!

Is it possible to swap 'msg' for 'message' with logrus logging

So when using Logrus I need the logging output to have the message key instead of msg key, but when I use the code below I get both, but msg is empty, how can I configure logrus to use 'message' instead of 'msg'?
contextLogger.WithField("logger_name",topic).WithField("message",messageString).Info()
Here's the log output when leaving .Info() empty // .Info() logs to the msg key
"logger_name": "logger","message": "log message","msg": "","time": "2020-08-12T15:14:48Z"
What I would like is to be able to use .Info(message) and have
"logger_name": "logger","message": "log message","time": "2020-08-12T15:14:48Z"
Is it possible to change default logging keys for .Info() etc?
You can do so using the FieldMap field of JSonFormatter:
// FieldMap allows users to customize the names of keys for default fields.
// As an example:
// formatter := &JSONFormatter{
// FieldMap: FieldMap{
// FieldKeyTime: "#timestamp",
// FieldKeyLevel: "#level",
// FieldKeyMsg: "#message",
// FieldKeyFunc: "#caller",
// },
// }
FieldMap FieldMap
This allows you to override the default field names, including msg.
Here's a short example (see on playground):
package main
import (
log "github.com/sirupsen/logrus"
)
func main() {
log.SetFormatter(&log.JSONFormatter{
FieldMap: log.FieldMap{
log.FieldKeyMsg: "message",
},
})
log.WithField("logger_name", "topic2").Info("this is my message")
}
Output:
{"level":"info","logger_name":"topic2","message":"this is my message","time":"2009-11-10T23:00:00Z"}
The same override is available on the TextFormatter.

Is there a better way to declare json variable

Declaring a variable of type map[string]map[string]... is not ideal, is there a better way
snaps := map[string]map[string]map[string]map[string]string{
"distros": {
"aws": {
"eu-west-1": {
"snap-0": "/dev/sdm",
},
"eu-west-2": {
"snap-1": "/dev/sdm",
},
},
},
}
fmt.Println(snaps["distros"]["aws"]["eu-west-1"])
The simplest way would be to use the type map[string]interface{}. Since the empty interface, interface{}, refers to any type and therefore handles the arbitrarily nested nature of JSON.
To do this you'll have to write your literal data as a string first and then parse the string into a Go map.
With that in mind here is a refactor of your example:
first: import "encoding/json", then
snapsStr := `{
"distros": {
"aws": {
"eu-west-1" : {
"snap-0": "/dev/sdm"
},
"eu-west-2": {
"snap-1": "/dev/sdm"
}
}
}
}`
var snaps map[string]interface{}
json.Unmarshal([]byte(snapsStr), &snaps)
And now snaps is as desired.
This is the most generic format for JSON data in Go and is one of the ways that the Go JSON library handles types for JSON. See these docs: https://golang.org/pkg/encoding/json/#Unmarshal

Resources