Skip to content

Commit 65a3f11

Browse files
committed
Update readme, allow to configure to log JSON or string format
1 parent cde0f2b commit 65a3f11

File tree

13 files changed

+416
-108
lines changed

13 files changed

+416
-108
lines changed

README.md

Lines changed: 114 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,117 @@
11
# middleware
2-
middleware log for
3-
- http
4-
- echo
5-
- gin
2+
Middleware logging is a technique used in software development, particularly in web and microservices applications, to log important information about incoming requests, outgoing responses, and the operations performed by the application.
3+
- [core-go/middleware](https://github.com/core-go/middleware) is designed to integrate with middleware logging seamlessly for existing Go libraries: [Echo](https://github.com/labstack/echo), [Gin](https://github.com/gin-gonic/gin), or net/http ([Gorilla mux](https://github.com/gorilla/mux), [Go-chi](https://github.com/go-chi/chi)), with any logging libraries ([zap](https://pkg.go.dev/go.uber.org/zap), [logrus](https://github.com/sirupsen/logrus)), to log request headers, request body, response status code, body content, response time, and size
4+
- Especially, [core-go/middleware](https://github.com/core-go/middleware) supported to encrypt sensitive data, which is useful for Financial Transactions (to comply with <b>PCI-DSS</b> standards) and Healthcare (to comply with <b>HIPAA</b> regulations)
5+
6+
### A typical micro service
7+
- When you zoom one micro service, the flow is as below, and you can see "middleware" in the full picture:
8+
![A typical micro service](https://cdn-images-1.medium.com/max/800/1*d9kyekAbQYBxH-C6w38XZQ.png)
9+
10+
## Content for logging
11+
### Request
12+
#### Features
13+
- <b>Log Request Method and URL</b>: Log the HTTP method (GET, POST, etc.) and the requested URL.
14+
- <b>Log Request Headers</b>: Option to log request headers for debugging purposes.
15+
- <b>Log Request Body</b>: Option to log the request body (with configurable size limits to avoid logging large payloads).
16+
#### Benefits
17+
- <b>Debugging</b>: Helps in tracing and debugging issues by providing complete information about incoming requests.
18+
- <b>Monitoring</b>: Provides visibility into the types of requests being received.
19+
20+
### Response
21+
#### Features
22+
- <b>Log Response Status Code</b>: Log the HTTP status code of the response.
23+
- <b>Log Response Headers</b>: Option to log response headers.
24+
- <b>Log Response Body</b>: Option to log the response body (with configurable size limits to avoid logging large payloads).
25+
#### Benefits
26+
- <b>Debugging</b>: Assists in diagnosing issues by providing complete information about the responses sent by the server.
27+
- <b>Auditing</b>: Helps in auditing and reviewing server responses for compliance and monitoring purposes.
28+
29+
### Response Time
30+
#### Features
31+
- <b>Log Response Time</b>: Calculate and log the time taken to process each request.
32+
#### Benefits
33+
- <b>Performance Monitoring</b>: Helps in identifying slow requests and performance bottlenecks.
34+
- <b>Optimization</b>: Provides data to optimize and improve server response times.
35+
36+
### Response Size
37+
#### Features
38+
- <b>Log Response Size</b>: Log the size of the response payload in bytes.
39+
#### Benefits
40+
- <b>Bandwidth Monitoring</b>: Helps in monitoring and managing bandwidth usage.
41+
- <b>Optimization</b>: Provides insights into the response sizes to optimize payloads and improve performance.
42+
43+
## Features
44+
### Middleware Integration
45+
#### Features
46+
- <b>Middleware Function</b>: Designed to integrate seamlessly with existing Go libraries: [Echo](https://github.com/labstack/echo), [Gin](https://github.com/gin-gonic/gin), or net/http ([Gorilla mux](https://github.com/gorilla/mux), [Go-chi](https://github.com/go-chi/chi)).
47+
- Sample for [Echo](https://github.com/labstack/echo) is at [go-sql-echo-sample](https://github.com/go-tutorials/go-sql-echo-sample)
48+
- Sample for [Gin](https://github.com/gin-gonic/gin) is at [go-sql-gin-sample](https://github.com/go-tutorials/go-sql-gin-sample)
49+
- Sample for [Gorilla mux](https://github.com/gorilla/mux) is at [go-sql-sample](https://github.com/go-tutorials/go-sql-sample)
50+
- <b>Context Handling</b>: Pass context to handle request-specific data throughout the middleware chain.
51+
#### Benefits
52+
- <b>Ease of Use</b>: Simplifies the integration of logging into existing web applications.
53+
- <b>Consistency</b>: Ensures consistent logging across different parts of the application.
54+
55+
### Logging Libraries Integration
56+
- Do not depend on any logging libraries.
57+
- Already supported to integrate with [zap](https://pkg.go.dev/go.uber.org/zap), [logrus](https://github.com/sirupsen/logrus)
58+
- Can be integrated with any logging library.
59+
60+
### Sensitive Data Encryption
61+
#### Features
62+
- Mask/Encrypt sensitive data in the request and response bodies.
63+
- Sensitive Data Identification: identify and encrypt specific fields in JSON payloads.
64+
65+
#### Benefits:
66+
- <b>Security</b>: Protects sensitive information from being exposed in logs.
67+
- <b>Compliance</b>: Helps meet security and compliance requirements by safeguarding sensitive data.
68+
- <b>Ease of Use</b>: Simplifies the integration of encryption/masking into any existing applications.
69+
- <b>Consistency</b>: Ensures that sensitive data is consistently encrypted or masked across all logged requests and responses
70+
71+
#### Samples:
72+
- Sample for [Echo](https://github.com/labstack/echo) is at [go-sql-echo-sample](https://github.com/go-tutorials/go-sql-echo-sample)
73+
- Sample for [Gin](https://github.com/gin-gonic/gin) is at [go-sql-gin-sample](https://github.com/go-tutorials/go-sql-gin-sample)
74+
- Sample for [Gorilla mux](https://github.com/gorilla/mux) is at [go-sql-sample](https://github.com/go-tutorials/go-sql-sample)
75+
76+
### Enable/Disable Logging
77+
#### Features
78+
- <b>Enable/Disable Logging</b>: Allow users to turn on or off logging for requests, responses, headers, and bodies independently.
79+
- <b>Logging Levels</b>: Support different logging levels (e.g., INFO, DEBUG, ERROR) to control the verbosity of logs.
80+
#### Benefits
81+
- <b>Flexibility</b>: Provides users with the flexibility to configure logging based on their needs and environment.
82+
- <b>Efficiency</b>: Reduces overhead by allowing selective logging, especially in production environments.
83+
84+
### Asynchronous Logging
85+
#### Features
86+
- <b>Non-Blocking Logs</b>: Implement asynchronous logging to ensure that logging does not block request processing.
87+
- <b>Log Buffering</b>: Use buffering to improve logging performance and reduce latency.
88+
#### Benefits:
89+
- <b>Performance</b>: Improves the overall performance of the application by reducing logging overhead.
90+
- <b>Scalability</b>: Allows the application to handle high-throughput logging without performance degradation.
91+
92+
93+
## Use Cases of Sensitive Data Encryption
94+
### Financial Transactions
95+
- <b>Benefit</b>: Encrypting sensitive financial data, such as credit card numbers and transaction details, helps comply with PCI-DSS standards and secures financial transactions from exposure in logs.
96+
### Healthcare
97+
- <b>Benefit</b>: Encrypting patient data such as medical records and health information in logs ensures compliance with HIPAA regulations and protects patient privacy.
98+
### E-commerce
99+
- <b>Benefit</b>: Protecting customer information, such as addresses and payment details, enhances customer trust and protects the e-commerce platform from potential data breaches.
100+
101+
## Benefits of Middleware Logging
102+
#### Debugging and Troubleshooting
103+
- Provides detailed logs that help developers debug and troubleshoot issues in the application by tracing the flow of requests and responses.
104+
#### Monitoring and Alerting
105+
- Enables monitoring of application performance and behavior, allowing for real-time alerting on errors, slow responses, and unusual activity.
106+
#### Performance Optimization
107+
- Logs performance metrics that can be analyzed to identify bottlenecks, optimize resource usage, and improve overall application performance.
108+
#### Security and Compliance
109+
- Helps in tracking access and usage patterns, detecting security incidents, and complying with regulatory requirements by logging relevant information.
110+
#### Auditing
111+
- Provides an audit trail of user actions and system operations, which is essential for security audits and forensic analysis.
112+
113+
## Conclusion
114+
Middleware logging is a critical aspect of building robust, maintainable, and secure applications, providing valuable insights and aiding in the continuous improvement of the software.
6115

7116
## Installation
8117
Please make sure to initialize a Go module before installing core-go/middleware:
@@ -16,17 +125,7 @@ Import:
16125
import "github.com/core-go/middleware"
17126
```
18127

19-
## Features
20-
### log tracing at middleware
21-
#### Support to turn on, turn off
22-
- request
23-
- response
24-
- duration
25-
- http response status code
26-
- response content length
27-
#### Support to mask or encrypt fields
28-
- support to mask or encrypt fields, such as mobileNumber, creditCardNumber
29-
128+
## Appendix
30129
### Microservice Architect
31130
![Microservice Architect](https://cdn-images-1.medium.com/max/800/1*vKeePO_UC73i7tfymSmYNA.png)
32131

echo/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package echo
33
type LogConfig struct {
44
Separate bool `yaml:"separate" mapstructure:"separate" json:"separate,omitempty" gorm:"column:separate" bson:"separate,omitempty" dynamodbav:"separate,omitempty" firestore:"separate,omitempty"`
55
Build bool `yaml:"build" mapstructure:"build" json:"build,omitempty" gorm:"column:build" bson:"build,omitempty" dynamodbav:"build,omitempty" firestore:"build,omitempty"`
6+
StringFormat bool `yaml:"string_format" mapstructure:"string_format" json:"stringFormat,omitempty" gorm:"column:string_format" bson:"stringFormat,omitempty" dynamodbav:"stringFormat,omitempty" firestore:"stringFormat,omitempty"`
67
Log bool `yaml:"log" mapstructure:"log" json:"log,omitempty" gorm:"column:log" bson:"log,omitempty" dynamodbav:"log,omitempty" firestore:"log,omitempty"`
78
Skips string `yaml:"skips" mapstructure:"skips" json:"skips,omitempty" gorm:"column:skips" bson:"skips,omitempty" dynamodbav:"skips,omitempty" firestore:"skips,omitempty"`
89
Ip string `yaml:"ip" mapstructure:"ip" json:"ip,omitempty" gorm:"column:ip" bson:"ip,omitempty" dynamodbav:"ip,omitempty" firestore:"ip,omitempty"`

echo/mask_logger.go

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,52 +14,57 @@ type MaskLogger struct {
1414
RequestKey string
1515
MaskRequest func(map[string]interface{})
1616
MaskResponse func(map[string]interface{})
17+
StringFormat bool
1718
}
1819

19-
func NewMaskLogger(requestKey string, maskRequest func(map[string]interface{}), maskResponse func(map[string]interface{})) *MaskLogger {
20-
return &MaskLogger{RequestKey: requestKey, MaskRequest: maskRequest, MaskResponse: maskResponse}
20+
func NewMaskLogger(requestKey string, maskRequest func(map[string]interface{}), maskResponse func(map[string]interface{}), stringFormat bool) *MaskLogger {
21+
return &MaskLogger{RequestKey: requestKey, MaskRequest: maskRequest, MaskResponse: maskResponse, StringFormat: stringFormat}
2122
}
22-
func NewMaskLoggerWithSending(requestKey string, maskRequest func(map[string]interface{}), maskResponse func(map[string]interface{}), send func(context.Context, []byte, map[string]string) error, options ...map[string]string) *MaskLogger {
23+
func NewMaskLoggerWithSending(requestKey string, maskRequest func(map[string]interface{}), maskResponse func(map[string]interface{}), stringFormat bool, send func(context.Context, []byte, map[string]string) error, options ...map[string]string) *MaskLogger {
2324
var keyMap map[string]string
2425
if len(options) >= 1 {
2526
keyMap = options[0]
2627
}
27-
return &MaskLogger{RequestKey: requestKey, MaskRequest: maskRequest, MaskResponse: maskResponse, send: send, KeyMap: keyMap}
28+
return &MaskLogger{RequestKey: requestKey, MaskRequest: maskRequest, MaskResponse: maskResponse, StringFormat: stringFormat, send: send, KeyMap: keyMap}
2829
}
2930
func (l *MaskLogger) LogResponse(log func(context.Context, string, map[string]interface{}), r *http.Request, ww WrapResponseWriter,
3031
c LogConfig, t1 time.Time, response string, fields map[string]interface{}, includeRequest bool) {
3132
if includeRequest && len(c.Request) > 0 {
32-
MaskRequest(c.Request, fields, l.MaskRequest)
33+
MaskRequest(c.Request, fields, l.MaskRequest, l.StringFormat)
3334
}
34-
MaskResponse(ww, c, t1, response, fields, l.MaskResponse)
35+
MaskResponse(ww, c, t1, response, fields, l.MaskResponse, l.StringFormat)
3536
msg := r.Method + " " + r.RequestURI
3637
log(r.Context(), msg, fields)
3738
if l.send != nil {
3839
go Send(r.Context(), l.send, msg, fields, l.KeyMap)
3940
}
4041
}
4142
func (l *MaskLogger) LogRequest(log func(context.Context, string, map[string]interface{}), r *http.Request, fields map[string]interface{}) {
42-
MaskRequest(l.RequestKey, fields, l.MaskRequest)
43+
MaskRequest(l.RequestKey, fields, l.MaskRequest, l.StringFormat)
4344
msg := "Request " + r.Method + " " + r.RequestURI
4445
log(r.Context(), msg, fields)
4546
if l.send != nil {
4647
go Send(r.Context(), l.send, msg, fields, l.KeyMap)
4748
}
4849
}
4950

50-
func MaskResponse(ww WrapResponseWriter, c LogConfig, t1 time.Time, response string, fields map[string]interface{}, mask func(map[string]interface{})) {
51+
func MaskResponse(ww WrapResponseWriter, c LogConfig, t1 time.Time, response string, fields map[string]interface{}, mask func(map[string]interface{}), isStringFormat bool) {
5152
if len(c.Response) > 0 {
5253
fields[c.Response] = response
5354
responseBody := response
5455
responseMap := map[string]interface{}{}
5556
json.Unmarshal([]byte(responseBody), &responseMap)
5657
if len(responseMap) > 0 {
5758
mask(responseMap)
58-
responseString, err := json.Marshal(responseMap)
59-
if err != nil {
60-
fmt.Printf("Error: %s", err.Error())
59+
if isStringFormat {
60+
responseString, err := json.Marshal(responseMap)
61+
if err != nil {
62+
fmt.Printf("Error: %s", err.Error())
63+
} else {
64+
fields[c.Response] = string(responseString)
65+
}
6166
} else {
62-
fields[c.Response] = string(responseString)
67+
fields[c.Response] = responseMap
6368
}
6469
}
6570
}
@@ -75,7 +80,7 @@ func MaskResponse(ww WrapResponseWriter, c LogConfig, t1 time.Time, response str
7580
fields[c.Size] = ww.BytesWritten()
7681
}
7782
}
78-
func MaskRequest(request string, fields map[string]interface{}, mask func(map[string]interface{})) {
83+
func MaskRequest(request string, fields map[string]interface{}, mask func(map[string]interface{}), isStringFormat bool) {
7984
if len(request) > 0 {
8085
req, ok := fields[request]
8186
if ok {
@@ -85,11 +90,15 @@ func MaskRequest(request string, fields map[string]interface{}, mask func(map[st
8590
json.Unmarshal([]byte(requestBody), &requestMap)
8691
if len(requestMap) > 0 {
8792
mask(requestMap)
88-
requestString, err := json.Marshal(requestMap)
89-
if err != nil {
90-
fmt.Printf("Error: %s", err.Error())
93+
if isStringFormat {
94+
requestString, err := json.Marshal(requestMap)
95+
if err != nil {
96+
fmt.Printf("Error: %s", err.Error())
97+
} else {
98+
fields[request] = string(requestString)
99+
}
91100
} else {
92-
fields[request] = string(requestString)
101+
fields[request] = requestMap
93102
}
94103
}
95104
}

echo/structured_logger.go

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,30 @@ type Formatter interface {
1414
LogResponse(log func(context.Context, string, map[string]interface{}), r *http.Request, ww WrapResponseWriter, c LogConfig, startTime time.Time, response string, fields map[string]interface{}, includeRequest bool)
1515
}
1616
type StructuredLogger struct {
17-
send func(context.Context, []byte, map[string]string) error
18-
KeyMap map[string]string
17+
send func(context.Context, []byte, map[string]string) error
18+
KeyMap map[string]string
19+
RequestKey string
20+
StringFormat bool
1921
}
2022

2123
var fieldConfig FieldConfig
2224

2325
func NewLogger() *StructuredLogger {
2426
return &StructuredLogger{}
2527
}
26-
func NewLoggerWithSending(send func(context.Context, []byte, map[string]string) error, options ...map[string]string) *StructuredLogger {
28+
func NewLoggerWithStringFormat(requestKey string, stringFormat bool) *StructuredLogger {
29+
return &StructuredLogger{RequestKey: requestKey, StringFormat: stringFormat}
30+
}
31+
func NewLoggerWithSending(requestKey string, stringFormat bool, send func(context.Context, []byte, map[string]string) error, options ...map[string]string) *StructuredLogger {
2732
var keyMap map[string]string
2833
if len(options) >= 1 {
2934
keyMap = options[0]
3035
}
31-
return &StructuredLogger{send: send, KeyMap: keyMap}
36+
return &StructuredLogger{RequestKey: requestKey, StringFormat: stringFormat, send: send, KeyMap: keyMap}
3237
}
3338
func (l *StructuredLogger) LogResponse(log func(context.Context, string, map[string]interface{}), r *http.Request, ww WrapResponseWriter,
34-
c LogConfig, t1 time.Time, response string, fields map[string]interface{}, singleLog bool) {
35-
BuildResponse(ww, c, t1, response, fields)
39+
c LogConfig, t1 time.Time, response string, fields map[string]interface{}, includeRequest bool) {
40+
BuildResponse(ww, c, t1, response, fields, l.StringFormat)
3641
msg := r.Method + " " + r.RequestURI
3742
log(r.Context(), msg, fields)
3843
if l.send != nil {
@@ -48,15 +53,52 @@ func Send(ctx context.Context, send func(context.Context, []byte, map[string]str
4853
}
4954
func (l *StructuredLogger) LogRequest(log func(context.Context, string, map[string]interface{}), r *http.Request, fields map[string]interface{}) {
5055
msg := "Request " + r.Method + " " + r.RequestURI
56+
if !l.StringFormat && len(l.RequestKey) > 0 {
57+
req, ok := fields[l.RequestKey]
58+
if ok {
59+
requestBody, ok2 := req.(string)
60+
if ok2 {
61+
requestMap := map[string]interface{}{}
62+
json.Unmarshal([]byte(requestBody), &requestMap)
63+
if len(requestMap) > 0 {
64+
fields[l.RequestKey] = requestMap
65+
}
66+
}
67+
}
68+
}
5169
log(r.Context(), msg, fields)
5270
if l.send != nil {
5371
go Send(r.Context(), l.send, msg, fields, l.KeyMap)
5472
}
5573
}
5674

57-
func BuildResponse(ww WrapResponseWriter, c LogConfig, t1 time.Time, response string, fields map[string]interface{}) {
75+
func BuildResponse(ww WrapResponseWriter, c LogConfig, t1 time.Time, response string, fields map[string]interface{}, isStringFormat bool) {
5876
if len(c.Response) > 0 {
59-
fields[c.Response] = response
77+
if isStringFormat {
78+
fields[c.Response] = response
79+
} else {
80+
responseBody := response
81+
responseMap := map[string]interface{}{}
82+
json.Unmarshal([]byte(responseBody), &responseMap)
83+
if len(responseMap) > 0 {
84+
fields[c.Response] = responseMap
85+
} else {
86+
fields[c.Response] = response
87+
}
88+
}
89+
}
90+
if !isStringFormat && len(c.Request) > 0 {
91+
req, ok := fields[c.Request]
92+
if ok {
93+
requestBody, ok2 := req.(string)
94+
if ok2 {
95+
requestMap := map[string]interface{}{}
96+
json.Unmarshal([]byte(requestBody), &requestMap)
97+
if len(requestMap) > 0 {
98+
fields[c.Request] = requestMap
99+
}
100+
}
101+
}
60102
}
61103
if len(c.ResponseStatus) > 0 {
62104
fields[c.ResponseStatus] = ww.Status()

echo/v3/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package echo
33
type LogConfig struct {
44
Separate bool `yaml:"separate" mapstructure:"separate" json:"separate,omitempty" gorm:"column:separate" bson:"separate,omitempty" dynamodbav:"separate,omitempty" firestore:"separate,omitempty"`
55
Build bool `yaml:"build" mapstructure:"build" json:"build,omitempty" gorm:"column:build" bson:"build,omitempty" dynamodbav:"build,omitempty" firestore:"build,omitempty"`
6+
StringFormat bool `yaml:"string_format" mapstructure:"string_format" json:"stringFormat,omitempty" gorm:"column:string_format" bson:"stringFormat,omitempty" dynamodbav:"stringFormat,omitempty" firestore:"stringFormat,omitempty"`
67
Log bool `yaml:"log" mapstructure:"log" json:"log,omitempty" gorm:"column:log" bson:"log,omitempty" dynamodbav:"log,omitempty" firestore:"log,omitempty"`
78
Skips string `yaml:"skips" mapstructure:"skips" json:"skips,omitempty" gorm:"column:skips" bson:"skips,omitempty" dynamodbav:"skips,omitempty" firestore:"skips,omitempty"`
89
Ip string `yaml:"ip" mapstructure:"ip" json:"ip,omitempty" gorm:"column:ip" bson:"ip,omitempty" dynamodbav:"ip,omitempty" firestore:"ip,omitempty"`

0 commit comments

Comments
 (0)