1
1
package logging
2
2
3
3
import (
4
- "log/slog"
5
- "net/http"
6
- "net/url"
7
- "strings"
8
- "time"
9
-
10
- "github.com/Azure/aks-middleware/http/common"
11
- azcorePolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
4
+ "log/slog"
5
+ "net/http"
6
+ "net/url"
7
+ "strings"
8
+ "time"
9
+
10
+ "github.com/Azure/aks-middleware/http/common"
11
+ azcorePolicy "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
12
+ "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
12
13
)
13
14
14
- var resourceTypes = map [ string ] bool {
15
- "resourcegroups" : true ,
16
- "storageaccounts" : true ,
17
- "operationresults" : true ,
18
- "asyncoperations" : true ,
19
- "checknameavailability" : true ,
15
+ type LogRequestParams struct {
16
+ Logger * slog. Logger
17
+ StartTime time. Time
18
+ Request interface {}
19
+ Response * http. Response
20
+ Error error
20
21
}
21
22
22
- type LogRequestParams struct {
23
- Logger * slog.Logger
24
- StartTime time.Time
25
- Request interface {}
26
- Response * http.Response
27
- Error error
23
+ func trimToSubscription (rawURL string ) string {
24
+ // Find the index of "/subscriptions"
25
+ if idx := strings .Index (rawURL , "/subscriptions" ); idx != - 1 {
26
+ return rawURL [idx :]
27
+ }
28
+ return rawURL
29
+ }
30
+
31
+ func sanitizeResourceType (rt string , rawURL string ) string {
32
+ // Keep only the substring after the last slash.
33
+ if idx := strings .LastIndex (rt , "/" ); idx != - 1 && idx < len (rt )- 1 {
34
+ rt = rt [idx + 1 :]
35
+ }
36
+ // Remove everything after the first '?'.
37
+ if idx := strings .Index (rt , "?" ); idx != - 1 {
38
+ rt = rt [:idx ]
39
+ }
40
+ rt = strings .ToLower (rt )
41
+
42
+ return rt
28
43
}
29
44
30
- // Shared logging function for REST API interactions
31
45
func getMethodInfo (method string , rawURL string ) string {
32
- urlParts := strings .Split (rawURL , "?api-version" )
33
- // malformed url
34
- // check for v1 to ensure we aren't classifying restlogger as malformed
35
- if len (urlParts ) < 2 && ! strings .Contains (urlParts [0 ], "v1" ) {
36
- return method + " " + rawURL
37
- }
38
- parts := strings .Split (urlParts [0 ], "/" )
39
- resource := urlParts [0 ]
40
- counter := 0
41
- // Start from the end of the split path and move backward
42
- // to get nested resource type
43
- for counter = len (parts ) - 1 ; counter >= 0 ; counter -- {
44
- currToken := strings .ToLower (parts [counter ])
45
- if strings .ContainsAny (currToken , "?/" ) {
46
- index := strings .IndexAny (currToken , "?/" )
47
- currToken = currToken [:index ]
48
- }
49
- if resourceTypes [currToken ] {
50
- resource = currToken
51
- break
52
- }
53
- }
54
-
55
- if method == "GET" {
56
- // resource name is specified, so it is a READ op
57
- if counter == len (parts )- 1 {
58
- resource = resource + " - LIST"
59
- } else {
60
- resource = resource + " - READ"
61
- }
62
- }
63
-
64
- // REST VERB + Resource Type
65
- methodInfo := method + " " + resource
66
-
67
- return methodInfo
46
+ // Trim the URL to ensure it starts with "/subscriptions"
47
+ validURL := trimToSubscription (rawURL )
48
+
49
+ // First, try to parse validURL as a full resource ID.
50
+ id , err := arm .ParseResourceID (validURL )
51
+ if err != nil {
52
+ // Retry by appending a false resource name ("dummy")
53
+ // To be a valid resource ID, the URL must end with the resource name.
54
+ fakeURL := validURL
55
+ if ! strings .HasSuffix (validURL , "/dummy" ) {
56
+ fakeURL = validURL + "/dummy"
57
+ }
58
+ id , err = arm .ParseResourceID (fakeURL )
59
+ if err != nil {
60
+ // Fallback: if parsing still fails, use the full URL.
61
+ return method + " " + rawURL
62
+ }
63
+ // We know a fake resource name was added.
64
+ if method == "GET" {
65
+ // For GET requests with a fake name, we assume it's a list operation.
66
+ return method + " " + sanitizeResourceType (id .ResourceType .String (), rawURL ) + " - LIST"
67
+ }
68
+ return method + " " + sanitizeResourceType (id .ResourceType .String (), rawURL )
69
+ }
70
+
71
+ // If parsing was successful on the first try.
72
+ if method == "GET" {
73
+ op := " - READ"
74
+ if strings .TrimSpace (id .Name ) == "" {
75
+ op = " - LIST"
76
+ }
77
+ return method + " " + sanitizeResourceType (id .ResourceType .String (), rawURL ) + op
78
+ }
79
+ return method + " " + sanitizeResourceType (id .ResourceType .String (), rawURL )
68
80
}
69
81
70
82
func trimURL (parsedURL url.URL ) string {
71
- // Extract the `api-version` parameter
72
- apiVersion := parsedURL .Query ().Get ("api-version" )
73
-
74
- // Reconstruct the URL with only the `api-version` parameter
75
- baseURL := parsedURL .Scheme + "://" + parsedURL .Host + parsedURL .Path
76
- if apiVersion != "" {
77
- return baseURL + "?api-version=" + apiVersion
78
- }
79
- return baseURL
83
+ // Extract the `api-version` parameter
84
+ apiVersion := parsedURL .Query ().Get ("api-version" )
85
+
86
+ // Reconstruct the URL with only the `api-version` parameter
87
+ baseURL := parsedURL .Scheme + "://" + parsedURL .Host + parsedURL .Path
88
+ if apiVersion != "" {
89
+ return baseURL + "?api-version=" + apiVersion
90
+ }
91
+ return baseURL
80
92
}
81
93
82
94
func LogRequest (params LogRequestParams ) {
83
- var method , service , reqURL string
84
- switch req := params .Request .(type ) {
85
- case * http.Request :
86
- method = req .Method
87
- service = req .Host
88
- reqURL = req .URL .String ()
89
-
90
- case * azcorePolicy.Request :
91
- method = req .Raw ().Method
92
- service = req .Raw ().Host
93
- reqURL = req .Raw ().URL .String ()
94
- default :
95
- return // Unknown request type, do nothing
96
- }
97
-
98
- parsedURL , parseErr := url .Parse (reqURL )
99
- if parseErr != nil {
100
- params .Logger .With (
101
- "source" , "ApiRequestLog" ,
102
- "protocol" , "REST" ,
103
- "method_type" , "unary" ,
104
- "code" , "na" ,
105
- "component" , "client" ,
106
- "time_ms" , "na" ,
107
- "method" , method ,
108
- "service" , service ,
109
- "url" , reqURL ,
110
- "error" , parseErr .Error (),
111
- ).Error ("error parsing request URL" )
112
- } else {
113
- reqURL = trimURL (* parsedURL )
114
- }
115
-
116
- methodInfo := getMethodInfo (method , reqURL )
117
- latency := time .Since (params .StartTime ).Milliseconds ()
118
-
119
- var headers map [string ]string
120
- if params .Response != nil {
121
- headers = extractHeaders (params .Response .Header )
122
- }
123
-
124
- logEntry := params .Logger .With (
125
- "source" , "ApiRequestLog" ,
126
- "protocol" , "REST" ,
127
- "method_type" , "unary" ,
128
- "component" , "client" ,
129
- "time_ms" , latency ,
130
- "method" , methodInfo ,
131
- "service" , service ,
132
- "url" , reqURL ,
133
- "headers" , headers ,
134
- )
135
-
136
- if params .Error != nil || params .Response == nil {
137
- logEntry .With ("error" , params .Error .Error (), "code" , "na" ).Error ("finished call" )
138
- } else if 200 <= params .Response .StatusCode && params .Response .StatusCode < 300 {
139
- logEntry .With ("error" , "na" , "code" , params .Response .StatusCode ).Info ("finished call" )
140
- } else {
141
- logEntry .With ("error" , params .Response .Status , "code" , params .Response .StatusCode ).Error ("finished call" )
142
- }
95
+ var method , service , reqURL string
96
+ switch req := params .Request .(type ) {
97
+ case * http.Request :
98
+ method = req .Method
99
+ service = req .Host
100
+ reqURL = req .URL .String ()
101
+
102
+ case * azcorePolicy.Request :
103
+ method = req .Raw ().Method
104
+ service = req .Raw ().Host
105
+ reqURL = req .Raw ().URL .String ()
106
+ default :
107
+ return // Unknown request type, do nothing
108
+ }
109
+
110
+ parsedURL , parseErr := url .Parse (reqURL )
111
+ if parseErr != nil {
112
+ params .Logger .With (
113
+ "source" , "ApiRequestLog" ,
114
+ "protocol" , "REST" ,
115
+ "method_type" , "unary" ,
116
+ "code" , "na" ,
117
+ "component" , "client" ,
118
+ "time_ms" , "na" ,
119
+ "method" , method ,
120
+ "service" , service ,
121
+ "url" , reqURL ,
122
+ "error" , parseErr .Error (),
123
+ ).Error ("error parsing request URL" )
124
+ } else {
125
+ reqURL = trimURL (* parsedURL )
126
+ }
127
+
128
+ methodInfo := getMethodInfo (method , reqURL )
129
+ latency := time .Since (params .StartTime ).Milliseconds ()
130
+
131
+ var headers map [string ]string
132
+ if params .Response != nil {
133
+ headers = extractHeaders (params .Response .Header )
134
+ }
135
+
136
+ logEntry := params .Logger .With (
137
+ "source" , "ApiRequestLog" ,
138
+ "protocol" , "REST" ,
139
+ "method_type" , "unary" ,
140
+ "component" , "client" ,
141
+ "time_ms" , latency ,
142
+ "method" , methodInfo ,
143
+ "service" , service ,
144
+ "url" , reqURL ,
145
+ "headers" , headers ,
146
+ )
147
+
148
+ if params .Error != nil || params .Response == nil {
149
+ logEntry .With ("error" , params .Error .Error (), "code" , "na" ).Error ("finished call" )
150
+ } else if 200 <= params .Response .StatusCode && params .Response .StatusCode < 300 {
151
+ logEntry .With ("error" , "na" , "code" , params .Response .StatusCode ).Info ("finished call" )
152
+ } else {
153
+ logEntry .With ("error" , params .Response .Status , "code" , params .Response .StatusCode ).Error ("finished call" )
154
+ }
143
155
}
144
156
145
157
func extractHeaders (header http.Header ) map [string ]string {
146
- headers := make (map [string ]string )
147
-
148
- // List of headers to extract
149
- headerKeys := []string {
150
- common .RequestCorrelationIDHeader ,
151
- common .RequestAcsOperationIDHeader ,
152
- common .RequestARMClientRequestIDHeader ,
153
- }
154
-
155
- // Convert header keys to lowercase
156
- lowerHeader := make (http.Header )
157
- for key , values := range header {
158
- lowerHeader [strings .ToLower (key )] = values
159
- }
160
-
161
- for _ , key := range headerKeys {
162
- lowerKey := strings .ToLower (key )
163
- if values , ok := lowerHeader [lowerKey ]; ok && len (values ) > 0 {
164
- headers [key ] = values [0 ]
165
- }
166
- }
167
-
168
- return headers
169
- }
158
+ headers := make (map [string ]string )
159
+
160
+ // List of headers to extract
161
+ headerKeys := []string {
162
+ common .RequestCorrelationIDHeader ,
163
+ common .RequestAcsOperationIDHeader ,
164
+ common .RequestARMClientRequestIDHeader ,
165
+ }
166
+
167
+ // Convert header keys to lowercase
168
+ lowerHeader := make (http.Header )
169
+ for key , values := range header {
170
+ lowerHeader [strings .ToLower (key )] = values
171
+ }
172
+
173
+ for _ , key := range headerKeys {
174
+ lowerKey := strings .ToLower (key )
175
+ if values , ok := lowerHeader [lowerKey ]; ok && len (values ) > 0 {
176
+ headers [key ] = values [0 ]
177
+ }
178
+ }
179
+
180
+ return headers
181
+ }
0 commit comments