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

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.

Related

Unable to add 'Price' field to SubscriptionItemsParams

I am trying to update the price of a Stripe subscription with Golang as shown here:
https://stripe.com/docs/billing/subscriptions/upgrade-downgrade
I copied and pasted the following code from the docs, but substituted in the correct Stripe key, subscription ID, and price ID:
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.stripe.com/apikeys
stripe.Key = "sk_test_51LsirAKpk5W1QCoV3cKpwMabHz8VzurJnNNSmvBkr4zRaicCJFsz8NL7HyvJ7EC61CuKc7eHjMLHqjK1C9Xl6RpD00X5YHcRBk"
subscription, err := sub.Get("sub_49ty4767H20z6a", nil)
params := &stripe.SubscriptionParams{
CancelAtPeriodEnd: stripe.Bool(false),
ProrationBehavior: stripe.String(string(stripe.SubscriptionProrationBehaviorCreateProrations)),
Items: []*stripe.SubscriptionItemsParams{
{
ID: stripe.String(subscription.Items.Data[0].ID),
Price: stripe.String("price_CBb6IXqvTLXp3f"),
},
},
}
subscription, err = sub.Update(subscription.ID, params)
This yields the following error:
unknown field 'Price' in struct literal of type "github.com/stripe/stripe-go".SubscriptionItemsParams
Any ideas where else the Price field might go?
I solved this by specifying a Stripe version in the import by changing this:
import (
"github.com/stripe/stripe-go"
"github.com/stripe/stripe-go/customer"
"github.com/stripe/stripe-go/sub"
)
to this:
import (
stripe "github.com/stripe/stripe-go/v74"
"github.com/stripe/stripe-go/v74/customer"
"github.com/stripe/stripe-go/v74/subscription"
)
It also appears that for v74,
ProrationBehavior: stripe.String(string(stripe.SubscriptionProrationBehaviorCreateProrations)),
should be changed to
ProrationBehavior: stripe.String(string(stripe.SubscriptionSchedulePhaseProrationBehaviorCreateProrations)),

Use struct in place of map in logrus logging

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'}"

How can I test validation functionality in a terraform provider

I have written some more sophisticated validation logic for fields I need to validate in a custom terraform provider. I can, of course, test these are unit tests, but that's insufficient; why if I forgot to actually apply the validator?
So, I need to actually use terraform config and have the provider do it's normal, natural thing.
Basically, I expect it to error. The documentation seems to indicate that I should do a regex match on the output. But this can't be right; it seems super brittle. Can someone tell me how this is done?
func TestValidation(t *testing.T) {
const userBasic = `
resource "my_user" "dude" {
name = "the.dude%d"
password = "Password1" // needs a special char to pass
email = "the.dude%d#domain.com"
groups = [ "readers" ]
}
`
rgx, _ := regexp.Compile("abc")
resource.UnitTest(t, resource.TestCase{
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: userBasic,
ExpectError: rgx,
},
},
})
}
This code obviously doesn't work. And, a lot of research isn't yielding answers.
Since sdk version 2.3.0 you can set ErrorCheck function on resource.TestCase to provide more complex validation for errors.
For example:
resource.UnitTest(t, resource.TestCase{
Providers: testAccProviders,
ErrorCheck: func(err error) error {
if err == nil {
return errors.New("expected error but got none")
}
// your validation code here
// some simple example with string matching
if strings.Contains(err.Error(), "your expected error message blah") {
return nil
}
// return original error if no match
return err
},
Steps: []resource.TestStep{
{
Config: userBasic,
},
},
})

Set nested fields in Golang with the Logrus?

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")`

Accessing values through bracket notation with a variable in bracket after looping through struct for field names

I have 2 JSON files one containing users, and another containing email templates
I'm looping through the code in the email templates, and when there is a key as a value, like this:
"keyToFind": "Username"
Then I want to get the value for Username in the other JSON file:
"Username": "ssmith"
KeyToFind can be a few different things, like Password or Group and I want to avoid writing a specific if statement
I'm trying to do this in my loop but it appears that I can't use a variable in bracket notation
for _, emailElements := range emailTemplates.EmailSpecification {
for _, fieldName := range structs.Names(&User{}) {
if emailElements.KeyToFind == fieldName {
EmailBody.WriteString(user[fieldName])
}
}
What the above is trying to do is loop through the elements in the email template, and then the fields in the Users struct; where an emailElement in the template JSON file of type KeyToFind is gotten, and this is the same as a field name in the struct; look up the user value for the KeyToFind
I could do this in Python without a problem
How can I rewrite line 4 to work in Go? --> user[FieldName]
The error I get is:
user[fieldName] (type User does not support indexing)
But if I write line 4 again to this:
user.Username
It will work fine, but that's obviously only for usernames, they could be Password or Group for the value in KeyToFind
Here are the JSON files:
Email template:
"emailName": "customer",
"emailSpecification": [
{
"emailSubject": "Hi"
},
{
"text": "Username: "
},
{
"keyToFind": "Username"
}
]
I want to get the value of KeyToFind and search the properties in the User file and return the value from that property
User file:
[
{
"UserType": "customer",
"Username": "ssmith",
"Password": "sophie"
}
]
I got it by converting the User struct to a map, once it's a map you can use the bracket notation with dot notation inside
In my context, I then had to convert to a string to pass into WriteString function that buffer needs
Here is the final version:
for _, emailElements := range emailTemplates.EmailSpecification {
for _, fieldName := range structs.Names(&User{}) {
if emailElements.KeyToFind == fieldName {
EmailBody.WriteString(structs.Map(user)[emailElements.KeyToFind].(string))
}
}
}
It's using the package:
"github.com/fatih/structs"

Resources