Use struct in place of map in logrus logging - go

I'm using the logrus package for logging in a Go project.
To display key value pairs in logs, the docs give the following format:
log.WithFields(log.Fields{
"animal": "walrus",
"size": 10,
}).Info("A group of walrus emerges from the ocean")
Instead of using string keys manually in each log, I wish to use a common struct across all logs (to avoid chances of typos in keys).
Something like this:
type LogMessage struct {
Status bool `json:"status"`
Message string `json:"message"`
}
log.WithFields(&LogMessage {Status: false, Message: "Error User Already Exists"}).Info("User Creation Failed.")
The log output should be as :
time="2015-03-26T01:27:38-04:00" level=info msg="User Creation Failed." status=false message="Error User Already Exists"
How can this be implemented ?
Thanks for any help !

You cannot pass a struct to WithFields(). It takes Fields type (which is basically map[string]interface{}). To avoid making mistakes in common key names you can create constants - that way if you make typo in constant name code won't even compile (and in the end it will be less verbose to write than passing a struct):
const Status = "status"
const Message = "message"
//...
log.WithFields(log.Fields{Status: true, Message: "a message"}).Info("foo")
To implement exactly what you want you will need to convert struct to a map before passing to WithFields():
import (
structs "github.com/fatih/structs" // ARCHIVED
log "github.com/sirupsen/logrus"
)
//...
type LogMessage struct {
Status bool `json:"status"`
Message string `json:"message"`
}
log.WithFields(structs.Map(&LogMessage{Status: true, Message: "a message"})).Info("foo")
// Will output:
// time="2009-11-10T23:00:00Z" level=info msg=foo Message="a message" Status=true
(Note: I used library "structs" that is archived just to demonstrate principle. Also reflection required to do conversion adds performance cost so I wouldn't use this in performance-critical parts of program).

you can use a custom wrapping function inside which you can set your field keys.
https://play.golang.org/p/H22M63kn8Jb
package main
import (
log "github.com/sirupsen/logrus"
)
func LogMyMessages(messageStruct *myMessageStruct) {
log.WithFields(log.Fields{"status": messageStruct.Status, "message": messageStruct.Message}).Info("foo")
}
type myMessageStruct struct {
Message string
Status bool
}
func main() {
LogMyMessages(&myMessageStruct{Status: true, Message: "test message"})
}
gives a message like so
time="2009-11-10T23:00:00Z" level=info msg=foo message="test message" status=true

Not exactly what you need but just want to provide another option using fmt.
log.WithFields(log.Fields{
"info": fmt.Sprintf("%+v", LogMessage{Status: false, Message: "Error User Already Exists"}),
}).Info("User Creation Failed.")
This will produce something like this
time="2015-03-26T01:27:38-04:00" level=info msg="User Creation Failed." info="{Status:false Message:'Error User Already Exists'}"

Related

too many parameters returned by Resolver.ModuleName

I am working on breaking the monolithic architecture into Microservice architecture.
I did that but when I am building the code in my current repository I am getting this error.
We use graphql-gophers library
panic: too many parameters returned by (Resolver).Dummy
Has anyone ever seen this error in golang using graphql for querying?
Tried so many things but nothing has worked.
Any help would be appreciated
The error message comes from graph-gophers/graphql-go internal/exec/resolvable/resolvable.go#makeFieldExec
It is called when you parse a schema which does not match the field of an existing struct.
The one illustrated in example/customerrors/starwars.go does match every field and would not trigger the error message:
var Schema = `
schema {
query: Query
}
type Query {
droid(id: ID!): Droid!
}
# An autonomous mechanical character in the Star Wars universe
type Droid {
# The ID of the droid
id: ID!
# What others call this droid
name: String!
}
`
type droid struct {
ID graphql.ID
Name string
}
Its resolver does use the right parameters:
type Resolver struct{}
func (r *Resolver) Droid(args struct{ ID graphql.ID }) (*droidResolver, error) {
if d := droidData[args.ID]; d != nil {
return &droidResolver{d: d}, nil
}
return nil, &droidNotFoundError{Code: "NotFound", Message: "This is not the droid you are looking for"}
}
Try and use that example to check it does work, then modify it to transition to your own code.

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)

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.

Document all potential errors on GraphQL server?

For a mutation addVoucher there are a limited list of potential errors that can occur.
Voucher code invalid
Voucher has expired
Voucher has already been redeemed
At the moment I'm throwing a custom error when one of these occurs.
// On the server:
const addVoucherResolver = () => {
if(checkIfInvalid) {
throw new Error('Voucher code invalid')
}
return {
// data
}
}
Then on the client I search the message description so I can alert the user. However this feels brittle and also the GraphQL API doesn't automatically document the potential errors. Is there a way to define the potential errors in the GraphQL schema?
Currently my schema looks like this:
type Mutation {
addVoucherResolver(id: ID!): Order
}
type Order {
cost: Int!
}
It would be nice to be able to do something like this:
type Mutation {
addVoucherResolver(id: ID!): Order || VoucherError
}
type Order {
cost: Int!
}
enum ErrorType {
INVALID
EXPIRED
REDEEMED
}
type VoucherError {
status: ErrorType!
}
Then anyone consuming the API would know all the potential errors. This feels like a standard requirement to me but from reading up there doesn't seem to be a standardises GraphQL approach.
It's possible to use a Union or Interface to do what you're trying to accomplish:
type Mutation {
addVoucher(id: ID!): AddVoucherPayload
}
union AddVoucherPayload = Order | VoucherError
You're right that there isn't a standardized way to handle user-visible errors. With certain implementations, like apollo-server, it is possible to expose additional properties on the errors returned in the response, as described here. This does make parsing the errors easier, but is still not ideal.
A "Payload" pattern has emerged fairly recently for handling these errors as part of the schema. You see can see it in public API's like Shopify's. Instead of a Union like in the example above, we just utilize an Object Type:
type Mutation {
addVoucher(id: ID!): AddVoucherPayload
otherMutation: OtherMutationPayload
}
type AddVoucherPayload {
order: Order
errors: [Error!]!
}
type OtherMutationPayload {
something: Something
errors: [Error!]!
}
type Error {
message: String!
code: ErrorCode! # or a String if you like
}
enum ErrorCode {
INVALID_VOUCHER
EXPIRED_VOUCHER
REDEEMED_VOUCHER
# etc
}
Some implementations add a status or success field as well, although I find that making the actual data field (order is our example) nullable and then returning null when the mutation fails is also sufficient. We can even take this one step further and add an interface to help ensure consistency across our payload types:
interface Payload {
errors: [Error!]!
}
Of course, if you want to be more granular and distinguish between different types of errors to better document which mutation can return what set of errors, you won't be able to use an interface.
I've had success with this sort of approach, as it not only documents possible errors, but also makes it easier for clients to deal with them. It also means that any other errors that are returned with a response should serve as an immediately red flag that something has gone wrong with either the client or the server. YMMV.
You can use scalar type present in graphql
just write scalar JSON and return any JSON type where you want to return it.
`
scalar JSON
type Response {
status: Boolean
message: String
data: [JSON]
}
`
Here is Mutation which return Response
`
type Mutation {
addVoucherResolver(id: ID!): Response
}
`
You can return from resolver
return {
status: false,
message: 'Voucher code invalid(or any error based on condition)',
data: null
}
or
return {
status: true,
message: 'Order fetch successfully.',
data: [{
object of order
}]
}
on Front end you can use status key to identify response is fetch or error occurs.

Resources