Integrate elastic search with Logrus (golang logging) - go

I am using logrus to do all the logging my golang application. However, I also want to integrate this with Elastic Search such that all the logs are also flushed to elastic search when I create a logrus log entry. Currently all logs are created in a file as shown in the snippet below. How could I integrate with elastic search?
type LoggerConfig struct {
Filename string `validate:regexp=.log$`
AppName string `validate:regexp=^[a-zA-Z]$`
}
type AppLogger struct {
Err error
Logger logrus.Entry
}
func Logger(loggerConfig LoggerConfig) AppLogger {
response := new(AppLogger)
// validate the schema of the logger_config
if errs := validator.Validate(loggerConfig); errs != nil {
response.Err = errs
// this sets up the error on the the response struct
}
logrus.SetFormatter(&logrus.JSONFormatter{})
f, err := os.OpenFile(loggerConfig.Filename, os.O_WRONLY|os.O_CREATE, 0755)
if err != nil {
response.Err = err
}
multipleWriter := io.MultiWriter(os.Stdout, f)
logrus.SetOutput(multipleWriter)
contextLogger := logrus.WithFields(logrus.Fields{
"app": loggerConfig.AppName,
})
//logrus.AddHook(hook)
response.Logger = *contextLogger
//response.Logger.Info("adele")
return *response
}
I had tried elogrus which adds a hook but I am not sure how to use it. Here is the method which attempts to create elastic search client. How could I integrate this with logrus instance?
func prepareElasticSearchClient() *elastic.Client {
indexName := "my-server"
client, _ := elastic.NewClientFromConfig(&config.Config{
URL: os.Getenv("ELASTIC_SEARCH_URL_LOGS") + ":" + os.Getenv("ELASTIC_SEARCH_PORT_LOGS"),
Index: indexName,
Username: os.Getenv("ELASTIC_SEARCH_USERNAME_LOGS"),
Password: os.Getenv("ELASTIC_SEARCH_PASSWORD_LOGS"),
})
return client
}
Earlier I have used modules like Winston where it was super easy to setup elastic search logging but somehow, I find little documentation with golang on how to integrate Golang logging with elastic search

