Skip to content

Commit

Permalink
parse azapi logs from the live traffic logger (#4)
Browse files Browse the repository at this point in the history
* parse azapi logs from the live traffic logger

* fix go version

* fix unit test
  • Loading branch information
ms-henglu authored Sep 18, 2023
1 parent 45c297b commit 8da9689
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 400 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
## v0.4.0
BREAKING CHANGES:
- Only support logs from terraform-provider-azapi v1.10.0 or above.

ENHANCEMENTS:
- The redundant query parameters are removed in the `markdown` format.
- Remove the `/providers` API from parsed logs, because its response couldn't be parsed.

BUG FIXES:
- Fix the issue that some resources may not be outputted to `azapi` format.

## v0.3.0

FEATURES:
- Support parsing terraform logs to `azapi` traffic format.
- Support parsing terraform logs to `azapi` format.

BUG FIXES:
- Fix the issue that the parsed URL paths are not normalized.
Expand Down
20 changes: 16 additions & 4 deletions formatter/azapi/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ func (formatter *AzapiFormatter) Format(r types.RequestTrace) string {
// ignore the request to other hosts
return ""
}
for _, v := range ignoreKeywords() {
if strings.Contains(r.Url, v) {
return ""
}

if shouldIgnore(r.Url) {
return ""
}

resourceId := GetId(r.Url)
Expand Down Expand Up @@ -151,6 +150,19 @@ func (formatter *AzapiFormatter) Format(r types.RequestTrace) string {
return def.String()
}

func shouldIgnore(url string) bool {
resourceType := GetResourceType(url)
if strings.EqualFold(resourceType, "Microsoft.ApiManagement/service/apis/operations") || strings.EqualFold(resourceType, "Microsoft.ApiManagement/service/apis/operations/tags") {
return false
}
for _, v := range ignoreKeywords() {
if strings.Contains(url, v) {
return true
}
}
return false
}

func (formatter *AzapiFormatter) formatAsAzapiResource(def AzapiDefinition) AzapiDefinition {
formatter.existingResourceSet[def.ResourceId] = true
def.Kind = "resource"
Expand Down
11 changes: 10 additions & 1 deletion formatter/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package formatter
import (
"fmt"
"net/http"
"net/url"
"strings"

"github.com/ms-henglu/pal/types"
Expand All @@ -19,7 +20,15 @@ func (m MarkdownFormatter) Format(r types.RequestTrace) string {
content = strings.ReplaceAll(content, "{Time}", r.TimeStamp.Format("15:04:05"))
content = strings.ReplaceAll(content, "{Method}", r.Method)
content = strings.ReplaceAll(content, "{Host}", r.Host)
content = strings.ReplaceAll(content, "{Url}", r.Url)
urlStr := r.Url
parsedUrl, err := url.Parse(r.Url)
if err == nil {
urlStr = parsedUrl.Path
if value := parsedUrl.Query()["api-version"]; len(value) > 0 {
urlStr += "?api-version=" + value[0]
}
}
content = strings.ReplaceAll(content, "{Url}", urlStr)
content = strings.ReplaceAll(content, "{StatusCode}", fmt.Sprintf("%d", r.StatusCode))
content = strings.ReplaceAll(content, "{StatusMessage}", http.StatusText(r.StatusCode))
content = strings.ReplaceAll(content, "{RequestHeaders}", m.formatHeaders(r.Request.Headers))
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/ms-henglu/pal/trace"
)

const version = "0.3.0"
const version = "0.4.0"

var showHelp = flag.Bool("help", false, "Show help")
var showVersion = flag.Bool("version", false, "Show version")
Expand Down
167 changes: 68 additions & 99 deletions provider/azapi.go
Original file line number Diff line number Diff line change
@@ -1,129 +1,98 @@
package provider

import (
"encoding/json"
"fmt"
"net/url"
"regexp"
"strings"

"github.com/ms-henglu/pal/rawlog"
"github.com/ms-henglu/pal/types"
"github.com/ms-henglu/pal/utils"
)

var _ Provider = AzAPIProvider{}

type AzAPIProvider struct {
}
var r = regexp.MustCompile(`Live traffic: (.+): timestamp`)

func (a AzAPIProvider) IsRequestTrace(l rawlog.RawLog) bool {
return l.Level == "DEBUG" && strings.Contains(l.Message, "Request: ==> OUTGOING REQUEST")
type AzAPIProvider struct {
}

func (a AzAPIProvider) IsResponseTrace(l rawlog.RawLog) bool {
return l.Level == "DEBUG" && strings.Contains(l.Message, "Response: ==> REQUEST/RESPONSE")
func (a AzAPIProvider) IsTrafficTrace(l rawlog.RawLog) bool {
return l.Level == "DEBUG" && strings.Contains(l.Message, "Live traffic:")
}

func (a AzAPIProvider) ParseRequest(l rawlog.RawLog) (*types.RequestTrace, error) {
method := ""
host := ""
uriPath := ""
body := ""
headers := make(map[string]string)
for _, line := range strings.Split(l.Message, "\n") {
line = strings.Trim(line, " ")
switch {
case line == "" || strings.Contains(line, "==>") ||
strings.Contains(line, "Request contained no body") ||
strings.Contains(line, "-----"):
continue
case strings.Contains(line, ": "):
key, value, err := utils.ParseHeader(line)
if err != nil {
return nil, err
}
headers[key] = value
case utils.IsJson(line):
body = line
default:
if parts := strings.Split(line, " "); len(parts) == 2 {
method = parts[0]
parsedUrl, err := url.Parse(parts[1])
if err == nil {
host = parsedUrl.Host
uriPath = fmt.Sprintf("%s?%s", parsedUrl.Path, parsedUrl.RawQuery)
}
}
}
func (a AzAPIProvider) ParseTraffic(l rawlog.RawLog) (*types.RequestTrace, error) {
matches := r.FindAllStringSubmatch(l.Message, -1)
if len(matches) == 0 || len(matches[0]) != 2 {
return nil, fmt.Errorf("failed to parse request trace, no matches found")
}
return &types.RequestTrace{
TimeStamp: l.TimeStamp,
Method: method,
Host: host,
Url: utils.NormalizeUrlPath(uriPath),
Provider: "azapi",
Request: &types.HttpRequest{
Headers: headers,
Body: body,
},
}, nil
}

func (a AzAPIProvider) ParseResponse(l rawlog.RawLog) (*types.RequestTrace, error) {
method := ""
host := ""
uriPath := ""
body := ""
headers := make(map[string]string)

sections := strings.Split(l.Message, strings.Repeat("-", 80))
message := l.Message
if len(sections) == 4 {
body = sections[2]
message = utils.LineAt(sections[0], 1) + sections[1]
trafficJson := matches[0][1]
var liveTraffic traffic
err := json.Unmarshal([]byte(trafficJson), &liveTraffic)
if err != nil {
return nil, fmt.Errorf("failed to parse request trace, %v", err)
}

for _, line := range strings.Split(message, "\n") {
line = strings.Trim(line, " ")
switch {
case line == "" || strings.Contains(line, "==>") ||
strings.Contains(line, "contained no body") ||
strings.Contains(line, "-----"):
continue
case strings.Contains(line, ": "):
key, value, err := utils.ParseHeader(line)
if err != nil {
return nil, err
}
headers[key] = value
case utils.IsJson(line):
body = line
default:
if parts := strings.Split(line, " "); len(parts) == 2 {
method = parts[0]
parsedUrl, err := url.Parse(parts[1])
if err == nil {
host = parsedUrl.Host
uriPath = fmt.Sprintf("%s?%s", parsedUrl.Path, parsedUrl.RawQuery)
}
}
}
parsedUrl, err := url.Parse(liveTraffic.LiveRequest.Url)
if err != nil {
return nil, fmt.Errorf("failed to parse request trace, %v", err)
}

statusCode := 0
if v := headers["RESPONSE Status"]; v != "" {
delete(headers, "RESPONSE Status")
fmt.Sscanf(v, "%d", &statusCode)
if liveTraffic.LiveRequest.Headers == nil {
liveTraffic.LiveRequest.Headers = map[string]string{}
}
if liveTraffic.LiveResponse.Headers == nil {
liveTraffic.LiveResponse.Headers = map[string]string{}
}

return &types.RequestTrace{
TimeStamp: l.TimeStamp,
Method: method,
Host: host,
Url: utils.NormalizeUrlPath(uriPath),
StatusCode: statusCode,
Method: liveTraffic.LiveRequest.Method,
Host: parsedUrl.Host,
Url: parsedUrl.Path + "?" + parsedUrl.RawQuery,
StatusCode: liveTraffic.LiveResponse.StatusCode,
Provider: "azapi",
Request: &types.HttpRequest{
Headers: liveTraffic.LiveRequest.Headers,
Body: liveTraffic.LiveRequest.Body,
},
Response: &types.HttpResponse{
Headers: headers,
Body: body,
Headers: liveTraffic.LiveResponse.Headers,
Body: liveTraffic.LiveResponse.Body,
},
}, nil
}

func (a AzAPIProvider) IsRequestTrace(l rawlog.RawLog) bool {
return false
}

func (a AzAPIProvider) IsResponseTrace(l rawlog.RawLog) bool {
return false
}

func (a AzAPIProvider) ParseRequest(l rawlog.RawLog) (*types.RequestTrace, error) {
return nil, fmt.Errorf("not implemented")
}

func (a AzAPIProvider) ParseResponse(l rawlog.RawLog) (*types.RequestTrace, error) {
return nil, fmt.Errorf("not implemented")
}

type traffic struct {
LiveRequest liveRequest `json:"request"`
LiveResponse liveResponse `json:"response"`
}

type liveRequest struct {
Headers map[string]string `json:"headers"`
Method string `json:"method"`
Url string `json:"url"`
Body string `json:"body"`
}

type liveResponse struct {
StatusCode int `json:"statusCode"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
}
Loading

0 comments on commit 8da9689

Please sign in to comment.