| @@ -119,10 +119,23 @@ func buildHTTPRequest( | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if log.V(logger.DebugLevel) { | 	if log.V(logger.DebugLevel) { | ||||||
| 		log.Debug( | 		if shouldLogBody(ct) { | ||||||
| 			ctx, | 			log.Debug( | ||||||
| 			fmt.Sprintf("request %s to %s with headers %v body %s", method, u.String(), hreq.Header, body), | 				ctx, | ||||||
| 		) | 				fmt.Sprintf( | ||||||
|  | 					"micro.client http request: method=%s url=%s headers=%v body=%s", | ||||||
|  | 					method, u.String(), hreq.Header, body, | ||||||
|  | 				), | ||||||
|  | 			) | ||||||
|  | 		} else { | ||||||
|  | 			log.Debug( | ||||||
|  | 				ctx, | ||||||
|  | 				fmt.Sprintf( | ||||||
|  | 					"micro.client http request: method=%s url=%s headers=%v", | ||||||
|  | 					method, u.String(), hreq.Header, | ||||||
|  | 				), | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return hreq, nil | 	return hreq, nil | ||||||
| @@ -261,3 +274,18 @@ func validateHeadersAndCookies(r *http.Request, parameters map[string]map[string | |||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func shouldLogBody(contentType string) bool { | ||||||
|  | 	ct := strings.ToLower(strings.Split(contentType, ";")[0]) | ||||||
|  | 	switch { | ||||||
|  | 	case strings.HasPrefix(ct, "text/"): // => text/html, text/plain, text/csv etc. | ||||||
|  | 		return true | ||||||
|  | 	case ct == "application/json", | ||||||
|  | 		ct == "application/xml", | ||||||
|  | 		ct == "application/x-www-form-urlencoded", | ||||||
|  | 		ct == "application/yaml": | ||||||
|  | 		return true | ||||||
|  | 	default: | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -399,3 +399,49 @@ func TestValidateHeadersAndCookies(t *testing.T) { | |||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestShouldLogBody(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		name        string | ||||||
|  | 		contentType string | ||||||
|  | 		want        bool | ||||||
|  | 	}{ | ||||||
|  | 		// --- text/* | ||||||
|  | 		{"plain text", "text/plain", true}, | ||||||
|  | 		{"html", "text/html", true}, | ||||||
|  | 		{"csv", "text/csv", true}, | ||||||
|  | 		{"yaml text", "text/yaml", true}, | ||||||
|  |  | ||||||
|  | 		// --- application/* | ||||||
|  | 		{"json", "application/json", true}, | ||||||
|  | 		{"xml", "application/xml", true}, | ||||||
|  | 		{"form-urlencoded", "application/x-www-form-urlencoded", true}, | ||||||
|  | 		{"yaml", "application/yaml", true}, | ||||||
|  |  | ||||||
|  | 		// --- with parameters | ||||||
|  | 		{"json with charset", "application/json; charset=utf-8", true}, | ||||||
|  | 		{"binary with charset", "application/octet-stream; charset=utf-8", false}, | ||||||
|  |  | ||||||
|  | 		// --- binary | ||||||
|  | 		{"multipart form", "multipart/form-data", false}, | ||||||
|  | 		{"binary stream", "application/octet-stream", false}, | ||||||
|  | 		{"pdf", "application/pdf", false}, | ||||||
|  | 		{"protobuf", "application/protobuf", false}, | ||||||
|  | 		{"image", "image/png", false}, | ||||||
|  |  | ||||||
|  | 		// --- edge cases | ||||||
|  | 		{"upper case type", "APPLICATION/JSON", true}, | ||||||
|  | 		{"mixed case type", "Text/HTML", true}, | ||||||
|  | 		{"unknown text prefix", "TEXT/FOO", true}, | ||||||
|  | 		{"weird semicolon only", ";", false}, | ||||||
|  | 		{"spaces only", "   ", false}, | ||||||
|  | 		{"empty content-type", "", false}, | ||||||
|  | 		{"missing main type", "/plain", false}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, tt := range tests { | ||||||
|  | 		t.Run(tt.name, func(t *testing.T) { | ||||||
|  | 			require.Equal(t, tt.want, shouldLogBody(tt.contentType)) | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -207,8 +207,6 @@ func (c *Client) parseRsp(ctx context.Context, hrsp *http.Response, rsp any, opt | |||||||
| 	default: | 	default: | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	var buf []byte |  | ||||||
|  |  | ||||||
| 	if opts.ResponseMetadata != nil { | 	if opts.ResponseMetadata != nil { | ||||||
| 		for k, v := range hrsp.Header { | 		for k, v := range hrsp.Header { | ||||||
| 			opts.ResponseMetadata.Append(k, v...) | 			opts.ResponseMetadata.Append(k, v...) | ||||||
| @@ -224,6 +222,8 @@ func (c *Client) parseRsp(ctx context.Context, hrsp *http.Response, rsp any, opt | |||||||
| 		ct = htype | 		ct = htype | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	var buf []byte | ||||||
|  |  | ||||||
| 	if hrsp.Body != nil { | 	if hrsp.Body != nil { | ||||||
| 		var err error | 		var err error | ||||||
| 		buf, err = io.ReadAll(hrsp.Body) | 		buf, err = io.ReadAll(hrsp.Body) | ||||||
| @@ -232,15 +232,31 @@ func (c *Client) parseRsp(ctx context.Context, hrsp *http.Response, rsp any, opt | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if log.V(logger.DebugLevel) { | ||||||
|  | 		if shouldLogBody(ct) { | ||||||
|  | 			log.Debug( | ||||||
|  | 				ctx, | ||||||
|  | 				fmt.Sprintf( | ||||||
|  | 					"micro.client http response: status=%s headers=%v body=%s", | ||||||
|  | 					hrsp.Status, hrsp.Header, buf, | ||||||
|  | 				), | ||||||
|  | 			) | ||||||
|  | 		} else { | ||||||
|  | 			log.Debug( | ||||||
|  | 				ctx, | ||||||
|  | 				fmt.Sprintf( | ||||||
|  | 					"micro.client http response: status=%s headers=%v", | ||||||
|  | 					hrsp.Status, hrsp.Header, | ||||||
|  | 				), | ||||||
|  | 			) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	cf, err := c.newCodec(ct) | 	cf, err := c.newCodec(ct) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return errors.InternalServerError("go.micro.client", "unknown content-type %s: %v", ct, err) | 		return errors.InternalServerError("go.micro.client", "unknown content-type %s: %v", ct, err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if log.V(logger.DebugLevel) { |  | ||||||
| 		log.Debug(ctx, fmt.Sprintf("response with headers: %v and body: %s", hrsp.Header, buf)) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if hrsp.StatusCode < http.StatusBadRequest { | 	if hrsp.StatusCode < http.StatusBadRequest { | ||||||
| 		if err = cf.Unmarshal(buf, rsp); err != nil { | 		if err = cf.Unmarshal(buf, rsp); err != nil { | ||||||
| 			return errors.InternalServerError("go.micro.client", "unmarshal response: %v", err) | 			return errors.InternalServerError("go.micro.client", "unmarshal response: %v", err) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user