generated from traefik/plugindemo
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtraefik_log_elasticsearch.go
191 lines (168 loc) · 6.24 KB
/
traefik_log_elasticsearch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//go:build !generated
// +build !generated
// Package traefiklogelasticsearch provides a Traefik middleware plugin
// that logs HTTP request details to an Elasticsearch instance.
package traefiklogelasticsearch
import (
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"strings"
"github.com/elastic/go-elasticsearch/v7"
"github.com/elastic/go-elasticsearch/v7/esapi"
"github.com/google/uuid"
)
// Config is a structure that holds the configuration needed for the Elasticsearch plugin in Traefik.
type Config struct {
// ElasticsearchURL is the URL of the Elasticsearch instance that the plugin should interact with.
ElasticsearchURL string
// IndexName is the name of the Elasticsearch index that the plugin should write logs to.
IndexName string
// Message is the default log message that will be used if no specific message is provided in the log entry.
Message string
// APIKey is used for authentication with the Elasticsearch instance. This should be used if Username and Password are not provided.
APIKey string
// Username is the username to be used for authentication with the Elasticsearch instance. This is an alternative to APIKey.
Username string
// Password is the password to be used for authentication with the Elasticsearch instance. This is an alternative to APIKey.
Password string
// VerifyTLS determines whether the plugin should verify the TLS certificate of the Elasticsearch instance.
// It is recommended to set this to true in production to prevent man-in-the-middle attacks.
VerifyTLS bool
}
// CreateConfig returns a pointer to a Config struct with its fields initialized to zero values.
// This is a convenient way to create a new Config instance.
func CreateConfig() *Config {
return &Config{}
}
// ElasticsearchLog is a middleware handler that logs HTTP requests to an Elasticsearch instance.
type ElasticsearchLog struct {
// Next is the next handler to be called in the middleware chain. The ElasticsearchLog handler will call this after logging the request.
Next http.Handler
// Name is the name of the handler. This is mainly used for identification and debugging purposes.
Name string
// Message is the default message to be logged to Elasticsearch if no specific message is provided in the log entry.
Message string
// ElasticsearchURL is the URL of the Elasticsearch instance where the logs should be written to.
ElasticsearchURL string
// IndexName is the name of the Elasticsearch index where the logs should be written to.
IndexName string
// APIKey is used for authentication with the Elasticsearch instance. This should be used if Username and Password are not provided.
APIKey string
// Username is the username to be used for authentication with the Elasticsearch instance. This is an alternative to APIKey.
Username string
// Password is the password to be used for authentication with the Elasticsearch instance. This is an alternative to APIKey.
Password string
// VerifyTLS determines whether the middleware should verify the TLS certificate of the Elasticsearch instance.
// It is recommended to set this to true in production to prevent man-in-the-middle attacks.
VerifyTLS bool
}
// New creates a new ElasticsearchLog middleware instance.
func New(_ context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
if len(config.ElasticsearchURL) == 0 {
return nil, errors.New("missing Elasticsearch URL")
}
if len(config.IndexName) == 0 {
return nil, errors.New("missing Elasticsearch index name")
}
if len(config.Message) == 0 {
return nil, errors.New("missing Elasticsearch message")
}
if (len(config.APIKey) == 0) && (len(config.Username) == 0 || len(config.Password) == 0) {
return nil, errors.New("missing Elasticsearch credentials")
}
elasticsearchLog := &ElasticsearchLog{
ElasticsearchURL: config.ElasticsearchURL,
IndexName: config.IndexName,
Next: next,
Name: name,
Username: config.Username,
Password: config.Password,
APIKey: config.APIKey,
VerifyTLS: config.VerifyTLS,
}
return elasticsearchLog, nil
}
func convertToJSON(data map[string]interface{}) string {
jsonData, err := json.Marshal(data)
if err != nil {
fmt.Println(err)
}
return string(jsonData)
}
func (e *ElasticsearchLog) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
var cfg elasticsearch.Config
if !e.VerifyTLS {
// Create a TLS config that skips certificate verification.
tlsConfig := &tls.Config{InsecureSkipVerify: true} //nolint:gosec
// Create a transport to use our TLS config.
transport := &http.Transport{TLSClientConfig: tlsConfig}
cfg = elasticsearch.Config{
Addresses: []string{
e.ElasticsearchURL,
},
Transport: transport,
Username: e.Username,
Password: e.Password,
APIKey: e.APIKey,
}
} else {
cfg = elasticsearch.Config{
Addresses: []string{
e.ElasticsearchURL,
},
Username: e.Username,
Password: e.Password,
APIKey: e.APIKey,
// Note: VerifyTLS is set to true by default when using the elasticsearch.Config struct.
}
}
// Create a client
es, err := elasticsearch.NewClient(cfg)
if err != nil {
log.Fatalf("Error creating the client: %s", err)
}
id := uuid.New().String()
msg := map[string]interface{}{
"message": e.Message,
}
// Set up the Elasticsearch request object directly
esReq := esapi.IndexRequest{
Index: e.IndexName,
DocumentID: id,
Body: strings.NewReader(convertToJSON(msg)),
Refresh: "true",
}
res, err := esReq.Do(req.Context(), es)
if err != nil {
log.Fatalf("Error getting response: %s", err)
}
defer func() {
err := res.Body.Close()
if err != nil {
log.Fatalf("Error closing the response body: %s", err)
}
}()
if res.IsError() {
log.Printf("[%s] Error indexing document ID=%d", res.Status(), 1)
log.Printf("%d", res.StatusCode)
return
}
// Deserialize the response into a map.
var r map[string]interface{}
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
log.Printf("Error parsing the response body: %s", err)
return
}
version, ok := r["_version"].(float64)
if !ok {
log.Printf("Error: expected '_version' to be a float64")
return
}
log.Printf("[%s] %s; version=%d", res.Status(), r["result"], int(version))
e.Next.ServeHTTP(rw, req)
}