|
| 1 | +// Copyright The OpenTelemetry Authors |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +package uri |
| 16 | + |
| 17 | +import ( |
| 18 | + "context" |
| 19 | + "fmt" |
| 20 | + "net/url" |
| 21 | + "strings" |
| 22 | + |
| 23 | + "github.com/open-telemetry/opentelemetry-log-collection/entry" |
| 24 | + "github.com/open-telemetry/opentelemetry-log-collection/operator" |
| 25 | + "github.com/open-telemetry/opentelemetry-log-collection/operator/helper" |
| 26 | +) |
| 27 | + |
| 28 | +func init() { |
| 29 | + operator.Register("uri_parser", func() operator.Builder { return NewURIParserConfig("") }) |
| 30 | +} |
| 31 | + |
| 32 | +// NewURIParserConfig creates a new uri parser config with default values. |
| 33 | +func NewURIParserConfig(operatorID string) *URIParserConfig { |
| 34 | + return &URIParserConfig{ |
| 35 | + ParserConfig: helper.NewParserConfig(operatorID, "uri_parser"), |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +// URIParserConfig is the configuration of a uri parser operator. |
| 40 | +type URIParserConfig struct { |
| 41 | + helper.ParserConfig `yaml:",inline"` |
| 42 | +} |
| 43 | + |
| 44 | +// Build will build a uri parser operator. |
| 45 | +func (c URIParserConfig) Build(context operator.BuildContext) ([]operator.Operator, error) { |
| 46 | + parserOperator, err := c.ParserConfig.Build(context) |
| 47 | + if err != nil { |
| 48 | + return nil, err |
| 49 | + } |
| 50 | + |
| 51 | + uriParser := &URIParser{ |
| 52 | + ParserOperator: parserOperator, |
| 53 | + } |
| 54 | + |
| 55 | + return []operator.Operator{uriParser}, nil |
| 56 | +} |
| 57 | + |
| 58 | +// URIParser is an operator that parses a uri. |
| 59 | +type URIParser struct { |
| 60 | + helper.ParserOperator |
| 61 | +} |
| 62 | + |
| 63 | +// Process will parse an entry. |
| 64 | +func (u *URIParser) Process(ctx context.Context, entry *entry.Entry) error { |
| 65 | + return u.ParserOperator.ProcessWith(ctx, entry, u.parse) |
| 66 | +} |
| 67 | + |
| 68 | +// parse will parse a uri from a field and attach it to an entry. |
| 69 | +func (u *URIParser) parse(value interface{}) (interface{}, error) { |
| 70 | + switch m := value.(type) { |
| 71 | + case string: |
| 72 | + return parseURI(m) |
| 73 | + case []byte: |
| 74 | + return parseURI(string(m)) |
| 75 | + default: |
| 76 | + return nil, fmt.Errorf("type '%T' cannot be parsed as URI", value) |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +// parseURI takes an absolute or relative uri and returns the parsed values. |
| 81 | +func parseURI(value string) (map[string]interface{}, error) { |
| 82 | + m := make(map[string]interface{}) |
| 83 | + |
| 84 | + if strings.HasPrefix(value, "?") { |
| 85 | + // remove the query string '?' prefix before parsing |
| 86 | + v, err := url.ParseQuery(value[1:]) |
| 87 | + if err != nil { |
| 88 | + return nil, err |
| 89 | + } |
| 90 | + return queryToMap(v, m), nil |
| 91 | + } |
| 92 | + |
| 93 | + x, err := url.ParseRequestURI(value) |
| 94 | + if err != nil { |
| 95 | + return nil, err |
| 96 | + } |
| 97 | + return urlToMap(x, m), nil |
| 98 | +} |
| 99 | + |
| 100 | +// urlToMap converts a url.URL to a map, excludes any values that are not set. |
| 101 | +func urlToMap(p *url.URL, m map[string]interface{}) map[string]interface{} { |
| 102 | + scheme := p.Scheme |
| 103 | + if scheme != "" { |
| 104 | + m["scheme"] = scheme |
| 105 | + } |
| 106 | + |
| 107 | + user := p.User.Username() |
| 108 | + if user != "" { |
| 109 | + m["user"] = user |
| 110 | + } |
| 111 | + |
| 112 | + host := p.Hostname() |
| 113 | + if host != "" { |
| 114 | + m["host"] = host |
| 115 | + } |
| 116 | + |
| 117 | + port := p.Port() |
| 118 | + if port != "" { |
| 119 | + m["port"] = port |
| 120 | + } |
| 121 | + |
| 122 | + path := p.EscapedPath() |
| 123 | + if path != "" { |
| 124 | + m["path"] = path |
| 125 | + } |
| 126 | + |
| 127 | + return queryToMap(p.Query(), m) |
| 128 | +} |
| 129 | + |
| 130 | +// queryToMap converts a query string url.Values to a map. |
| 131 | +func queryToMap(query url.Values, m map[string]interface{}) map[string]interface{} { |
| 132 | + // no-op if query is empty, do not create the key m["query"] |
| 133 | + if len(query) <= 0 { |
| 134 | + return m |
| 135 | + } |
| 136 | + |
| 137 | + /* 'parameter' will represent url.Values |
| 138 | + map[string]interface{}{ |
| 139 | + "parameter-a": []interface{}{ |
| 140 | + "a", |
| 141 | + "b", |
| 142 | + }, |
| 143 | + "parameter-b": []interface{}{ |
| 144 | + "x", |
| 145 | + "y", |
| 146 | + }, |
| 147 | + } |
| 148 | + */ |
| 149 | + parameters := map[string]interface{}{} |
| 150 | + for param, values := range query { |
| 151 | + parameters[param] = queryParamValuesToMap(values) |
| 152 | + } |
| 153 | + m["query"] = parameters |
| 154 | + return m |
| 155 | +} |
| 156 | + |
| 157 | + |
| 158 | +// queryParamValuesToMap takes query string parameter values and |
| 159 | +// returns an []interface populated with the values |
| 160 | +func queryParamValuesToMap(values []string) []interface{} { |
| 161 | + v := make([]interface{}, len(values)) |
| 162 | + for i, value := range values { |
| 163 | + v[i] = value |
| 164 | + } |
| 165 | + return v |
| 166 | +} |
0 commit comments