With elogrus you first create Elastic client and pass it to elogrus hook when creating it with elogrus.NewAsyncElasticHook(). Hook just wraps sending message to Elastic. Then you add this hook to logrus log. Every time you log message using log it will fire your hook and send message (if log level filter passes) to Elastic.
log := logrus.New()
client, err := elastic.NewClient(elastic.SetURL("http://localhost:9200"))
// ... handle err
hook, err := elogrus.NewAsyncElasticHook(client, "localhost", logrus.DebugLevel, "testlog")
// ... handle err
log.Hooks.Add(hook)
Signature of NewAsyncElasticHook is (client *elastic.Client, host string, level logrus.Level, index string) where:
client is pointer to Elastic.Client you obtained before using elastic
host is string that denotes from which host you are sending the log trace (it's a string - hostname of host where program that logs is running)
level is the maximum logrus log level you want messages to be sent (e.g. if you want to see DEBUG messages locally but only send only ERROR and below to Elastic)
index is name of Elastic Search index you want to add messages from log to
From here you can use log normally as you would do with logrus and all messages will get passed to Elastic.
Another part of issue was a bit more tricky and rooted in (not only) Golang elastic client node sniffing behavior. We debugged it in chat and summary was posted as my answer to OP's another question regarding that: Cannot connect to elastic search : no active connection found: no Elasticsearch node available

Related

Why Google Logging client libraries not logging inside Google cloud functions?

I'm trying to implement a google cloud function to test Google Logging client library. below is my code
// Package p contains an HTTP Cloud Function.
package loggingclient
import (
"cloud.google.com/go/logging"
"net/http"
"context"
"fmt"
)
// HelloWorld prints the JSON encoded "message" field in the body
// of the request or "Hello, World!" if there isn't one.
func HelloWorld(w http.ResponseWriter, r *http.Request) {
label := map[string]string{"priority": "High"}
var projectName = "my-project-id"
ctx := context.Background()
client, err := logging.NewClient(ctx, projectName)
if err != nil {
fmt.Printf("client not created: %v", err)
}
lg := client.Logger("MY-LOGGER")
lg.Log(logging.Entry{
Payload: "Hello, This is error!!",
Severity: logging.Error,
Labels: label,
})
client.Close()
}
Here, I'm expecting a log entry with a message:"Hello, This is error!!" and with a lable:"priority": "High" and severirty "ERROR"
But actually, when I trigger this Cloud Function, I didn't get any new log entries. Therefore don't client logging libraries work inside cloud functions?, How to resolve this?
Thanks
It works on cloud functions. I have done the exact same thing in a cloud function before. You can use google's official documenation with cloud function logging here
Also ensure that the service account have necessary permissions for logging
https://cloud.google.com/logging/docs/access-control

400 Bad request when generating the Google API access token using Go iamcredentials client API

I am trying to implement iamcredentials Go API client to generate an Access Token to access some Google APIs via REST API, I am using this code
package main
import (
"context"
"log"
"google.golang.org/api/iamcredentials/v1"
)
func main() {
iamcredentialsService, err := iamcredentials.NewService(context.Background())
if err != nil {
log.Println("error initialize iamcredential Service ", err)
return
}
accessTokenCall := iamcredentialsService.Projects.ServiceAccounts.GenerateAccessToken(
"projects/-/serviceAccounts/some-sa#some-project-id.iam.gserviceaccount.com:generateAccessToken",
&iamcredentials.GenerateAccessTokenRequest{
Scope: []string{
iamcredentials.CloudPlatformScope,
},
},
)
iamResp, err := accessTokenCall.Do()
if err != nil {
log.Println("error generate access token", err)
return
}
log.Println(iamResp)
}
But when I tried to run the above snippet, I got this message
go run main.go
error generate access token googleapi: Error 400: Request contains an invalid argument., badRequest
Is there any way to check which one is causing the above response? I am not sure since there isn't any good example of implementation. Any help would be appreciated, Thanks.
Notes :
I have checked following documentation on this topic https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials and this https://pkg.go.dev/google.golang.org/api/iamcredentials/v1#pkg-overview
I have already setup the Service account using Service Account Token Creator role on IAM and also enabled the IAM API from the console
Also I have added GOOGLE_APPLICATION_CREDENTIALS to the environment variables as suggested
#DanielFarrell is right, you need to remove the :generateAccessToken at the end. Here the documentation in the code. Don't hesitate to explore it, it's open source ;)
// GenerateAccessToken: Generates an OAuth 2.0 access token for a
// service account.
//
// - name: The resource name of the service account for which the
// credentials are requested, in the following format:
// `projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID}`. The `-`
// wildcard character is required; replacing it with a project ID is
// invalid.
func (r *ProjectsServiceAccountsService) GenerateAccessToken(name string, generateaccesstokenrequest *GenerateAccessTokenRequest) *ProjectsServiceAccountsGenerateAccessTokenCall {
c := &ProjectsServiceAccountsGenerateAccessTokenCall{s: r.s, urlParams_: make(gensupport.URLParams)}
c.name = name
c.generateaccesstokenrequest = generateaccesstokenrequest
return c
}

Google Directory API add Custom Schema/Update it to Users per google API (in go)

I am trying to upload a CustomSchema to all Users of a company in GSuite. This Custom Schema contains their Github Usernames, which I extracted with the github API.
The problem is, after running the code, the account in Gsuite is not added.
Relevant code (A connection to GSuite with admin Authentication is established, the map has all user entries. If you still want more code, I can provide you with it - just trying to keep it simple):
for _, u := range allUsers.Users {
if u.CustomSchemas != nil {
log.Printf("%v", string(u.CustomSchemas["User_Names"]))
}else{
u.CustomSchemas = map[string]googleapi.RawMessage{}
}
nameFromGsuite := u.Name.FullName
if githubLogin, ok := gitHubAccs[nameFromGsuite]; ok {
userSchemaForGithub := GithubAcc{GitHub: githubLogin}
jsonRaw, err := json.Marshal(userSchemaForGithub)
if err != nil {
log.Fatalf("Something went wrong logging: %v", err)
}
u.CustomSchemas["User_Names"] = jsonRaw
adminService.Users.Update(u.Id, u)
} else {
log.Printf("User not found for %v\n", nameFromGsuite)
}
}
This is the struct for the json encoding:
type GithubAcc struct {
GitHub string `json:"GitHub"`
}
For anyone stumbling upon this.
Everything in the code snippet is correct. By the way the method is written, I expected that adminService.Users.Update() actually updates the user. Instead, it returns an UserUpdatesCall.
You need to execute that update by calling .Do()
From the API:
Do executes the "directory.users.update" call.
So the solution is to change adminService.Users.Update(u.Id, u)
into adminService.Users.Update(u.Id, u).Do()

How to ensure redis subscriber receive message in Go (Golang)?

I'm using gin framework to build an API server. In General, I'm build 2 projects. Project 'API' and Project 'SOCKET'. Project 'API' is the main REST API that will used in Android, developed using gin framework (golang). And Project 'SOCKET' is the socket server for client that will use socket connection , using node.js (Socket.IO)
The process begin like this :
User A : as the requester ; A connect to "API"
User B : as the responder ; B connect to "SOCKET"
User A call API requestData from android, the request will handled by "API"'s project. And Project "API" will record the request, and publish on redis
as new_request using pubsub
this is the code for example :
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
pong, err := client.Ping().Result()
fmt.Println(pong, err)
if err !=nil {
fmt.Println("err",err);
}
pubsub, err := client.Subscribe("responseclient")
if err !=nil {
panic(err)
}
defer pubsub.Close()
err = client.Publish("new_request", "Example New Request").Err()
if err !=nil {
panic(err)
}
msg, err :=pubsub.ReceiveMessage()
if err != nil {
panic(err)
}
fmt.Println(msg.Channel, msg.Payload)
}
In Project "SOCKET" there is a subscriber that will listen every publish that occured, and publish new message to channel responseclient
this is for the example code :
ioApp.on ('connection' , function(socket) {
redisSub.on('new_request', function (channel, message) {
console.log(channel + ':' + message);
redisPub.publish("responseclient", JSON.stringify(res));
});
})
This work smoothly, if User B is Connected to Socket.IO. But if User B was offline, or not connected to socket.io, this will waiting for long, until we kill manually or until User B is online
What i am asking for , are :
Can we create something like a callback on redis pub/sub ? If the subscriber doesn't accept the message, due to off line, or something else , we close the connection. Is this possible ?
In Node.Js i know i can use timeout function, that will close the subscribe or emit any event if on certain time, there was no message received, how to do this on golang ? I need to inform the User A if User B is active or offline, so he can wait for a another time to create request.
If nothing can, what is your suggestion for me to do this ?
I hope my question , understandable, and can answered well.
*Some code maybe, missing variable.
** I'm using this library for golang redis : go-redis
1) There are no callbacks in Redis.
2) The usual way to implement a timeout in Go is to use channels and select - where one is a channel where you do the blocking and another channel receives a message on timeout. Examples of that can be found here and here for the docs
Now for (3), you have some options on methods. The first is to use a list, pushing from one side (publishing) and popping from another (subscribing). For the receiver you wild use BRPOP of BLPOP - blocking pop from right or left respectively. You can combine the two to have persistent messaging.
Now part of PUBSUB also depends on what you are publishing to. If you are publishing to a channel that would have a subscriber if and only if there is a user connected to receive it (and thus one and only one subscriber to that channel), you can check the response from your publish command. It will tell you how many clients it was published to. If the channel is only subscribed to by an online receiver you would get a '1' back, and a '0' if the user was offline.
A third example is to store the messages in a sorted set, with the timestamp as the score. This would allow the receiver to connect and get messages from the last time it was connected - but that assumes some persistence of that somewhere - usually the client. You would also need some cleanup activity on the sorted sets.
Some other things to consider in this scenario is whether you eventually use replication, in which case you have to explicitly account for failovers - though really in the scenario you describe you'd want to account for disconnects and reconnects. There are specific examples of this at my post on reliable PUBSUB.
package main
import (
"context"
"fmt"
"time"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
func main() {
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
Password: "", // no password set
DB: 0, // use default DB
})
subscribe := rdb.Subscribe(ctx, "hello")
subscriptions := subscribe.ChannelWithSubscriptions(ctx, 1)
go func() {
var sentCount = 0
for {
rdb.Publish(ctx,"hello",time.Now().UnixNano())
sentCount++
if sentCount >300{
break
}
}
}()
for {
select {
case sub:=<-subscriptions:
fmt.Println(sub)
}
}
}

