Repository navigation
Expand file tree
/
Copy pathmain.go
More file actions
executable file
·108 lines (92 loc) · 2.96 KB
/
Copy pathmain.go
File metadata and controls
executable file
·108 lines (92 loc) · 2.96 KB
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
package trace_plugin
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"net/http"
"strconv"
"time"
)
// Config структура конфигурации плагина
type Config struct {
HeaderName string `json:"headerName,omitempty"`
}
// CreateConfig создает конфигурацию по умолчанию
func CreateConfig() *Config {
return &Config{
HeaderName: "X-Trace-Id",
}
}
// TraceIDPlugin основная структура плагина
type TraceIDPlugin struct {
next http.Handler
name string
headerName string
}
// New создает новый экземпляр плагина
func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) {
if config.HeaderName == "" {
config.HeaderName = "X-Trace-Id"
}
return &TraceIDPlugin{
next: next,
name: name,
headerName: config.HeaderName,
}, nil
}
// ServeHTTP обрабатывает HTTP запросы
func (t *TraceIDPlugin) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
startTime := time.Now()
// Проверяем, есть ли уже trace_id в заголовках
traceID := req.Header.Get(t.headerName)
// Если нет, генерируем новый
if traceID == "" {
traceID = generateTraceID()
req.Header.Set(t.headerName, traceID)
}
// Добавляем trace_id в response headers
// rw.Header().Set(t.headerName, traceID)
// Добавляем trace_id в контекст для использования в приложении
ctx := context.WithValue(req.Context(), "trace_id", traceID)
req = req.WithContext(ctx)
logData := map[string]interface{}{
"trace_id": traceID,
"method": req.Method,
"path": req.URL.Path,
"remote_addr": req.RemoteAddr,
"user_agent": req.Header.Get("User-Agent"),
"x_forwarded_for": req.Header.Get("X-Forwarded-For"), // ip клиента
"timestamp": startTime.Format(time.RFC3339Nano), // Корректная временная метка
}
fmt.Printf("%s\n", formatJSON(logData))
// Передаем управление следующему middleware
t.next.ServeHTTP(rw, req)
endTime := time.Now()
logData["timestamp"] = endTime.Format(time.RFC3339Nano)
logData["response_time"] = strconv.Itoa(int(endTime.Sub(startTime).Milliseconds())) + " ms"
fmt.Printf("%s\n", formatJSON(logData))
}
func generateTraceID() string {
// Используем crypto/rand для безопасной генерации
bytes := make([]byte, 16)
if _, err := rand.Read(bytes); err != nil {
// Fallback - используем timestamp если crypto/rand недоступен
return fmt.Sprintf("trace-%d", time.Now().UnixNano())
}
return hex.EncodeToString(bytes)
}
func formatJSON(data map[string]interface{}) string {
var s string
s = "{"
first := true
for k, v := range data {
if !first {
s += ","
}
s += fmt.Sprintf(`"%s":"%v"`, k, v)
first = false
}
s += "}"
return s
}