MQTT existing client

I am using eclipse paho golang library to create new MQTT client for a particular client id:
func CreateMQTTClient(clientID string) (client MQTT.Client) {
username := viper.GetString("messaging.rabbitmq.username")
password := viper.GetString("messaging.rabbitmq.password")
host := viper.GetString("messaging.rabbitmq.host")
mqqtPort := viper.GetString("messaging.rabbitmq.mqqtPort")
rabbitMqMQQTURL := "tcp://" + host + ":" + mqqtPort
opts := MQTT.NewClientOptions().AddBroker(rabbitMqMQQTURL)
opts.SetClientID(clientID)
opts.Username = username
opts.Password = password
opts.SetCleanSession(false)
cli := MQTT.NewClient(opts)
if (!cli.IsConnected()) {
log.Println("I came here for cli:", clientID)
if token := cli.Connect(); token.Wait() && token.Error() != nil {
log.Print(token.Error())
}
}
return cli
}
I am not sure how do I get this client back using clientId. If I call CreateMQTTClient again, all existing subscriptions are lost.
There is, unfortunately, no way to query an MQTT server to find out what subscriptions it has active for your client id. When you connect with the same client ID as a previous session the server assumes you have the same state as last time you were connected, but there is no way to pre connect a MessageHandler with a topic in the Go client, the only way to add and remove them is with Subscribe/Unsubscribe.
Assuming the server is working correctly, if you connect as above reusing a client id the server will continue to send you messages according to your previous subscriptions but the Go client doesn't know what to do with them so will invoke the default message handler. The best way to currently resolve this would be to call Subscribe() in the OnConnectHandler, assuming the topics you want to subscribe to are predetermined rather than dynamic.

Resources